| import os |
| import numpy as np |
| import torch |
| from torch.utils.data import IterableDataset, DataLoader |
| import tensorflow as tf |
| import tensorflow_datasets as tfds |
|
|
| tf.config.set_visible_devices([], 'GPU') |
|
|
| action_min_spatial = [-0.9375, -0.9375, -0.9375, -0.1875, -0.3675000071525574, -0.36000001430511475] |
| action_max_spatial = [0.9375, 0.9375, 0.9375, 0.1971428543329239, 0.33642858266830444, 0.375] |
|
|
| action_min_object = [-0.8839285969734192, -0.9375, -0.9375, -0.15000000596046448, -0.29035714268684387, -0.32892856001853943] |
| action_max_object = [0.9375, 0.8919642567634583, 0.9375, 0.17678570747375488, 0.35035714507102966, 0.1810714304447174] |
|
|
| action_min_goal = [-0.9375, -0.9375, -0.9375, -0.2582142949104309, -0.375, -0.2871428430080414] |
| action_max_goal = [0.9375, 0.9375, 0.9375, 0.3557142913341522, 0.375, 0.375] |
|
|
| action_min_10 = [-0.9375, -0.9375, -0.9375, -0.23642857372760773, -0.3053571283817291, -0.3675000071525574] |
| action_max_10 = [0.9375, 0.9375, 0.9375, 0.30000001192092896, 0.29357144236564636, 0.375] |
|
|
| action_min_mixed = [-0.9375, -0.9375, -0.9375, -0.2582142949104309, -0.375, -0.3675000071525574] |
| action_max_mixed = [0.9375, 0.9375, 0.9375, 0.3557142913341522, 0.375, 0.375] |
|
|
|
|
| def get_libero_normalization_suite_name(task_suite_name, configured_suite_name=None): |
| if configured_suite_name: |
| return configured_suite_name |
| return task_suite_name |
|
|
|
|
| def get_libero_action_stats(suite_name): |
| """Return action min/max stats for a LIBERO normalization suite.""" |
| stats_suite_name = suite_name |
|
|
| if 'mixed' in stats_suite_name: |
| action_min, action_max = action_min_mixed, action_max_mixed |
| elif 'spatial' in stats_suite_name: |
| action_min, action_max = action_min_spatial, action_max_spatial |
| elif 'object' in stats_suite_name: |
| action_min, action_max = action_min_object, action_max_object |
| elif 'goal' in stats_suite_name: |
| action_min, action_max = action_min_goal, action_max_goal |
| elif '10' in stats_suite_name: |
| action_min, action_max = action_min_10, action_max_10 |
| else: |
| raise ValueError(f"Unknown LIBERO stats suite '{stats_suite_name}'") |
|
|
| return np.array(action_min, dtype=np.float32), np.array(action_max, dtype=np.float32) |
|
|
|
|
| class LiberoAct(IterableDataset): |
| def __init__( |
| self, |
| data_path, |
| dataset_name='libero', |
| normalization_suite_name=None, |
| length=None, |
| history_len=15, |
| future_len=15, |
| full_sequence=True, |
| input_modality="video", |
| view_mode="single", |
| buffer_size=10000, |
| sampling_rate=0.1, |
| allow_end_padding=True, |
| load_future_image=False, |
| future_image_mode="horizon", |
| ): |
| super().__init__() |
| self.data_path = data_path |
| self.dataset_name = dataset_name |
| self.length = length |
| self.history_len = history_len |
| self.future_len = future_len |
| self.full_sequence = full_sequence |
| self.input_modality = input_modality |
| self.view_mode = view_mode |
| self.buffer_size = buffer_size |
| self.sampling_rate = sampling_rate |
| self.allow_end_padding = allow_end_padding |
| self.load_future_image = load_future_image |
| self.future_image_mode = future_image_mode |
|
|
| self.normalization_suite_name = get_libero_normalization_suite_name( |
| dataset_name, normalization_suite_name |
| ) |
| self.action_min, self.action_max = get_libero_action_stats(self.normalization_suite_name) |
|
|
| self.action_denominator = self.action_max - self.action_min |
| self.action_denominator = np.where(self.action_denominator == 0, 1.0, self.action_denominator) |
|
|
| zero_pose = np.zeros(6, dtype=np.float32) |
| self.normalized_zero_pose = 2.0 * (zero_pose - self.action_min) / self.action_denominator - 1.0 |
| self.normalized_zero_pose = np.clip(self.normalized_zero_pose, -1.0, 1.0) |
|
|
| def __iter__(self): |
| builder = tfds.builder_from_directory(builder_dir=self.data_path) |
| |
| read_config = tfds.ReadConfig(shuffle_seed=42, shuffle_reshuffle_each_iteration=False) |
| ds = builder.as_dataset(split='train', shuffle_files=False, read_config=read_config) |
| |
| if self.length is not None: |
| ds = ds.take(self.length) |
|
|
| shuffle_buffer = [] |
| BUFFER_SIZE = self.buffer_size |
|
|
| main_key = "image" |
| wrist_key = "wrist_image" |
|
|
| if torch.distributed.is_available() and torch.distributed.is_initialized(): |
| rank = torch.distributed.get_rank() |
| world_size = torch.distributed.get_world_size() |
| else: |
| rank = 0 |
| world_size = 1 |
|
|
| worker_info = torch.utils.data.get_worker_info() |
| if worker_info is None: |
| worker_id = 0 |
| num_workers = 1 |
| else: |
| worker_id = worker_info.id |
| num_workers = worker_info.num_workers |
|
|
| total_shards = world_size * num_workers |
| shard_index = rank * num_workers + worker_id |
| ds_iterator = ds.shard(num_shards=total_shards, index=shard_index) |
|
|
| for traj_id, traj_data in enumerate(ds_iterator): |
| try: |
| traj_batch = next(iter(traj_data['steps'].batch(2000))) |
|
|
| if traj_batch['reward'][-1].numpy() != 1: |
| continue |
|
|
| traj_len = traj_batch['action'].shape[0] |
|
|
| obs = traj_batch['observation'] |
| images_np = obs[main_key].numpy() |
| if images_np.dtype != np.uint8: |
| images_np = (images_np * 255).astype(np.uint8) |
| |
| wrist_np = None |
| if self.view_mode == "multi": |
| if wrist_key in obs: |
| wrist_np = obs[wrist_key].numpy() |
| if wrist_np.dtype != np.uint8: |
| wrist_np = (wrist_np * 255).astype(np.uint8) |
| else: |
| wrist_np = images_np |
|
|
| |
| raw_state = traj_batch['observation']['state'].numpy().astype(np.float32) |
| |
| |
| |
| |
| |
| gripper_qpos = raw_state[:, 6:8] |
| gripper_state = 1.0 - (np.mean(np.abs(gripper_qpos), axis=1, keepdims=True) / 0.04) |
| gripper_state = np.clip(gripper_state, 0.0, 1.0) |
| |
| proprio_np = np.concatenate([raw_state[:, :6], gripper_state], axis=1) |
|
|
| |
| raw_actions = traj_batch['action'].numpy().astype(np.float32) |
| delta_pose = raw_actions[:, :6] |
|
|
| delta_pose = 2.0 * (delta_pose - self.action_min) / self.action_denominator - 1.0 |
| delta_pose = np.clip(delta_pose, -1.0, 1.0) |
|
|
| |
| |
| |
| |
| gripper_action = raw_actions[:, 6:7] |
| gripper_action = np.clip(gripper_action, -1.0, 1.0) |
|
|
| actions_np = np.concatenate([delta_pose, gripper_action], axis=1) |
|
|
| |
| |
| |
| pad_action_hist = np.concatenate([self.normalized_zero_pose, actions_np[0:1, 6]]) |
| pad_action_fut = np.concatenate([self.normalized_zero_pose, actions_np[-1:, 6]]) |
| |
| instruction = traj_batch['language_instruction'][0].numpy().decode('utf-8') |
|
|
| if self.full_sequence: |
| sample_indices = np.arange(traj_len) |
| else: |
| num_samples = max(1, int(traj_len * self.sampling_rate)) |
| sample_indices = np.random.choice(traj_len, size=num_samples, replace=False) |
|
|
| if not self.allow_end_padding: |
| sample_indices = sample_indices[sample_indices + self.future_len <= traj_len] |
|
|
| for t in sample_indices: |
| start_hist_obs = t - self.history_len + 1 |
| hist_indices_obs = np.arange(start_hist_obs, t + 1) |
| hist_indices_obs = np.clip(hist_indices_obs, 0, traj_len - 1) |
| |
| start_hist_act = t - self.history_len |
| hist_indices_act = np.arange(start_hist_act, t) |
| |
| end_fut = t + self.future_len |
| fut_indices = np.arange(t, end_fut) |
|
|
| hist_imgs = images_np[hist_indices_obs] |
| hist_imgs_wrist = wrist_np[hist_indices_obs] if wrist_np is not None else None |
| hist_proprio = torch.from_numpy(proprio_np[hist_indices_obs]) |
| |
| hist_actions = np.tile(pad_action_hist, (self.history_len, 1)).astype(np.float32) |
| valid_mask = hist_indices_act >= 0 |
| if np.any(valid_mask): |
| valid_indices = hist_indices_act[valid_mask] |
| valid_indices = np.clip(valid_indices, 0, traj_len - 1) |
| hist_actions[valid_mask] = actions_np[valid_indices] |
| hist_actions = torch.from_numpy(hist_actions) |
|
|
| fut_acts_np = np.tile(pad_action_fut, (self.future_len, 1)).astype(np.float32) |
| valid_mask_fut = fut_indices < traj_len |
| if np.any(valid_mask_fut): |
| valid_indices_fut = fut_indices[valid_mask_fut] |
| fut_acts_np[valid_mask_fut] = actions_np[valid_indices_fut] |
| fut_acts = torch.from_numpy(fut_acts_np) |
|
|
| sample = { |
| 'proprioception': hist_proprio, |
| 'history_actions': hist_actions, |
| 'future_actions': fut_acts, |
| 'instruction': instruction, |
| } |
|
|
| if self.load_future_image: |
| if self.future_image_mode == "last": |
| target_idx = traj_len - 1 |
| else: |
| target_idx = min(t + self.future_len, traj_len - 1) |
| sample['future_image'] = images_np[target_idx] |
|
|
| if self.input_modality == "video": |
| sample['video'] = hist_imgs |
| if self.view_mode == "multi": |
| sample['video_wrist'] = hist_imgs_wrist if hist_imgs_wrist is not None else hist_imgs |
| elif self.input_modality == "image": |
| sample['image'] = images_np[t] |
| if self.view_mode == "multi": |
| sample['image_wrist'] = wrist_np[t] if wrist_np is not None else images_np[t] |
| else: |
| raise ValueError(f"Unknown input_modality: {self.input_modality}") |
|
|
| shuffle_buffer.append(sample) |
| |
| if len(shuffle_buffer) >= BUFFER_SIZE: |
| idx = np.random.randint(len(shuffle_buffer)) |
| shuffle_buffer[idx], shuffle_buffer[-1] = shuffle_buffer[-1], shuffle_buffer[idx] |
| yield shuffle_buffer.pop() |
|
|
| except Exception as e: |
| print(f"[Warn] Skipping trajectory {traj_id} due to error: {e}") |
| continue |
|
|
| np.random.shuffle(shuffle_buffer) |
| for sample in shuffle_buffer: |
| yield sample |
|
|
| class LiberoMixedAct(IterableDataset): |
| """Mix of all four LIBERO suites (spatial, object, goal, 10) for joint training. |
| |
| All sub-datasets are constructed with ``dataset_name="libero_mixed"`` so every |
| suite is normalized with the shared ``action_min_mixed`` / ``action_max_mixed`` |
| stats (union across suites). This keeps a single consistent [-1, 1] target |
| space across the mixed batch. |
| At iteration time, samples are drawn uniformly at random from whichever |
| sub-iterators are still active; when a sub-iterator is exhausted it is dropped |
| and the remaining iterators continue. |
| """ |
|
|
| SUITES = ("libero_spatial", "libero_object", "libero_goal", "libero_10") |
|
|
| def __init__( |
| self, |
| data_root, |
| version="1.0.0", |
| suites=None, |
| length=None, |
| history_len=15, |
| future_len=15, |
| full_sequence=True, |
| input_modality="video", |
| view_mode="single", |
| buffer_size=10000, |
| sampling_rate=0.1, |
| allow_end_padding=True, |
| load_future_image=False, |
| future_image_mode="horizon", |
| ): |
| super().__init__() |
| self.suites = tuple(suites) if suites is not None else self.SUITES |
| self._sub_datasets = [ |
| LiberoAct( |
| data_path=os.path.join(data_root, suite, version), |
| dataset_name="libero_mixed", |
| normalization_suite_name="libero_mixed", |
| length=length, |
| history_len=history_len, |
| future_len=future_len, |
| full_sequence=full_sequence, |
| input_modality=input_modality, |
| view_mode=view_mode, |
| buffer_size=buffer_size, |
| sampling_rate=sampling_rate, |
| allow_end_padding=allow_end_padding, |
| load_future_image=load_future_image, |
| future_image_mode=future_image_mode, |
| ) |
| for suite in self.suites |
| ] |
|
|
| def __iter__(self): |
| iterators = [iter(ds) for ds in self._sub_datasets] |
| active = list(range(len(iterators))) |
| while active: |
| i = active[np.random.randint(len(active))] |
| try: |
| yield next(iterators[i]) |
| except StopIteration: |
| active.remove(i) |
|
|
|
|
| def collate_fn(batch): |
| return batch |
|
|
| if __name__ == "__main__": |
| """ |
| Fast stats: count how many training samples LiberoAct would yield for each suite. |
| Also computes min/max statistics for the first 6 dimensions of actions. |
| """ |
| from tqdm import tqdm |
|
|
| |
| BASE_DIR = "/data/NTU_slab/draven/data/LIBERO_modified" |
| SUITES = [ |
| "libero_spatial", |
| "libero_object", |
| "libero_goal", |
| "libero_10", |
| ] |
| VERSION = "1.0.0" |
|
|
| print(f"Scanning Libero datasets in {BASE_DIR}...") |
|
|
| mixed_total_trajs = 0 |
| mixed_success_trajs = 0 |
| mixed_total_samples = 0 |
| mixed_act_min = np.full(6, np.inf) |
| mixed_act_max = np.full(6, -np.inf) |
|
|
| for suite_name in SUITES: |
| data_path = os.path.join(BASE_DIR, suite_name, VERSION) |
| if not os.path.exists(data_path): |
| continue |
|
|
| builder = tfds.builder_from_directory(builder_dir=data_path) |
| read_config = tfds.ReadConfig(shuffle_seed=42, shuffle_reshuffle_each_iteration=False) |
| ds = builder.as_dataset(split='train', shuffle_files=False, read_config=read_config) |
|
|
| total_files = builder.info.splits['train'].num_examples |
|
|
| total_trajs = 0 |
| success_trajs = 0 |
| total_samples = 0 |
|
|
| act_min = np.full(6, np.inf) |
| act_max = np.full(6, -np.inf) |
|
|
| print(f"\nProcessing {suite_name} ({total_files} trajectories)...") |
| pbar = tqdm(enumerate(ds), total=total_files, unit="traj", desc=suite_name) |
|
|
| for traj_id, traj_data in pbar: |
| total_trajs += 1 |
| try: |
| traj_batch = next(iter(traj_data['steps'].batch(2000))) |
| if traj_batch['reward'][-1].numpy() != 1: |
| continue |
|
|
| success_trajs += 1 |
| traj_len = int(traj_batch['action'].shape[0]) |
|
|
| |
| actions = traj_batch['action'].numpy()[:, :6] |
| current_min = np.min(actions, axis=0) |
| current_max = np.max(actions, axis=0) |
| act_min = np.minimum(act_min, current_min) |
| act_max = np.maximum(act_max, current_max) |
|
|
| total_samples += traj_len |
| pbar.set_postfix({"Succ": success_trajs, "Samples": total_samples}) |
|
|
| except Exception as e: |
| continue |
|
|
| mixed_total_trajs += total_trajs |
| mixed_success_trajs += success_trajs |
| mixed_total_samples += total_samples |
| mixed_act_min = np.minimum(mixed_act_min, act_min) |
| mixed_act_max = np.maximum(mixed_act_max, act_max) |
|
|
| print(f"--- {suite_name} Stats ---") |
| print(f"Total Trajectories: {total_trajs}") |
| print(f"Successful Trajs: {success_trajs}") |
| print(f"Avg Samples/Succ: {total_samples / success_trajs:.4f}" if success_trajs > 0 else "") |
| print(f"action_min = {act_min.tolist()}") |
| print(f"action_max = {act_max.tolist()}") |
|
|
| print(f"\n--- libero_mixed Stats (union of {', '.join(SUITES)}) ---") |
| print(f"Total Trajectories: {mixed_total_trajs}") |
| print(f"Successful Trajs: {mixed_success_trajs}") |
| print(f"Avg Samples/Succ: {mixed_total_samples / mixed_success_trajs:.4f}" if mixed_success_trajs > 0 else "") |
| print(f"action_min_mixed = {mixed_act_min.tolist()}") |
| print(f"action_max_mixed = {mixed_act_max.tolist()}") |
|
|