From 3c38b2e0ea64c25242e06f328df75c097e9ce5f4 Mon Sep 17 00:00:00 2001 From: liangkaiz Date: Tue, 30 Jun 2026 23:22:36 -0700 Subject: [PATCH 1/5] Add more action datasets --- .../data/vfm/action/agibot_spec.py | 2 +- .../data/vfm/action/datasets/__init__.py | 4 + .../agibotworld_beta_lerobot_dataset.py | 2 +- .../datasets/bridge_orig_lerobot_dataset.py | 2 +- .../action/datasets/droid_lerobot_dataset.py | 2 +- .../datasets/fractal_lerobot_dataset.py | 188 ++++++++++ .../datasets/robomind_franka_dataset.py | 139 ++++++-- .../action/datasets/robomind_ur_dataset.py | 203 +++++++++++ .../action/datasets/umi_lerobot_dataset.py | 2 +- .../agibotworld_beta_lerobot_stats.json | 0 .../bridge_orig_lerobot_stats.json | 0 .../droid_lerobot_stats.json | 0 .../fractal_lerobot_stats.json | 4 + .../robomind_franka_dual_stats.json} | 0 .../robomind_franka_stats.json | 4 + .../normalizer_stats/robomind_ur_stats.json | 4 + .../umi_lerobot_stats.json | 0 .../G1_omnipicker_calibrated.urdf | 0 .../action/robot_assets/ur5e_robotiq_2f85.xml | 321 ++++++++++++++++++ 19 files changed, 837 insertions(+), 40 deletions(-) create mode 100644 cosmos_framework/data/vfm/action/datasets/fractal_lerobot_dataset.py create mode 100644 cosmos_framework/data/vfm/action/datasets/robomind_ur_dataset.py rename cosmos_framework/data/vfm/action/{datasets/stats => normalizer_stats}/agibotworld_beta_lerobot_stats.json (100%) rename cosmos_framework/data/vfm/action/{datasets/stats => normalizer_stats}/bridge_orig_lerobot_stats.json (100%) rename cosmos_framework/data/vfm/action/{datasets/stats => normalizer_stats}/droid_lerobot_stats.json (100%) create mode 100644 cosmos_framework/data/vfm/action/normalizer_stats/fractal_lerobot_stats.json rename cosmos_framework/data/vfm/action/{datasets/stats/robomind_franka_stats.json => normalizer_stats/robomind_franka_dual_stats.json} (100%) create mode 100644 cosmos_framework/data/vfm/action/normalizer_stats/robomind_franka_stats.json create mode 100644 cosmos_framework/data/vfm/action/normalizer_stats/robomind_ur_stats.json rename cosmos_framework/data/vfm/action/{datasets/stats => normalizer_stats}/umi_lerobot_stats.json (100%) rename cosmos_framework/data/vfm/action/{urdf_visualizer => robot_assets}/G1_omnipicker_calibrated.urdf (100%) create mode 100644 cosmos_framework/data/vfm/action/robot_assets/ur5e_robotiq_2f85.xml diff --git a/cosmos_framework/data/vfm/action/agibot_spec.py b/cosmos_framework/data/vfm/action/agibot_spec.py index 0abfe22a..853afd58 100644 --- a/cosmos_framework/data/vfm/action/agibot_spec.py +++ b/cosmos_framework/data/vfm/action/agibot_spec.py @@ -126,4 +126,4 @@ def get_agibot_world_kind(embodiment_type: str) -> AgibotWorldKind: def get_agibot_world_urdf_path() -> Path: """Return the committed AgiBot G1 omnipicker URDF path.""" - return Path(__file__).resolve().parent / "urdf_visualizer" / AGIBOT_WORLD_URDF_FILENAME + return Path(__file__).resolve().parent / "robot_assets" / AGIBOT_WORLD_URDF_FILENAME diff --git a/cosmos_framework/data/vfm/action/datasets/__init__.py b/cosmos_framework/data/vfm/action/datasets/__init__.py index 64b1b278..b94b1440 100644 --- a/cosmos_framework/data/vfm/action/datasets/__init__.py +++ b/cosmos_framework/data/vfm/action/datasets/__init__.py @@ -12,7 +12,9 @@ from cosmos_framework.data.vfm.action.datasets.base_dataset import ActionBaseDataset from cosmos_framework.data.vfm.action.datasets.bridge_orig_lerobot_dataset import BridgeOrigLeRobotDataset from cosmos_framework.data.vfm.action.datasets.droid_lerobot_dataset import DROIDLeRobotDataset +from cosmos_framework.data.vfm.action.datasets.fractal_lerobot_dataset import FractalLeRobotDataset from cosmos_framework.data.vfm.action.datasets.robomind_franka_dataset import RoboMINDFrankaDataset +from cosmos_framework.data.vfm.action.datasets.robomind_ur_dataset import RoboMINDURDataset from cosmos_framework.data.vfm.action.datasets.umi_lerobot_dataset import UMILeRobotDataset __all__ = [ @@ -20,6 +22,8 @@ "AgiBotWorldBetaLeRobotDataset", "BridgeOrigLeRobotDataset", "DROIDLeRobotDataset", + "FractalLeRobotDataset", "RoboMINDFrankaDataset", + "RoboMINDURDataset", "UMILeRobotDataset", ] diff --git a/cosmos_framework/data/vfm/action/datasets/agibotworld_beta_lerobot_dataset.py b/cosmos_framework/data/vfm/action/datasets/agibotworld_beta_lerobot_dataset.py index f95feea8..ce22773c 100644 --- a/cosmos_framework/data/vfm/action/datasets/agibotworld_beta_lerobot_dataset.py +++ b/cosmos_framework/data/vfm/action/datasets/agibotworld_beta_lerobot_dataset.py @@ -39,7 +39,7 @@ _ROBOT_POSITION_KEY = "observation.states.robot.position" _ROBOT_ORIENTATION_KEY = "observation.states.robot.orientation" -_NORMALIZER_PATH = Path(__file__).parent / "stats/agibotworld_beta_lerobot_stats.json" +_NORMALIZER_PATH = Path(__file__).parent.parent / "normalizer_stats/agibotworld_beta_lerobot_stats.json" def _split_task_for_caption(task: str) -> tuple[str, str]: diff --git a/cosmos_framework/data/vfm/action/datasets/bridge_orig_lerobot_dataset.py b/cosmos_framework/data/vfm/action/datasets/bridge_orig_lerobot_dataset.py index 5992ce67..25e167b0 100644 --- a/cosmos_framework/data/vfm/action/datasets/bridge_orig_lerobot_dataset.py +++ b/cosmos_framework/data/vfm/action/datasets/bridge_orig_lerobot_dataset.py @@ -51,7 +51,7 @@ dtype=np.float32, ) -_NORMALIZER_PATH = Path(__file__).parent / "stats/bridge_orig_lerobot_stats.json" +_NORMALIZER_PATH = Path(__file__).parent.parent / "normalizer_stats/bridge_orig_lerobot_stats.json" class BridgeOrigLeRobotDataset(ActionBaseDataset): diff --git a/cosmos_framework/data/vfm/action/datasets/droid_lerobot_dataset.py b/cosmos_framework/data/vfm/action/datasets/droid_lerobot_dataset.py index 3bd18598..f3faa91f 100644 --- a/cosmos_framework/data/vfm/action/datasets/droid_lerobot_dataset.py +++ b/cosmos_framework/data/vfm/action/datasets/droid_lerobot_dataset.py @@ -52,7 +52,7 @@ dtype=np.float32, ) -_NORMALIZER_PATH = Path(__file__).parent / "stats/droid_lerobot_stats.json" +_NORMALIZER_PATH = Path(__file__).parent.parent / "normalizer_stats/droid_lerobot_stats.json" class DROIDLeRobotDataset(ActionBaseDataset): diff --git a/cosmos_framework/data/vfm/action/datasets/fractal_lerobot_dataset.py b/cosmos_framework/data/vfm/action/datasets/fractal_lerobot_dataset.py new file mode 100644 index 00000000..fe67137b --- /dev/null +++ b/cosmos_framework/data/vfm/action/datasets/fractal_lerobot_dataset.py @@ -0,0 +1,188 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: OpenMDW-1.1 + +"""Fractal (fractal20220817_data) LeRobot dataset — Google Robot RT-1. + +Robot: google_robot +87,212 episodes, 3,786,400 frames, 599 tasks, fps=3 +state: [x, y, z, rx, ry, rz, rw, gripper] (8D, quaternion) +action: [x, y, z, roll, pitch, yaw, gripper] (7D, delta) +video: observation.images.image (256×320) +""" + +from __future__ import annotations + +import random +from pathlib import Path +from typing import Any, Literal + +import numpy as np +import torch +from lerobot.datasets.video_utils import decode_video_frames + +from cosmos_framework.data.vfm.action.action_spec import ActionSpec, Gripper, Pos, Rot, build_action_spec +from cosmos_framework.data.vfm.action.datasets.base_dataset import ActionBaseDataset +from cosmos_framework.data.vfm.action.pose_utils import ( + build_abs_pose_from_components, + pose_abs_to_rel, +) + +PoseConvention = Literal["backward_framewise"] +Viewpoint = Literal["ego_view"] + +_IMAGE_FEATURE = "observation.images.image" +_STATE_FEATURE = "observation.state" +_ACTION_FEATURE = "action" + +# These episodes contain base motion, which breaks the fixed-base Google Robot +# action assumption used by training and the viewer. +_SKIPPED_EPISODE_IDS: frozenset[int] = frozenset({29, 189, 382}) + +# Google Robot raw EE frame has x/y axes rotated ~90° around z compared to +# OpenCV convention. Rz(-90°) as a right-multiply corrects this: +# new_x = -old_y (rightward), new_y = old_x (downward), z unchanged (approach). +_GOOGLE_ROBOT_TO_OPENCV = np.array([[0, 1, 0], [-1, 0, 0], [0, 0, 1]], dtype=np.float32) + +# --------------------------------------------------------------------------- +# TCP → flange (gripper body) offset +# --------------------------------------------------------------------------- +# The fractal dataset records EE poses at ``link_gripper_tcp`` — a calibrated +# tool-center-point 164 mm past the gripper body (``link_gripper``), roughly +# at the fingertip. Re-referencing to ``link_gripper`` removes the +# calibration-dependent tilt and places the frame at the last actuated link. +# +# T = oMf[link_gripper_tcp]⁻¹ · oMf[link_gripper], computed via pinocchio FK +# at the neutral config from the SimplerEnv URDF. +# fmt: off +_TCP_TO_FLANGE = np.array([ + [+0.9999897671, -0.0008686425, +0.0044397163, -0.0050618476], + [+0.0008745501, +0.9999987346, -0.0013288658, -0.0016717725], + [-0.0044385564, +0.0013327349, +0.9999892615, -0.1635144743], + [+0.0000000000, +0.0000000000, +0.0000000000, +1.0000000000], +], dtype=np.float32) +# fmt: on + +_NORMALIZER_PATH = Path(__file__).parent.parent / "normalizer_stats/fractal_lerobot_stats.json" + + +class FractalLeRobotDataset(ActionBaseDataset): + """Fractal (Google RT-1) dataset with 10D cartesian actions: + + [pos_delta(3), rot6d_delta(6), gripper(1)] + + Expects a LeRobot v2 dataset with: + * ``observation.images.image``: ego-view RGB video (256×320). + * ``observation.state``: 8D EE pose ``[x, y, z, rx, ry, rz, rw, gripper]`` + in TCP frame with quaternion (x, y, z, w) order. + * ``action``: 7D delta ``[x, y, z, roll, pitch, yaw, gripper]``; only the + gripper column (index 6) is used — SE(3) actions are derived from state. + + Episodes in ``_SKIPPED_EPISODE_IDS`` (base-motion outliers) are dropped. + """ + + def __init__( + self, + root: str, + fps: float = 3.0, + chunk_length: int = 16, + mode: str = "joint", + pose_convention: PoseConvention = "backward_framewise", + tolerance_s: float = 1e-4, + viewpoint: Viewpoint = "ego_view", + action_normalization: str | None = None, + sample_stride: int = 1, + ) -> None: + if viewpoint != "ego_view": + raise NotImplementedError("FractalLeRobotDataset only supports ego_view.") + super().__init__( + root=root, + domain_name="fractal", + fps=fps, + chunk_length=chunk_length, + mode=mode, + pose_convention=pose_convention, + tolerance_s=tolerance_s, + viewpoint=viewpoint, + action_normalization=action_normalization, + sample_stride=sample_stride, + ) + + # Drop rows belonging to known-bad episodes (base motion outliers). + n_before = len(self._rows) + self._rows = [row for row in self._rows if int(row["episode_index"]) not in _SKIPPED_EPISODE_IDS] + n_dropped = n_before - len(self._rows) + if n_dropped: + import logging + logging.getLogger(__name__).info( + "FractalLeRobotDataset: dropped %d / %d rows from episodes %s", + n_dropped, + n_before, + sorted(_SKIPPED_EPISODE_IDS), + ) + + @property + def action_dim(self) -> int: + """Action dimensionality: position(3) + 6D rotation(6) + gripper(1) = 10.""" + return 10 + + def _action_spec(self) -> ActionSpec: + return build_action_spec(Pos(dim=3), Rot("rot6d"), Gripper()) + + @classmethod + def _stats_path(cls) -> Path: + return _NORMALIZER_PATH + + def __getitem__(self, idx: int) -> dict[str, Any]: + mode = self._choose_mode() + idx = int(idx) + row_idx = idx * self._sample_stride + observation_rows = self._rows[row_idx : row_idx + self._chunk_length + 1] + action_rows = observation_rows[: self._chunk_length] + + episode = self._episodes[int(observation_rows[0]["episode_index"])] + task = self._tasks[int(observation_rows[0]["task_index"])] + ai_caption = random.choice([part.strip() for part in task.split(" | ") if part.strip()] or [task]) + + video = self._load_video(episode, observation_rows) + raw_action, initial_pose = self._build_raw_action(observation_rows, action_rows) + + return self._build_result( + mode=mode, + video=video, + action=raw_action, + ai_caption=ai_caption, + initial_pose=initial_pose, + ) + + def _load_video(self, episode: dict[str, Any], observation_rows: list[dict[str, Any]]) -> torch.Tensor: + timestamps = [float(row["timestamp"]) for row in observation_rows] + return decode_video_frames( + self._video_path(episode, _IMAGE_FEATURE), + [float(episode.get(f"videos/{_IMAGE_FEATURE}/from_timestamp", 0.0)) + ts for ts in timestamps], + self._tolerance_s, + ) + + def _build_raw_action( + self, + observation_rows: list[dict[str, Any]], + action_rows: list[dict[str, Any]], + ) -> tuple[torch.Tensor, torch.Tensor]: + # State layout: [x, y, z, rx, ry, rz, rw, gripper] (T+1 frames) + # Quaternion order: (rx, ry, rz, rw) matches scipy's (x, y, z, w). + state = np.asarray([row[_STATE_FEATURE] for row in observation_rows], dtype=np.float32) # [T+1, 8] + poses_abs = build_abs_pose_from_components(state[:, 0:3], state[:, 3:7], "quat_xyzw") # [T+1, 4, 4] + + # 1. TCP → flange: shift from link_gripper_tcp to link_gripper + poses_abs = poses_abs @ _TCP_TO_FLANGE + # 2. Kinematics → OpenCV convention (rotation only) + poses_abs[:, :3, :3] = poses_abs[:, :3, :3] @ _GOOGLE_ROBOT_TO_OPENCV + + initial_pose = torch.from_numpy(poses_abs[0].copy()).float() + poses_rel = pose_abs_to_rel(poses_abs, rotation_format="rot6d", pose_convention=self._pose_convention) + + gripper = np.asarray( + [row[_ACTION_FEATURE][6] for row in action_rows], dtype=np.float32 + ).reshape(-1, 1) # [T, 1] + + action = np.concatenate([poses_rel[-self._chunk_length :], gripper[-self._chunk_length :]], axis=-1) + return torch.from_numpy(action).float(), initial_pose diff --git a/cosmos_framework/data/vfm/action/datasets/robomind_franka_dataset.py b/cosmos_framework/data/vfm/action/datasets/robomind_franka_dataset.py index 136cd6c0..53b0a2c6 100644 --- a/cosmos_framework/data/vfm/action/datasets/robomind_franka_dataset.py +++ b/cosmos_framework/data/vfm/action/datasets/robomind_franka_dataset.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: OpenMDW-1.1 -"""RoboMIND Franka LeRobot dataset.""" +"""RoboMIND Franka LeRobot dataset — single-arm and dual-arm variants.""" from __future__ import annotations @@ -14,6 +14,7 @@ import torch.nn.functional as F from lerobot.datasets.video_utils import decode_video_frames +from cosmos_framework.data.vfm.action.action_normalization import load_action_stats from cosmos_framework.data.vfm.action.action_spec import ActionSpec, Gripper, Pos, Rot, build_action_spec from cosmos_framework.data.vfm.action.datasets.base_dataset import ActionBaseDataset from cosmos_framework.data.vfm.action.pose_utils import ( @@ -22,10 +23,16 @@ ) PoseConvention = Literal["backward_framewise"] -Viewpoint = Literal["concat_view"] +Viewpoint = Literal["concat_view", "third_person_view"] -_IMAGE_FEATURES = { - "front": "observation.images.camera_front", +# Dual-arm uses camera_front as the top view; single-arm uses camera_top. +_IMAGE_FEATURES_DUAL = { + "top": "observation.images.camera_front", + "left": "observation.images.camera_left", + "right": "observation.images.camera_right", +} +_IMAGE_FEATURES_SINGLE = { + "top": "observation.images.camera_top", "left": "observation.images.camera_left", "right": "observation.images.camera_right", } @@ -39,7 +46,8 @@ dtype=np.float32, ) -_NORMALIZER_PATH = Path(__file__).parent / "stats/robomind_franka_stats.json" +_NORMALIZER_PATH_DUAL = Path(__file__).parent.parent / "normalizer_stats/robomind_franka_dual_stats.json" +_NORMALIZER_PATH_SINGLE = Path(__file__).parent.parent / "normalizer_stats/robomind_franka_stats.json" def _dual_arm_action_spec(): @@ -54,15 +62,15 @@ def _dual_arm_action_spec(): class RoboMINDFrankaDataset(ActionBaseDataset): - """RoboMIND Franka dual-arm dataset with 20D cartesian actions:: - - [left_pos_delta(3), left_rot6d_delta(6), left_gripper(1), - right_pos_delta(3), right_rot6d_delta(6), right_gripper(1)] + """RoboMIND Franka dataset — single-arm (10D) or dual-arm (20D) cartesian actions. - Single-arm shards, split/filter logic, image augmentation, fast - initialization, and alternate viewpoints are omitted. + Single-arm (``robomind-franka``): ``[pos_delta(3), rot6d_delta(6), gripper(1)]`` + Dual-arm (``robomind-franka-dual``): + ``[left_pos(3), left_rot6d(6), left_gripper(1), + right_pos(3), right_rot6d(6), right_gripper(1)]`` """ + _SUPPORTED_EMBODIMENTS = ("robomind-franka", "robomind-franka-dual") def __init__( self, @@ -77,11 +85,17 @@ def __init__( action_normalization: str | None = "quantile", sample_stride: int = 1, ) -> None: - if embodiment_type != "robomind-franka-dual": - raise NotImplementedError("This minimal RoboMIND dataset only supports robomind-franka-dual.") - if viewpoint != "concat_view": - raise NotImplementedError("This minimal RoboMIND dataset only supports concat_view.") + if embodiment_type not in self._SUPPORTED_EMBODIMENTS: + raise ValueError( + f"RoboMINDFrankaDataset only supports {self._SUPPORTED_EMBODIMENTS}, " + f"got embodiment_type={embodiment_type!r}." + ) + if viewpoint not in ("concat_view", "third_person_view"): + raise NotImplementedError(f"RoboMINDFrankaDataset does not support viewpoint={viewpoint!r}.") self._embodiment_type = embodiment_type + self._image_features = ( + _IMAGE_FEATURES_SINGLE if embodiment_type == "robomind-franka" else _IMAGE_FEATURES_DUAL + ) super().__init__( root=root, domain_name=embodiment_type, @@ -97,41 +111,82 @@ def __init__( @property def action_dim(self) -> int: - return 20 + return 10 if self._embodiment_type == "robomind-franka" else 20 def _action_spec(self) -> ActionSpec: + if self._embodiment_type == "robomind-franka": + return build_action_spec(Pos(), Rot("rot6d"), Gripper()) return _dual_arm_action_spec() @classmethod def _stats_path(cls) -> Path: - return _NORMALIZER_PATH + return _NORMALIZER_PATH_DUAL + + def _load_norm_stats(self) -> dict[str, torch.Tensor]: + if self._norm_stats is None: + path = _NORMALIZER_PATH_SINGLE if self._embodiment_type == "robomind-franka" else _NORMALIZER_PATH_DUAL + self._norm_stats = { + key: torch.from_numpy(value).float() + for key, value in load_action_stats(str(path)).items() + } + return self._norm_stats def __getitem__(self, idx: int) -> dict[str, Any]: mode = self._choose_mode() idx = int(idx) - first_row = self._rows[idx] - episode = self._episodes[int(first_row["episode_index"])] - row_idx = idx * self._sample_stride observation_rows = self._rows[row_idx : row_idx + self._chunk_length + 1] action_rows = observation_rows[: self._chunk_length] - video = self._load_concat_video(episode, observation_rows) - raw_action, initial_pose_left, initial_pose_right = self._build_raw_action(observation_rows, action_rows) + episode = self._episodes[int(observation_rows[0]["episode_index"])] task = self._tasks[int(observation_rows[0]["task_index"])] ai_caption = random.choice([part.strip() for part in task.split(" | ") if part.strip()] or [task]) + if self._viewpoint == "concat_view": + video = self._load_concat_video(episode, observation_rows) + if self._embodiment_type == "robomind-franka": + view_desc = ( + "The top row shows a third-person perspective looking towards the single-arm Franka robot from above. " + "The bottom-left view looks at the scene from the left side, and the bottom-right view looks at the scene from the right side." + ) + else: + view_desc = ( + "The top row shows a third-person perspective looking towards the dual-arm Franka robot from the front. " + "The bottom-left view looks at the scene from the left side, and the bottom-right view looks at the scene from the right side." + ) + else: + video = self._load_single_video(episode, observation_rows) + view_desc = None + + if self._embodiment_type == "robomind-franka": + raw_action, initial_pose = self._build_raw_action_single(observation_rows, action_rows) + extras: dict[str, Any] = {"initial_pose": initial_pose} + else: + raw_action, initial_pose_left, initial_pose_right = self._build_raw_action_dual(observation_rows, action_rows) + extras = {"initial_pose": initial_pose_left, "initial_pose_right": initial_pose_right} + + if view_desc is not None: + extras["additional_view_description"] = view_desc + return self._build_result( mode=mode, video=video, action=raw_action, ai_caption=ai_caption, - initial_pose=initial_pose_left, - initial_pose_right=initial_pose_right, - additional_view_description=( - "The top row shows a third-person perspective looking towards the dual-arm Franka robot from the front. " - "The bottom-left view looks at the scene from the left side, and the bottom-right view looks at the scene from the right side." - ), + **extras, + ) + + def _load_single_video( + self, + episode: dict[str, Any], + observation_rows: list[dict[str, Any]], + ) -> torch.Tensor: + video_key = self._image_features["top"] + timestamps = [float(row["timestamp"]) for row in observation_rows] + return decode_video_frames( + self._video_path(episode, video_key), + [float(episode.get(f"videos/{video_key}/from_timestamp", 0.0)) + ts for ts in timestamps], + self._tolerance_s, ) def _load_concat_video( @@ -146,18 +201,18 @@ def _load_concat_video( [float(episode.get(f"videos/{video_key}/from_timestamp", 0.0)) + ts for ts in timestamps], self._tolerance_s, ) - for name, video_key in _IMAGE_FEATURES.items() + for name, video_key in self._image_features.items() } - front = frames_by_view["front"] + top = frames_by_view["top"] left = frames_by_view["left"] right = frames_by_view["right"] - _, _, h_front, w_front = front.shape - half_h, half_w = h_front // 2, w_front // 2 + _, _, h_top, w_top = top.shape + half_h, half_w = h_top // 2, w_top // 2 left = F.interpolate(left, size=(half_h, half_w), mode="bilinear", align_corners=False) right = F.interpolate(right, size=(half_h, half_w), mode="bilinear", align_corners=False) bottom = torch.cat([left, right], dim=-1) - return torch.cat([front, bottom], dim=-2) + return torch.cat([top, bottom], dim=-2) def _build_relative_poses( self, @@ -170,7 +225,21 @@ def _build_relative_poses( poses_rel = pose_abs_to_rel(poses_abs, rotation_format="rot6d", pose_convention=self._pose_convention) return poses_rel, initial_pose - def _build_raw_action( + def _build_raw_action_single( + self, + observation_rows: list[dict[str, Any]], + action_rows: list[dict[str, Any]], + ) -> tuple[torch.Tensor, torch.Tensor]: + state = np.asarray([row[_STATE_FEATURE] for row in observation_rows], dtype=np.float32) + gripper = np.asarray([row[_ACTION_FEATURE] for row in action_rows], dtype=np.float32) + poses_rel, initial_pose = self._build_relative_poses(state[:, 0:3], state[:, 3:6]) + action = np.concatenate( + [poses_rel[-self._chunk_length :], 1.0 - gripper[-self._chunk_length :, [7]]], + axis=-1, + ) # [T, 10] + return torch.from_numpy(action).float(), initial_pose + + def _build_raw_action_dual( self, observation_rows: list[dict[str, Any]], action_rows: list[dict[str, Any]], @@ -188,5 +257,5 @@ def _build_raw_action( 1.0 - gripper[-self._chunk_length :, [15]], ], axis=-1, - ) + ) # [T, 20] return torch.from_numpy(action).float(), initial_pose_left, initial_pose_right diff --git a/cosmos_framework/data/vfm/action/datasets/robomind_ur_dataset.py b/cosmos_framework/data/vfm/action/datasets/robomind_ur_dataset.py new file mode 100644 index 00000000..317525f7 --- /dev/null +++ b/cosmos_framework/data/vfm/action/datasets/robomind_ur_dataset.py @@ -0,0 +1,203 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: OpenMDW-1.1 + +"""RoboMIND UR LeRobot dataset for single-arm UR5e embodiment.""" + +from __future__ import annotations + +import random +from pathlib import Path +from typing import Any, Literal + +import numpy as np +import torch +from lerobot.datasets.video_utils import decode_video_frames + +from cosmos_framework.data.vfm.action.action_spec import ActionSpec, Gripper, Pos, Rot, build_action_spec +from cosmos_framework.data.vfm.action.datasets.base_dataset import ActionBaseDataset +from cosmos_framework.data.vfm.action.pose_utils import pose_abs_to_rel + +PoseConvention = Literal["backward_framewise"] +Viewpoint = Literal["third_person_view"] + +_IMAGE_FEATURE = "observation.images.camera_top" +_ACTION_JOINT_FEATURE = "actions.joint_position" + +# UR EE frame → OpenCV convention rotation (3×3, post-multiplied). +# Identity: attachment_site (quat="-1 1 0 0" in ur5e_robotiq_2f85.xml) already +# satisfies OpenCV convention (z = approach). +_ROBOMIND_UR_TO_OPENCV: np.ndarray = np.eye(3, dtype=np.float32) + +_UR5E_ARM_JOINTS = 6 # shoulder_pan … wrist_3 +_UR5E_EE_SITE = "attachment_site" # flange site in ur5e_robotiq_2f85.xml + +_MJCF_PATH = Path(__file__).resolve().parent.parent / "robot_assets" / "ur5e_robotiq_2f85.xml" + +_NORMALIZER_PATH = Path(__file__).parent.parent / "normalizer_stats/robomind_ur_stats.json" + + +class RoboMINDURDataset(ActionBaseDataset): + """RoboMIND UR dataset with 10D cartesian actions: + + [pos_delta(3), rot6d_delta(6), gripper(1)] + + Uses FK on ``actions.joint_position`` (6 arm joints → MuJoCo + ``attachment_site`` SE(3) pose). ``observation.states.end_effector`` is + NOT used — it is recorded incorrectly (constant) in ~89 % of UR episodes; + ``actions.joint_position`` is valid for 100 % of episodes. + + The sample also includes ``joint_configs``: absolute joint angles ``(T, 7)`` + from ``actions.joint_position[1:T+1]`` for FK-based robot mesh animation. + """ + + def __init__( + self, + root: str, + fps: float = 10.0, + chunk_length: int = 16, + mode: str = "joint", + pose_convention: PoseConvention = "backward_framewise", + tolerance_s: float = 1e-4, + viewpoint: Viewpoint = "third_person_view", + action_normalization: str | None = None, + sample_stride: int = 1, + ) -> None: + if viewpoint != "third_person_view": + raise NotImplementedError("RoboMINDURDataset only supports third_person_view.") + super().__init__( + root=root, + domain_name="robomind-ur", + fps=fps, + chunk_length=chunk_length, + mode=mode, + pose_convention=pose_convention, + tolerance_s=tolerance_s, + viewpoint=viewpoint, + action_normalization=action_normalization, + sample_stride=sample_stride, + ) + self._mj_model, self._mj_data, self._ee_site_id = self._init_mujoco() + + @staticmethod + def _init_mujoco(): + """Load UR5e+Robotiq MuJoCo model (kinematics-only) and locate the EE site. + + Strips all geoms and mesh/texture/material assets from the MJCF via + ``MjSpec`` before compile so the model loads without mesh files on disk. + FK only needs the kinematic tree, so ``mj_forward`` + site poses still + produce identical EE poses. + """ + import mujoco + + spec = mujoco.MjSpec.from_file(str(_MJCF_PATH)) + + def _strip_geoms(body): + for g in list(body.geoms): + spec.delete(g) + for child in body.bodies: + _strip_geoms(child) + + _strip_geoms(spec.worldbody) + for m in list(spec.meshes): + spec.delete(m) + for t in list(spec.textures): + spec.delete(t) + for mat in list(spec.materials): + spec.delete(mat) + + mj_model = spec.compile() + mj_data = mujoco.MjData(mj_model) + ee_site_id = mujoco.mj_name2id(mj_model, mujoco.mjtObj.mjOBJ_SITE, _UR5E_EE_SITE) + if ee_site_id < 0: + raise RuntimeError(f"EE site '{_UR5E_EE_SITE}' not found in {_MJCF_PATH}") + return mj_model, mj_data, ee_site_id + + def _fk_ee_poses(self, arm_q: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + """Run MuJoCo FK for T+1 arm configs → EE site positions and rotations. + + Args: + arm_q: ``(T+1, 6)`` arm joint angles in radians. + + Returns: + ``(positions (T+1, 3), rotations (T+1, 3, 3))`` in MuJoCo world frame. + """ + import mujoco + + T1 = len(arm_q) + positions = np.empty((T1, 3), dtype=np.float32) + rotations = np.empty((T1, 3, 3), dtype=np.float32) + for t in range(T1): + self._mj_data.qpos[:_UR5E_ARM_JOINTS] = arm_q[t] + mujoco.mj_forward(self._mj_model, self._mj_data) + positions[t] = self._mj_data.site_xpos[self._ee_site_id] + rotations[t] = self._mj_data.site_xmat[self._ee_site_id].reshape(3, 3) + return positions, rotations + + @property + def action_dim(self) -> int: + return 10 + + def _action_spec(self) -> ActionSpec: + return build_action_spec(Pos(), Rot("rot6d"), Gripper()) + + @classmethod + def _stats_path(cls) -> Path: + return _NORMALIZER_PATH + + def __getitem__(self, idx: int) -> dict[str, Any]: + mode = self._choose_mode() + idx = int(idx) + row_idx = idx * self._sample_stride + # T+1 rows: current frame + T future frames (needed for FK EE poses and joint_configs) + observation_rows = self._rows[row_idx : row_idx + self._chunk_length + 1] + + episode = self._episodes[int(observation_rows[0]["episode_index"])] + task = self._tasks[int(observation_rows[0]["task_index"])] + ai_caption = random.choice([part.strip() for part in task.split(" | ") if part.strip()] or [task]) + + video = self._load_video(episode, observation_rows) + action, initial_pose, joint_configs = self._build_raw_action(observation_rows) + + return self._build_result( + mode=mode, + video=video, + action=action, + ai_caption=ai_caption, + initial_pose=initial_pose, + joint_configs=joint_configs, + ) + + def _load_video(self, episode: dict[str, Any], observation_rows: list[dict[str, Any]]) -> torch.Tensor: + timestamps = [float(row["timestamp"]) for row in observation_rows] + return decode_video_frames( + self._video_path(episode, _IMAGE_FEATURE), + [float(episode.get(f"videos/{_IMAGE_FEATURE}/from_timestamp", 0.0)) + ts for ts in timestamps], + self._tolerance_s, + ) + + def _build_raw_action( + self, + observation_rows: list[dict[str, Any]], + ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + # [T+1, 7]: 6 arm joints + 1 gripper command + q = np.asarray([row[_ACTION_JOINT_FEATURE] for row in observation_rows], dtype=np.float32) + T = len(q) - 1 + + # FK EE trajectory: T+1 absolute poses from arm joints via MuJoCo + fk_pos, fk_rot = self._fk_ee_poses(q[:, :_UR5E_ARM_JOINTS]) + poses_abs = np.tile(np.eye(4, dtype=np.float32), (T + 1, 1, 1)) + poses_abs[:, :3, 3] = fk_pos + poses_abs[:, :3, :3] = fk_rot @ _ROBOMIND_UR_TO_OPENCV + + initial_pose = torch.from_numpy(poses_abs[0].copy()).float() + poses_rel = pose_abs_to_rel(poses_abs, rotation_format="rot6d", pose_convention=self._pose_convention) + + # Raw UR gripper: 0=open, 1=closed → invert so action convention is 0=closed, 1=open. + # joint_configs keeps the raw value; FK mesh uses raw * 255 → Robotiq ctrl. + gripper = torch.from_numpy(1.0 - q[:T, 6:7]).float() + action = torch.cat([torch.from_numpy(poses_rel).float(), gripper], dim=-1) # [T, 10] + + # Mesh animation: frames 1..T of joint position (post-action states) + joint_configs = torch.from_numpy(q[1 : 1 + T].copy()) # [T, 7] + + return action, initial_pose, joint_configs diff --git a/cosmos_framework/data/vfm/action/datasets/umi_lerobot_dataset.py b/cosmos_framework/data/vfm/action/datasets/umi_lerobot_dataset.py index cec95876..59395961 100644 --- a/cosmos_framework/data/vfm/action/datasets/umi_lerobot_dataset.py +++ b/cosmos_framework/data/vfm/action/datasets/umi_lerobot_dataset.py @@ -36,7 +36,7 @@ FORWARD_EEF_IN_CAMERA_FRAME_XYZ_WXYZ: tuple[float, ...] = (0.0, 0.086, 0.056, 1.0, 0.0, 0.0, 0.0) """EEF offset for touch_in_the_wild / FastUMI rigs (camera mounted slightly forward).""" -_NORMALIZER_PATH = Path(__file__).parent / "stats/umi_lerobot_stats.json" +_NORMALIZER_PATH = Path(__file__).parent.parent / "normalizer_stats/umi_lerobot_stats.json" # Action layout: single-arm is the first 10D of the 20D bimanual stats file # (right_eef_poses(9) + right_eef_commands(1)). diff --git a/cosmos_framework/data/vfm/action/datasets/stats/agibotworld_beta_lerobot_stats.json b/cosmos_framework/data/vfm/action/normalizer_stats/agibotworld_beta_lerobot_stats.json similarity index 100% rename from cosmos_framework/data/vfm/action/datasets/stats/agibotworld_beta_lerobot_stats.json rename to cosmos_framework/data/vfm/action/normalizer_stats/agibotworld_beta_lerobot_stats.json diff --git a/cosmos_framework/data/vfm/action/datasets/stats/bridge_orig_lerobot_stats.json b/cosmos_framework/data/vfm/action/normalizer_stats/bridge_orig_lerobot_stats.json similarity index 100% rename from cosmos_framework/data/vfm/action/datasets/stats/bridge_orig_lerobot_stats.json rename to cosmos_framework/data/vfm/action/normalizer_stats/bridge_orig_lerobot_stats.json diff --git a/cosmos_framework/data/vfm/action/datasets/stats/droid_lerobot_stats.json b/cosmos_framework/data/vfm/action/normalizer_stats/droid_lerobot_stats.json similarity index 100% rename from cosmos_framework/data/vfm/action/datasets/stats/droid_lerobot_stats.json rename to cosmos_framework/data/vfm/action/normalizer_stats/droid_lerobot_stats.json diff --git a/cosmos_framework/data/vfm/action/normalizer_stats/fractal_lerobot_stats.json b/cosmos_framework/data/vfm/action/normalizer_stats/fractal_lerobot_stats.json new file mode 100644 index 00000000..1368f2f7 --- /dev/null +++ b/cosmos_framework/data/vfm/action/normalizer_stats/fractal_lerobot_stats.json @@ -0,0 +1,4 @@ +{ + "q01": [-0.039816, -0.049270, -0.056266, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.000000], + "q99": [ 0.043860, 0.050352, 0.072505, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.000000] +} diff --git a/cosmos_framework/data/vfm/action/datasets/stats/robomind_franka_stats.json b/cosmos_framework/data/vfm/action/normalizer_stats/robomind_franka_dual_stats.json similarity index 100% rename from cosmos_framework/data/vfm/action/datasets/stats/robomind_franka_stats.json rename to cosmos_framework/data/vfm/action/normalizer_stats/robomind_franka_dual_stats.json diff --git a/cosmos_framework/data/vfm/action/normalizer_stats/robomind_franka_stats.json b/cosmos_framework/data/vfm/action/normalizer_stats/robomind_franka_stats.json new file mode 100644 index 00000000..0f3329ae --- /dev/null +++ b/cosmos_framework/data/vfm/action/normalizer_stats/robomind_franka_stats.json @@ -0,0 +1,4 @@ +{ + "q01": [-0.065029, -0.030683, -0.075321, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.000000], + "q99": [ 0.068546, 0.036309, 0.051772, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.000000] +} diff --git a/cosmos_framework/data/vfm/action/normalizer_stats/robomind_ur_stats.json b/cosmos_framework/data/vfm/action/normalizer_stats/robomind_ur_stats.json new file mode 100644 index 00000000..a9c60b5c --- /dev/null +++ b/cosmos_framework/data/vfm/action/normalizer_stats/robomind_ur_stats.json @@ -0,0 +1,4 @@ +{ + "q01": [-0.050087, -0.032055, -0.045222, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.000000], + "q99": [ 0.040385, 0.036577, 0.032376, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.000000] +} diff --git a/cosmos_framework/data/vfm/action/datasets/stats/umi_lerobot_stats.json b/cosmos_framework/data/vfm/action/normalizer_stats/umi_lerobot_stats.json similarity index 100% rename from cosmos_framework/data/vfm/action/datasets/stats/umi_lerobot_stats.json rename to cosmos_framework/data/vfm/action/normalizer_stats/umi_lerobot_stats.json diff --git a/cosmos_framework/data/vfm/action/urdf_visualizer/G1_omnipicker_calibrated.urdf b/cosmos_framework/data/vfm/action/robot_assets/G1_omnipicker_calibrated.urdf similarity index 100% rename from cosmos_framework/data/vfm/action/urdf_visualizer/G1_omnipicker_calibrated.urdf rename to cosmos_framework/data/vfm/action/robot_assets/G1_omnipicker_calibrated.urdf diff --git a/cosmos_framework/data/vfm/action/robot_assets/ur5e_robotiq_2f85.xml b/cosmos_framework/data/vfm/action/robot_assets/ur5e_robotiq_2f85.xml new file mode 100644 index 00000000..500e6861 --- /dev/null +++ b/cosmos_framework/data/vfm/action/robot_assets/ur5e_robotiq_2f85.xml @@ -0,0 +1,321 @@ + + + + + From d39ebb8888b9cea48afebf08fadf37fc4881351b Mon Sep 17 00:00:00 2001 From: liangkaiz Date: Wed, 1 Jul 2026 13:45:36 -0700 Subject: [PATCH 2/5] New changes to base dataset --- .../data/vfm/action/datasets/base_dataset.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cosmos_framework/data/vfm/action/datasets/base_dataset.py b/cosmos_framework/data/vfm/action/datasets/base_dataset.py index 2c9c4cb2..d7cdcc23 100644 --- a/cosmos_framework/data/vfm/action/datasets/base_dataset.py +++ b/cosmos_framework/data/vfm/action/datasets/base_dataset.py @@ -12,6 +12,7 @@ from typing import Any import numpy as np +import pandas as pd import pyarrow.parquet as pq import torch from torch.utils.data import Dataset @@ -69,9 +70,13 @@ def __init__( for path in sorted((self._root / "meta" / "episodes").glob("chunk-*/file-*.parquet")) for row in pq.read_table(path).to_pylist() } + tasks_df = pd.read_parquet(self._root / "meta" / "tasks.parquet") + # LeRobot v2.x stores task text in a "task" column; v3.0 stores it as the + # (unnamed) DataFrame index and keeps only "task_index" as a column. + task_texts = tasks_df["task"] if "task" in tasks_df.columns else tasks_df.index self._tasks = { - int(row["task_index"]): str(row["task"]) - for row in pq.read_table(self._root / "meta" / "tasks.parquet").to_pylist() + int(task_index): str(task) + for task, task_index in zip(task_texts, tasks_df["task_index"]) } self._rows = sorted( ( From 242d1fc92ac23af9a85c6f007fe35ef6b8da70a5 Mon Sep 17 00:00:00 2001 From: liangkaiz Date: Wed, 1 Jul 2026 13:55:48 -0700 Subject: [PATCH 3/5] Update stats --- .../vfm/action/normalizer_stats/fractal_lerobot_stats.json | 4 ++-- .../vfm/action/normalizer_stats/robomind_franka_stats.json | 4 ++-- .../data/vfm/action/normalizer_stats/robomind_ur_stats.json | 4 ++-- .../data/vfm/action/normalizer_stats/umi_lerobot_stats.json | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cosmos_framework/data/vfm/action/normalizer_stats/fractal_lerobot_stats.json b/cosmos_framework/data/vfm/action/normalizer_stats/fractal_lerobot_stats.json index 1368f2f7..d54b1720 100644 --- a/cosmos_framework/data/vfm/action/normalizer_stats/fractal_lerobot_stats.json +++ b/cosmos_framework/data/vfm/action/normalizer_stats/fractal_lerobot_stats.json @@ -1,4 +1,4 @@ { - "q01": [-0.039816, -0.049270, -0.056266, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.000000], - "q99": [ 0.043860, 0.050352, 0.072505, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.000000] + "q01": [-0.039816, -0.049270, -0.056266, 0.983667, -0.134543, -0.107048, -0.126518, 0.977277, -0.091363, 0.000000], + "q99": [0.043860, 0.050352, 0.072505, 1.000000, 0.127404, 0.107273, 0.134140, 1.000000, 0.179731, 1.000000] } diff --git a/cosmos_framework/data/vfm/action/normalizer_stats/robomind_franka_stats.json b/cosmos_framework/data/vfm/action/normalizer_stats/robomind_franka_stats.json index 0f3329ae..c2d8432e 100644 --- a/cosmos_framework/data/vfm/action/normalizer_stats/robomind_franka_stats.json +++ b/cosmos_framework/data/vfm/action/normalizer_stats/robomind_franka_stats.json @@ -1,4 +1,4 @@ { - "q01": [-0.065029, -0.030683, -0.075321, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.000000], - "q99": [ 0.068546, 0.036309, 0.051772, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.000000] + "q01": [-0.065029, -0.030683, -0.075321, 0.981664, -0.137429, -0.069593, -0.140220, 0.976885, -0.140399, 0.000000], + "q99": [0.068546, 0.036309, 0.051772, 1.000000, 0.140290, 0.079942, 0.137529, 1.000000, 0.113651, 1.000000] } diff --git a/cosmos_framework/data/vfm/action/normalizer_stats/robomind_ur_stats.json b/cosmos_framework/data/vfm/action/normalizer_stats/robomind_ur_stats.json index a9c60b5c..60964383 100644 --- a/cosmos_framework/data/vfm/action/normalizer_stats/robomind_ur_stats.json +++ b/cosmos_framework/data/vfm/action/normalizer_stats/robomind_ur_stats.json @@ -1,4 +1,4 @@ { - "q01": [-0.050087, -0.032055, -0.045222, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 0.000000], - "q99": [ 0.040385, 0.036577, 0.032376, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.000000] + "q01": [-0.050087, -0.032055, -0.045222, 0.986554, -0.114343, -0.074432, -0.128455, 0.982298, -0.132509, 0.000000], + "q99": [0.040385, 0.036577, 0.032376, 1.000000, 0.129744, 0.095558, 0.115080, 1.000000, 0.121987, 1.000000] } diff --git a/cosmos_framework/data/vfm/action/normalizer_stats/umi_lerobot_stats.json b/cosmos_framework/data/vfm/action/normalizer_stats/umi_lerobot_stats.json index 44b9c8ce..5df9734a 100644 --- a/cosmos_framework/data/vfm/action/normalizer_stats/umi_lerobot_stats.json +++ b/cosmos_framework/data/vfm/action/normalizer_stats/umi_lerobot_stats.json @@ -1,4 +1,4 @@ { - "q01": [-0.035246, -0.037122, -0.035762, 0.984050, -0.108706, -0.065188, -0.110908, 0.982889, -0.085106, 0.000000, -0.027468, -0.036971, -0.029396, 0.993522, -0.076207, -0.061227, -0.074231, 0.992173, -0.076929, 0.000000], - "q99": [ 0.038095, 0.033082, 0.032447, 1.000000, 0.110573, 0.068087, 0.108972, 1.000000, 0.089037, 0.096749, 0.033588, 0.032840, 0.027391, 1.000000, 0.073697, 0.057541, 0.075834, 1.000000, 0.086257, 0.085000] + "q01": [-0.035246, -0.037122, -0.035762, 0.984050, -0.108706, -0.065188, -0.110908, 0.982889, -0.085106, 0.000000, -0.027468, -0.036971, -0.029396, 0.993522, -0.076207, -0.061227, -0.074231, 0.992173, -0.076929, 0.000000], + "q99": [ 0.038095, 0.033082, 0.032447, 1.000000, 0.110573, 0.068087, 0.108972, 1.000000, 0.089037, 0.096749, 0.033588, 0.032840, 0.027391, 1.000000, 0.073697, 0.057541, 0.075834, 1.000000, 0.086257, 0.085000] } From ef776d77dec5eb3b4786dcc0cf10a982d1489f70 Mon Sep 17 00:00:00 2001 From: liangkaiz Date: Wed, 1 Jul 2026 17:02:21 -0700 Subject: [PATCH 4/5] Fix comments --- .../data/vfm/action/datasets/fractal_lerobot_dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cosmos_framework/data/vfm/action/datasets/fractal_lerobot_dataset.py b/cosmos_framework/data/vfm/action/datasets/fractal_lerobot_dataset.py index fe67137b..10628db1 100644 --- a/cosmos_framework/data/vfm/action/datasets/fractal_lerobot_dataset.py +++ b/cosmos_framework/data/vfm/action/datasets/fractal_lerobot_dataset.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: OpenMDW-1.1 -"""Fractal (fractal20220817_data) LeRobot dataset — Google Robot RT-1. +"""Fractal LeRobot dataset — Google Robot RT-1. Robot: google_robot 87,212 episodes, 3,786,400 frames, 599 tasks, fps=3 From b4a1ae39de41944b2ef3d9c9082da630850ec44c Mon Sep 17 00:00:00 2001 From: liangkaiz Date: Wed, 1 Jul 2026 17:18:22 -0700 Subject: [PATCH 5/5] Fix licences --- .../data/vfm/action/robot_assets/ur5e_robotiq_2f85.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cosmos_framework/data/vfm/action/robot_assets/ur5e_robotiq_2f85.xml b/cosmos_framework/data/vfm/action/robot_assets/ur5e_robotiq_2f85.xml index 500e6861..75025458 100644 --- a/cosmos_framework/data/vfm/action/robot_assets/ur5e_robotiq_2f85.xml +++ b/cosmos_framework/data/vfm/action/robot_assets/ur5e_robotiq_2f85.xml @@ -1,3 +1,5 @@ + +