Compare commits

..

2 Commits

Author SHA1 Message Date
Martino Russi 2e37cb22e8 docs(openarm): add episode-replay example
Add examples/openarm with a self-contained script that replays a recorded
bimanual-OpenArm episode into an mp4 by driving the official OpenArm MuJoCo
model (enactic/openarm_mujoco, v1) from a LeRobot dataset's observation.state,
plus a README covering model provenance, setup, and troubleshooting.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 15:42:32 +02:00
Martino Russi ea5cabe6ff feat(openarm): add end-effector kinematics support
Expose optional URDF-based forward/inverse kinematics for the OpenArm
follower so it can be recorded/commanded in end-effector (Cartesian)
space, mirroring the SO-100 kinematics pattern.

- config: add optional urdf_path / target_frame_name fields
- robot: add arm_motor_names property and make_kinematics() helper that
  builds a RobotKinematics solver (lazy import; placo only needed when used)

No behavior change when urdf_path is unset (joint-space only).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 15:24:25 +02:00
24 changed files with 519 additions and 515 deletions
+1 -1
View File
@@ -53,7 +53,7 @@ permissions:
contents: read
env:
UV_VERSION: "0.11.30"
UV_VERSION: "0.8.0"
PYTHON_VERSION: "3.12"
# Cancel in-flight runs for the same branch/PR.
+1 -1
View File
@@ -27,7 +27,7 @@ on:
# Sets up the environment variables
env:
UV_VERSION: "0.11.30"
UV_VERSION: "0.8.0"
PYTHON_VERSION: "3.12"
DOCKER_IMAGE_NAME_CPU: huggingface/lerobot-cpu:latest
DOCKER_IMAGE_NAME_GPU: huggingface/lerobot-gpu:latest
+1 -1
View File
@@ -48,7 +48,7 @@ permissions:
# Sets up the environment variables
env:
UV_VERSION: "0.11.30"
UV_VERSION: "0.8.0"
PYTHON_VERSION: "3.12"
# Ensures that only the latest commit for a PR or branch is built, canceling older runs.
+1 -1
View File
@@ -37,7 +37,7 @@ permissions:
# Sets up the environment variables
env:
UV_VERSION: "0.11.30"
UV_VERSION: "0.8.0"
PYTHON_VERSION: "3.12"
DOCKER_IMAGE_NAME: huggingface/lerobot-gpu
+1 -1
View File
@@ -27,7 +27,7 @@ on:
# Sets up the environment variables
env:
UV_VERSION: "0.11.30"
UV_VERSION: "0.8.0"
PYTHON_VERSION: "3.12"
DOCKER_IMAGE_NAME: huggingface/lerobot-gpu:latest-deps
+1 -1
View File
@@ -21,7 +21,7 @@ on:
# Sets up the environment variables
env:
UV_VERSION: "0.11.30"
UV_VERSION: "0.8.0"
PYTHON_VERSION: "3.12"
jobs:
+5 -5
View File
@@ -19,8 +19,8 @@ on:
workflow_dispatch:
# Runs at 02:00
schedule:
- cron: "0 2 * * *"
# schedule:
# - cron: "0 2 * * *"
env:
CLOSE_ISSUE_MESSAGE: >
@@ -31,7 +31,7 @@ env:
Feel free to reopen if is still relevant, or to ping a collaborator if you have any questions.
WARN_ISSUE_MESSAGE: >
This issue has been automatically marked as stale because it has not had
recent activity (1 year). It will be closed if no further activity occurs within 30 days.
recent activity (1 year). It will be closed if no further activity occurs.
Any change, comment or update to this issue will reset this count.
Thank you for your contributions.
WARN_PR_MESSAGE: >
@@ -61,8 +61,8 @@ jobs:
exempt-pr-labels: never-stale
days-before-issue-stale: 365
days-before-issue-close: 30
days-before-pr-stale: -1
days-before-pr-close: -1
days-before-pr-stale: 365
days-before-pr-close: 30
delete-branch: true
close-issue-message: ${{ env.CLOSE_ISSUE_MESSAGE }}
close-pr-message: ${{ env.CLOSE_PR_MESSAGE }}
+10 -217
View File
@@ -23,18 +23,18 @@ The broader EVO1 project may include additional training scripts and dataset too
2. Install EVO1 dependencies:
```bash
pip install -e ".[training,evo1]"
pip install -e ".[evo1]"
```
For LIBERO training and evaluation, install the LIBERO extra as well:
For LIBERO evaluation, install the LIBERO extra as well:
```bash
pip install -e ".[training,evo1,libero]"
pip install -e ".[evo1,libero]"
```
3. Install a `flash-attn` wheel only if it is compatible with your Python, PyTorch, CUDA, and GPU stack. EVO1 falls back to standard attention when `flash_attn` is not available.
EVO1 uses the native Hugging Face `transformers` InternVL implementation, so `policy.vlm_model_name` must point to a natively converted checkpoint such as `OpenGVLab/InternVL3-1B-hf` (note the `-hf` suffix). The first run downloads the configured VLM checkpoint and later runs reuse it from the Hugging Face cache.
EVO1 uses the native Hugging Face `transformers` InternVL implementation, so `policy.vlm_model_name` must point to a natively converted checkpoint such as `OpenGVLab/InternVL3-1B-hf` (note the `-hf` suffix). The first run may download the configured VLM checkpoint unless `policy.vlm_model_name` points to a local model directory.
## Data Requirements
@@ -92,7 +92,7 @@ lerobot-train \
### Stage 2
Stage 2 loads the Stage 1 policy, but starts a fresh optimizer and scheduler:
Stage 2 finetunes the VLM branches and action head. A common workflow starts from a Stage 1 checkpoint:
```bash
lerobot-train \
@@ -152,154 +152,16 @@ lerobot-rollout \
### LIBERO Evaluation
#### Reference result
> [!NOTE]
> The released Stage-2 checkpoint passed clean-download and rollout verification:
> [`zuoxingdong/evo1_libero`](https://huggingface.co/zuoxingdong/evo1_libero), revision
> [`515921f4a2c1d3f3ad523721eafa26fdf2af315b`](https://huggingface.co/zuoxingdong/evo1_libero/commit/515921f4a2c1d3f3ad523721eafa26fdf2af315b).
> The clean-download evaluation used LeRobot revision
> [`e40b58a8dfa9e7b86918c374791599d070518d11`](https://github.com/huggingface/lerobot/commit/e40b58a8dfa9e7b86918c374791599d070518d11).
> Benchmark results for a `lerobot`-hosted LIBERO checkpoint trained with this implementation
> will be added once training completes.
The single-run Stage-2 checkpoint at step 70,000 produced:
| Suite | Successful episodes | Episodes | Success rate |
| -------------- | ------------------: | --------: | -----------: |
| LIBERO Spatial | 485 | 500 | 97.0% |
| LIBERO Object | 496 | 500 | 99.2% |
| LIBERO Goal | 483 | 500 | 96.6% |
| LIBERO-10 | 469 | 500 | 93.8% |
| **Overall** | **1,933** | **2,000** | **96.65%** |
These results use one trained checkpoint and evaluation seed `1000`; they are not a multi-seed
mean or confidence estimate.
#### Reference training recipe
The released checkpoint records the complete resolved Stage-2 configuration in
[`train_config.json`](https://huggingface.co/zuoxingdong/evo1_libero/blob/515921f4a2c1d3f3ad523721eafa26fdf2af315b/train_config.json).
The measured run used two H100 GPUs with two DDP processes and batch 64 per process, giving global batch 128. Both stages used the same topology. The base VLM came from revision
`014c0583a0d4bedf29fbe2dbff4f865eb998e171` of `OpenGVLab/InternVL3-1B-hf`.
The released artifact does not record the exact LeRobot training commit or its original dependency lock,
so the commands below reproduce the recorded configuration and topology from a current checkout rather
than reconstructing the software environment bit for bit.
From a LeRobot source checkout, install the locked dependencies and download that exact VLM revision:
```bash
uv sync --locked --extra training --extra evo1 --extra libero
VLM_DIR=$(uv run hf download OpenGVLab/InternVL3-1B-hf \
--revision=014c0583a0d4bedf29fbe2dbff4f865eb998e171)
```
Stage 1 freezes the VLM and trains the action head for 5,000 steps:
```bash
uv run accelerate launch --num_processes=2 -m lerobot.scripts.lerobot_train \
--dataset.repo_id=lerobot/libero \
--dataset.revision=a1aaacb7f6cd6ee5fb43120f673cebb0cfea7dd4 \
--dataset.video_backend=torchcodec \
--dataset.return_uint8=true \
--dataset.image_transforms.enable=true \
--dataset.use_imagenet_stats=true \
--dataset.eval_split=0.0 \
--policy.type=evo1 \
--policy.training_stage=stage1 \
--policy.apply_training_stage_defaults=true \
--policy.vlm_model_name="${VLM_DIR}" \
--policy.vlm_num_layers=14 \
--policy.vlm_dtype=bfloat16 \
--policy.device=cuda \
--policy.use_amp=true \
--policy.use_flash_attn=true \
--policy.enable_gradient_checkpointing=true \
--policy.gradient_checkpointing_use_reentrant=false \
--policy.image_resolution='[448,448]' \
--policy.chunk_size=50 \
--policy.n_action_steps=50 \
--policy.max_state_dim=24 \
--policy.max_action_dim=24 \
--policy.dropout=0.2 \
--policy.optimizer_lr=1e-5 \
--policy.optimizer_weight_decay=1e-3 \
--policy.optimizer_grad_clip_norm=1.0 \
--policy.scheduler_warmup_steps=1000 \
--policy.push_to_hub=false \
--use_policy_training_preset=true \
--batch_size=64 \
--steps=5000 \
--save_checkpoint=true \
--save_checkpoint_to_hub=false \
--save_freq=2500 \
--log_freq=10 \
--env_eval_freq=0 \
--num_workers=4 \
--prefetch_factor=2 \
--persistent_workers=true \
--seed=1000 \
--wandb.enable=false \
--output_dir=./outputs/evo1-libero-stage1-g128-5k
```
Stage 2 loads the Stage-1 policy but starts a fresh optimizer and scheduler. It trains for 80,000 steps;
the reported checkpoint is the save at step 70,000:
```bash
uv run accelerate launch --num_processes=2 -m lerobot.scripts.lerobot_train \
--dataset.repo_id=lerobot/libero \
--dataset.revision=a1aaacb7f6cd6ee5fb43120f673cebb0cfea7dd4 \
--dataset.video_backend=torchcodec \
--dataset.return_uint8=true \
--dataset.image_transforms.enable=true \
--dataset.use_imagenet_stats=true \
--dataset.eval_split=0.0 \
--policy.path=./outputs/evo1-libero-stage1-g128-5k/checkpoints/005000/pretrained_model \
--policy.training_stage=stage2 \
--policy.apply_training_stage_defaults=true \
--policy.vlm_model_name="${VLM_DIR}" \
--policy.vlm_num_layers=14 \
--policy.vlm_dtype=float32 \
--policy.device=cuda \
--policy.use_amp=true \
--policy.use_flash_attn=true \
--policy.enable_gradient_checkpointing=true \
--policy.gradient_checkpointing_use_reentrant=false \
--policy.image_resolution='[448,448]' \
--policy.chunk_size=50 \
--policy.n_action_steps=50 \
--policy.max_state_dim=24 \
--policy.max_action_dim=24 \
--policy.dropout=0.2 \
--policy.optimizer_lr=1e-5 \
--policy.optimizer_weight_decay=1e-3 \
--policy.optimizer_grad_clip_norm=1.0 \
--policy.scheduler_warmup_steps=1000 \
--policy.push_to_hub=false \
--use_policy_training_preset=true \
--batch_size=64 \
--steps=80000 \
--resume=false \
--save_checkpoint=true \
--save_checkpoint_to_hub=false \
--save_freq=10000 \
--log_freq=10 \
--env_eval_freq=0 \
--num_workers=4 \
--prefetch_factor=2 \
--persistent_workers=true \
--seed=1000 \
--wandb.enable=false \
--output_dir=./outputs/evo1-libero-stage2-g128-80k
```
#### Author-format evaluation profile
The author-format EVO1 LIBERO profile uses the raw LIBERO camera feature names
The official EVO1 LIBERO rollout protocol uses the raw LIBERO camera feature names
(`observation.images.agentview_image` and `observation.images.robot0_eye_in_hand_image`), replans every
14 actions, and binarizes the gripper command before stepping the simulator. The EVO1 policy postprocessor
can crop the padded 24D action back to the 7D LIBERO action space and apply that gripper binarization. To
evaluate an author-format checkpoint under the same one-episode-per-task setting, keep the raw camera names
instead of the default `image`/`image2` mapping and set the LIBERO action postprocessing flags:
evaluate a LIBERO checkpoint under the same one-episode-per-task setting, keep the raw camera names instead
of the default `image`/`image2` mapping and set the LIBERO action postprocessing flags:
```bash
lerobot-eval \
@@ -319,75 +181,6 @@ lerobot-eval \
--eval.n_episodes=1
```
#### Native `lerobot/libero` v3 profile
Revision `a1aaacb7f6cd6ee5fb43120f673cebb0cfea7dd4` stores camera features as `image` and
`image2`. This example evaluates all ten LIBERO Object tasks, launching each task in a fresh process:
```bash
export MUJOCO_GL=egl
export PYOPENGL_PLATFORM=egl
suite=libero_object
horizon=280
for task_id in {0..9}; do
lerobot-eval \
--policy.path=zuoxingdong/evo1_libero \
--policy.pretrained_revision=515921f4a2c1d3f3ad523721eafa26fdf2af315b \
--policy.vlm_model_name=OpenGVLab/InternVL3-1B-hf \
--policy.device=cuda \
--policy.use_amp=true \
--policy.vlm_dtype=bfloat16 \
--policy.use_flash_attn=false \
--policy.enable_gradient_checkpointing=false \
--policy.vlm_num_layers=14 \
--policy.image_resolution='[448,448]' \
--policy.max_text_length=1024 \
--policy.chunk_size=50 \
--policy.n_action_steps=14 \
--policy.max_state_dim=24 \
--policy.max_action_dim=24 \
--policy.num_inference_timesteps=32 \
--policy.postprocess_action_dim=7 \
--policy.binarize_gripper=true \
--policy.gripper_threshold=0.0 \
--policy.gripper_below_threshold_value=-1.0 \
--policy.gripper_above_threshold_value=1.0 \
--env.type=libero \
--env.task="${suite}" \
--env.task_ids="[${task_id}]" \
--env.camera_name=agentview_image,robot0_eye_in_hand_image \
--env.camera_name_mapping="{agentview_image: image, robot0_eye_in_hand_image: image2}" \
--env.control_mode=relative \
--env.obs_type=pixels_agent_pos \
--env.observation_width=448 \
--env.observation_height=448 \
--env.init_states=true \
--env.episode_length="${horizon}" \
--env.render_mode=rgb_array \
--env.max_parallel_tasks=1 \
--eval.n_episodes=50 \
--eval.batch_size=1 \
--eval.use_async_envs=false \
--eval.recording=false \
--seed=1000 \
--output_dir="./outputs/evo1-libero-stage2-70k-eval/${suite}/task-${task_id}" \
--job_name="evo1-libero-stage2-70k-${suite}-task-${task_id}"
done
```
Run all ten task IDs for each suite with these horizons:
| `env.task` | `env.episode_length` |
| ---------------- | -------------------: |
| `libero_spatial` | `280` |
| `libero_object` | `280` |
| `libero_goal` | `300` |
| `libero_10` | `520` |
Set `suite` and `horizon` for each row. This gives 500 episodes per suite and 2,000 episodes overall, while
the loop's fresh process per task matches the measured RNG-reset topology.
## References
- [EVO1 repository](https://github.com/MINT-SJTU/Evo-1)
-14
View File
@@ -92,20 +92,6 @@ LIBERO supports two control modes — `relative` (default) and `absolute`. Diffe
--env.control_mode=relative # or "absolute"
```
### Reset performance
By default, LeRobot preserves LIBERO's hard-reset behavior. With fixed initial
states enabled, you can opt into soft resets to skip rebuilding the simulator
model and renderer on every episode:
```bash
--env.init_states=true --env.hard_reset=false
```
Soft resets are faster but are not bit-identical to hard resets after the
environment's settling steps, so camera observations and policy results may
differ slightly. Use hard resets when reproducing benchmark results.
### Policy inputs and outputs
**Observations:**
-14
View File
@@ -134,20 +134,6 @@ LIBERO-plus supports two control modes — `relative` (default) and `absolute`.
--env.control_mode=relative # or "absolute"
```
### Reset performance
By default, LeRobot preserves LIBERO's hard-reset behavior. With fixed initial
states enabled, you can opt into soft resets to skip rebuilding the simulator
model and renderer on every episode:
```bash
--env.init_states=true --env.hard_reset=false
```
Soft resets are faster but are not bit-identical to hard resets after the
environment's settling steps, so camera observations and policy results may
differ slightly. Use hard resets when reproducing benchmark results.
### Policy inputs and outputs
**Observations:**
+75
View File
@@ -0,0 +1,75 @@
# OpenArm — Episode Replay in Simulation
Replay a recorded bimanual-[OpenArm](https://openarm.dev) episode into an mp4 by driving the
official OpenArm MuJoCo model directly from a LeRobot dataset's recorded joint states. This is
a visual sanity check for recorded/commanded trajectories and for the end-effector kinematics
exposed by `OpenArmFollower.make_kinematics()` (see the [OpenArm docs](../../docs/source/openarm.mdx)).
## Model provenance
Everything is pulled from Enactic's official, Apache-2.0 OpenArm repositories — nothing is
vendored into LeRobot:
| Asset | Source | License |
| ------------------------------------ | ------------------------------------------------------------------------------- | ---------- |
| MuJoCo MJCF (used here) | [`enactic/openarm_mujoco`](https://github.com/enactic/openarm_mujoco) | Apache-2.0 |
| URDF / xacro (for `RobotKinematics`) | [`enactic/openarm_description`](https://github.com/enactic/openarm_description) | Apache-2.0 |
Use the **v1** MuJoCo revision (`v1/openarm_bimanual.xml`). v2 is a different wrist hardware
revision (DM3507) and will look sign-flipped when replaying v1 recordings.
## Setup
```bash
# LeRobot in your env (see https://huggingface.co/docs/lerobot/installation)
# Plus the sim/replay deps:
pip install mujoco av pandas
# Get the OpenArm MuJoCo model (either works):
pip install openarm-mujoco # installs models under <prefix>/share/openarm_mujoco/
# or
git clone https://github.com/enactic/openarm_mujoco.git # then pass --mjcf .../v1/openarm_bimanual.xml
```
The script auto-locates the model in this order: `--mjcf` arg → `$OPENARM_MJCF`
`<sys.prefix>/share/openarm_mujoco/v1/openarm_bimanual.xml`.
## Dataset layout
`observation.state` must be the 16-D bimanual vector (degrees):
```
right_joint_1..7, right_gripper, left_joint_1..7, left_gripper
```
Only the 14 arm joints affect the rendered pose; the two gripper scalars drive the fingers.
## Run
Headless rendering needs `MUJOCO_GL=egl`, and MuJoCo's GL libs on `LD_LIBRARY_PATH`
(in conda: `$CONDA_PREFIX/lib`).
```bash
# Replay episode 1 of a local LeRobot v3.0 dataset
LD_LIBRARY_PATH=$CONDA_PREFIX/lib MUJOCO_GL=egl \
python -m examples.openarm.render_episode \
--dataset data/folding_src_meta \
--episode 1 \
--out openarm_ep1.mp4
# No dataset handy? Smoke-test with a synthetic wave:
LD_LIBRARY_PATH=$CONDA_PREFIX/lib MUJOCO_GL=egl \
python -m examples.openarm.render_episode --demo --out openarm_demo.mp4
```
Useful flags: `--fps` (default 30), `--width` / `--height` (default 960×720), `--mjcf` to point
at an explicit model file.
## Troubleshooting
| Symptom | Fix |
| ------------------------------------- | ---------------------------------------------------------------------- |
| `Could not find the OpenArm v1 MJCF` | Pass `--mjcf`, set `$OPENARM_MJCF`, or install/clone `openarm_mujoco`. |
| `libEGL`/`GLEW` / blank window errors | Ensure `MUJOCO_GL=egl` and `LD_LIBRARY_PATH=$CONDA_PREFIX/lib`. |
| Wrists look mirrored / flipped | You are on the v2 model; switch to **v1**. |
| `KeyError: 'observation.state'` | Dataset isn't in the expected 16-D bimanual layout. |
+217
View File
@@ -0,0 +1,217 @@
#!/usr/bin/env python
# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Replay a recorded bimanual-OpenArm episode into an mp4, in simulation.
This drives the official OpenArm MuJoCo model (``enactic/openarm_mujoco``, v1) directly
from a LeRobot dataset's recorded ``observation.state`` and renders a headless video. It is
a visual sanity check that a recorded/commanded joint trajectory is what you think it is --
the same forward-kinematics path used to expose end-effector poses (see the OpenArm
end-effector kinematics helper ``OpenArmFollower.make_kinematics``).
The ``observation.state`` is expected in the 16-D bimanual layout (degrees):
right_joint_1..7, right_gripper, left_joint_1..7, left_gripper
Model (Apache-2.0): https://github.com/enactic/openarm_mujoco (use the **v1** revision;
v2 is a different wrist hardware revision and will look sign-flipped on v1 recordings).
Examples:
# replay episode 1 of a local LeRobot v3.0 dataset
LD_LIBRARY_PATH=$CONDA_PREFIX/lib MUJOCO_GL=egl python -m examples.openarm.render_episode \
--dataset data/folding_src_meta --episode 1 --out openarm_ep1.mp4
# smoke test with a synthetic wave (no dataset / model joints only)
LD_LIBRARY_PATH=$CONDA_PREFIX/lib MUJOCO_GL=egl python -m examples.openarm.render_episode \
--demo --out openarm_demo.mp4
"""
from __future__ import annotations
import argparse
import os
import sys
from pathlib import Path
os.environ.setdefault("MUJOCO_GL", "egl") # headless GPU rendering
import numpy as np
# Policy/dataset state layout: 16-D, degrees.
POLICY_ORDER = [
*(f"right_joint_{i}" for i in range(1, 8)),
"right_gripper",
*(f"left_joint_{i}" for i in range(1, 8)),
"left_gripper",
]
# MuJoCo joint name for each of the 14 arm entries (grippers handled separately).
ARM_MAP = {
**{f"right_joint_{i}": f"openarm_right_joint{i}" for i in range(1, 8)},
**{f"left_joint_{i}": f"openarm_left_joint{i}" for i in range(1, 8)},
}
RIGHT_GRIPPER_IDX, LEFT_GRIPPER_IDX = 7, 15
GRIP_FULL_DEG = 65.0 # follower gripper limit magnitude -> fully open
def locate_mjcf(explicit: str | None) -> str:
"""Resolve the OpenArm v1 bimanual MJCF path.
Priority: --mjcf arg, then $OPENARM_MJCF, then the file installed by the
``openarm_mujoco`` pip package under ``<prefix>/share/openarm_mujoco/v1``.
"""
if explicit:
return explicit
if os.environ.get("OPENARM_MJCF"):
return os.environ["OPENARM_MJCF"]
for prefix in (sys.prefix, os.environ.get("CONDA_PREFIX", "")):
if not prefix:
continue
cand = Path(prefix) / "share" / "openarm_mujoco" / "v1" / "openarm_bimanual.xml"
if cand.exists():
return str(cand)
raise SystemExit(
"Could not find the OpenArm v1 MJCF. Pass --mjcf /path/to/v1/openarm_bimanual.xml, "
"set $OPENARM_MJCF, or clone https://github.com/enactic/openarm_mujoco."
)
def load_state_from_dataset(root: str, ep: int) -> np.ndarray:
"""Read one episode's recorded observation.state (N, 16; degrees) from a LeRobot v3.0 root."""
import pandas as pd
root = Path(root)
ep_meta = pd.read_parquet(root / "meta" / "episodes" / "chunk-000" / "file-000.parquet")
row = ep_meta[ep_meta["episode_index"] == ep].iloc[0]
a, b = int(row["dataset_from_index"]), int(row["dataset_to_index"])
dchunk, dfile = int(row["data/chunk_index"]), int(row["data/file_index"])
df = pd.read_parquet(root / "data" / f"chunk-{dchunk:03d}" / f"file-{dfile:03d}.parquet")
df = df[(df["index"] >= a) & (df["index"] < b)].sort_values("frame_index")
return np.stack(df["observation.state"].to_numpy()).astype(np.float32)
def encode_mp4(frames: list[np.ndarray], path: str, fps: int) -> None:
import av
h, w = frames[0].shape[:2]
container = av.open(path, mode="w")
stream = container.add_stream("libx264", rate=fps)
stream.width, stream.height, stream.pix_fmt = w, h, "yuv420p"
for f in frames:
frame = av.VideoFrame.from_ndarray(np.ascontiguousarray(f), format="rgb24")
for pkt in stream.encode(frame):
container.mux(pkt)
for pkt in stream.encode():
container.mux(pkt)
container.close()
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
ap.add_argument("--dataset", default=None, help="LeRobot v3.0 dataset root (meta/ + data/)")
ap.add_argument("--episode", type=int, default=0)
ap.add_argument("--demo", action="store_true", help="drive a synthetic wave (no dataset)")
ap.add_argument("--mjcf", default=None, help="path to v1/openarm_bimanual.xml (see locate_mjcf)")
ap.add_argument("--out", default="openarm_episode.mp4")
ap.add_argument("--fps", type=int, default=30)
ap.add_argument("--width", type=int, default=960)
ap.add_argument("--height", type=int, default=720)
args = ap.parse_args()
import mujoco
model = mujoco.MjModel.from_xml_path(locate_mjcf(args.mjcf))
model.vis.global_.offwidth = max(model.vis.global_.offwidth, args.width)
model.vis.global_.offheight = max(model.vis.global_.offheight, args.height)
data = mujoco.MjData(model)
qadr = {
pk: int(model.jnt_qposadr[mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_JOINT, mj)])
for pk, mj in ARM_MAP.items()
}
def finger_adr(names):
out = []
for nm in names:
jid = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_JOINT, nm)
out.append((int(model.jnt_qposadr[jid]), model.jnt_range[jid].copy(), int(model.jnt_type[jid])))
return out
right_fingers = finger_adr(["openarm_right_finger_joint1", "openarm_right_finger_joint2"])
left_fingers = finger_adr(["openarm_left_finger_joint1", "openarm_left_finger_joint2"])
hinge_type = int(mujoco.mjtJoint.mjJNT_HINGE)
def finger_target(gripper_deg, rng, jtype):
opening = min(1.0, abs(gripper_deg) / GRIP_FULL_DEG) # 0=closed .. 1=open
if jtype == hinge_type: # hinge in radians; sign encodes side via range direction
lo, hi = rng
mag = np.deg2rad(min(abs(gripper_deg), GRIP_FULL_DEG))
return np.clip(-mag if lo < 0 else mag, lo, hi)
return rng[0] + opening * (rng[1] - rng[0]) # slide (v1): lo=closed .. hi=open
if args.demo:
n_frames = 120
traj = np.zeros((n_frames, 16), np.float32)
wave = 40.0 * np.sin(np.linspace(0, 2 * np.pi, n_frames))
for i, pk in enumerate(POLICY_ORDER):
if pk in qadr:
traj[:, i] = wave * (0.5 + 0.5 * (i % 3))
elif args.dataset:
traj = load_state_from_dataset(args.dataset, args.episode)
else:
raise SystemExit("provide --dataset <root> (with --episode) or --demo")
n_frames = traj.shape[0]
print(f"driving {n_frames} frames")
# Auto-frame the arms (exclude pedestal/world) from body positions at the mid pose.
mid = n_frames // 2
for i, pk in enumerate(POLICY_ORDER):
if pk in qadr:
data.qpos[qadr[pk]] = np.deg2rad(traj[mid, i])
mujoco.mj_forward(model, data)
arm_pts = [
data.xpos[b].copy()
for b in range(model.nbody)
if (mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_BODY, b) or "").startswith("openarm")
and "base" not in (mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_BODY, b) or "")
]
arm_pts = np.array(arm_pts) if arm_pts else data.xpos[1:]
lo, hi = arm_pts.min(0), arm_pts.max(0)
cam = mujoco.MjvCamera()
mujoco.mjv_defaultCamera(cam)
cam.azimuth, cam.elevation = 150.0, -20.0
cam.distance = max(0.8, float(np.linalg.norm(hi - lo)) * 1.3)
cam.lookat[:] = (lo + hi) / 2.0
renderer = mujoco.Renderer(model, height=args.height, width=args.width)
frames = []
for t in range(n_frames):
for i, pk in enumerate(POLICY_ORDER):
if pk in qadr:
data.qpos[qadr[pk]] = np.deg2rad(traj[t, i])
for adr, rng, jt in right_fingers:
data.qpos[adr] = finger_target(float(traj[t, RIGHT_GRIPPER_IDX]), rng, jt)
for adr, rng, jt in left_fingers:
data.qpos[adr] = finger_target(float(traj[t, LEFT_GRIPPER_IDX]), rng, jt)
mujoco.mj_forward(model, data)
renderer.update_scene(data, camera=cam)
frames.append(renderer.render())
renderer.close()
encode_mp4(frames, args.out, args.fps)
print(f"wrote {args.out} ({n_frames} frames @ {args.fps} fps, {args.width}x{args.height})")
if __name__ == "__main__":
main()
+2 -2
View File
@@ -87,7 +87,7 @@ dependencies = [
# Build tools (required by opencv-python-headless on some platforms)
"cmake>=3.29.0.1,<4.2.0",
"setuptools>=71.0.0,<82.0.0", # torch 2.11 requires setuptools<82; a higher cap makes the resolver downgrade torch
"setuptools>=71.0.0,<81.0.0",
]
# Optional dependencies
@@ -261,7 +261,7 @@ annotations = [
# Development
dev = ["pre-commit>=3.7.0,<5.0.0", "debugpy>=1.8.1,<1.9.0", "lerobot[grpcio-dep]", "grpcio-tools>=1.73.1,<2.0.0", "mypy>=1.19.1", "ruff>=0.14.1", "lerobot[notebook]"]
notebook = ["jupyter>=1.0.0,<2.0.0", "ipykernel>=6.0.0,<7.0.0"]
test = ["pytest>=8.1.0,<10.0.0", "pytest-timeout>=2.4.0,<3.0.0", "pytest-cov>=5.0.0,<8.0.0", "mock-serial>=0.0.1,<0.1.0 ; sys_platform != 'win32'"]
test = ["pytest>=8.1.0,<9.0.0", "pytest-timeout>=2.4.0,<3.0.0", "pytest-cov>=5.0.0,<8.0.0", "mock-serial>=0.0.1,<0.1.0 ; sys_platform != 'win32'"]
video_benchmark = ["scikit-image>=0.23.2,<0.26.0", "pandas>=2.2.2,<2.4.0"]
# Simulation
-1
View File
@@ -102,7 +102,6 @@ class ImageServer:
fps=self.fps,
width=shape[1],
height=shape[0],
fourcc=cfg.get("fourcc", "MJPG"),
color_mode=ColorMode.RGB,
)
camera = OpenCVCamera(cam_config)
-4
View File
@@ -328,7 +328,6 @@ class LiberoEnv(EnvConfig):
render_mode: str = "rgb_array"
camera_name: str = "agentview_image,robot0_eye_in_hand_image"
init_states: bool = True
hard_reset: bool = True
camera_name_mapping: dict[str, str] | None = None
observation_height: int = 360
observation_width: int = 360
@@ -357,8 +356,6 @@ class LiberoEnv(EnvConfig):
def __post_init__(self):
if self.fps <= 0:
raise ValueError(f"fps must be positive, got {self.fps}")
if not self.hard_reset and not self.init_states:
raise ValueError("hard_reset=False requires init_states=True")
if self.obs_type == "pixels":
self.features[LIBERO_KEY_PIXELS_AGENTVIEW] = PolicyFeature(
@@ -419,7 +416,6 @@ class LiberoEnv(EnvConfig):
"observation_height": self.observation_height,
"observation_width": self.observation_width,
"control_freq": self.fps,
"hard_reset": self.hard_reset,
}
if self.task_ids is not None:
kwargs["task_ids"] = self.task_ids
+2 -15
View File
@@ -128,13 +128,10 @@ class LiberoEnv(gym.Env):
control_freq: int = 20,
control_mode: str = "relative",
is_libero_plus: bool = False,
hard_reset: bool = True,
):
super().__init__()
if control_freq <= 0:
raise ValueError(f"control_freq must be positive, got {control_freq}")
if not hard_reset and not init_states:
raise ValueError("hard_reset=False requires init_states=True")
self.task_id = task_id
self.is_libero_plus = is_libero_plus
self.obs_type = obs_type
@@ -161,7 +158,6 @@ class LiberoEnv(gym.Env):
self.camera_name_mapping = camera_name_mapping
self.num_steps_wait = num_steps_wait
self.control_freq = control_freq
self.hard_reset = hard_reset
self.episode_index = episode_index
self.episode_length = episode_length
# Load once and keep
@@ -269,9 +265,6 @@ class LiberoEnv(gym.Env):
camera_heights=self.observation_height,
camera_widths=self.observation_width,
control_freq=self.control_freq,
# Soft resets skip LIBERO's model and renderer rebuild. They are opt-in
# because settle steps can make their observations differ from hard resets.
hard_reset=self.hard_reset,
)
env.reset()
self._env = env
@@ -384,9 +377,8 @@ class LiberoEnv(gym.Env):
}
)
observation = self._format_raw_obs(raw_obs)
# Return the terminal observation unchanged. The caller owns resetting after
# termination; vector envs created below use NEXT_STEP autoreset. Resetting here
# would therefore reset twice and skip an initial state.
if terminated:
self.reset()
truncated = False
return observation, reward, terminated, truncated, info
@@ -484,7 +476,6 @@ def create_libero_envs(
print(f"Restricting to task_ids={task_ids_filter}")
is_async = env_cls is gym.vector.AsyncVectorEnv
is_sync = env_cls is gym.vector.SyncVectorEnv
out: dict[str, dict[int, Any]] = defaultdict(dict)
for suite_name in suite_names:
@@ -521,10 +512,6 @@ def create_libero_envs(
cached_act_space = lazy.action_space
cached_metadata = lazy.metadata
out[suite_name][tid] = lazy
elif is_sync:
out[suite_name][tid] = gym.vector.SyncVectorEnv(
fns, autoreset_mode=gym.vector.AutoresetMode.NEXT_STEP
)
else:
out[suite_name][tid] = env_cls(fns)
print(f"Built vec env | suite={suite_name} | task_id={tid} | n_envs={n_envs}")
+1 -6
View File
@@ -212,12 +212,7 @@ class _LazyAsyncVectorEnv:
def _ensure(self) -> None:
if self._env is None:
self._env = gym.vector.AsyncVectorEnv(
self._env_fns,
context="forkserver",
shared_memory=True,
autoreset_mode=gym.vector.AutoresetMode.NEXT_STEP,
)
self._env = gym.vector.AsyncVectorEnv(self._env_fns, context="forkserver", shared_memory=True)
@property
def unwrapped(self):
@@ -74,6 +74,14 @@ class OpenArmFollowerConfigBase:
# Set to a positive scalar for all motors, or a dict mapping motor names to limits
max_relative_target: float | dict[str, float] | None = None
# End-effector kinematics (optional). Point `urdf_path` at the OpenArm URDF and set
# `target_frame_name` to the end-effector link in that URDF to enable forward/inverse
# kinematics in Cartesian (EE) space -- via `lerobot.model.RobotKinematics` and the
# shared FK/IK processor steps. Left as None, the robot behaves exactly as before
# (joint space only). The URDF is user-supplied, so no large asset is vendored here.
urdf_path: str | None = None
target_frame_name: str | None = None
# Camera configurations
cameras: dict[str, CameraConfig] = field(default_factory=dict)
@@ -17,7 +17,7 @@
import logging
import time
from functools import cached_property
from typing import Any
from typing import TYPE_CHECKING, Any
from lerobot.cameras import make_cameras_from_configs
from lerobot.lerobot_types import RobotAction, RobotObservation
@@ -25,6 +25,9 @@ from lerobot.motors import Motor, MotorCalibration, MotorNormMode
from lerobot.motors.damiao import DamiaoMotorsBus
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
if TYPE_CHECKING:
from lerobot.model import RobotKinematics
from ..robot import Robot
from ..utils import ensure_safe_goal_position
from .config_openarm_follower import (
@@ -120,6 +123,33 @@ class OpenArmFollower(Robot):
"""Action features."""
return self._motors_ft
@property
def arm_motor_names(self) -> list[str]:
"""Arm joints forming the kinematic chain to the end-effector (excludes the gripper)."""
return [motor for motor in self.bus.motors if motor != "gripper"]
def make_kinematics(self) -> "RobotKinematics":
"""Build a solver for end-effector forward/inverse kinematics.
Requires ``config.urdf_path`` (path to the OpenArm URDF) and
``config.target_frame_name`` (the end-effector link in that URDF). Pair the returned
solver with the shared FK/IK processor steps in
``lerobot.robots.so_follower.robot_kinematic_processor`` to record or command the arm
in end-effector (Cartesian) space instead of raw joint angles.
"""
from lerobot.model import RobotKinematics
if self.config.urdf_path is None or self.config.target_frame_name is None:
raise ValueError(
"OpenArm end-effector kinematics require config.urdf_path and "
"config.target_frame_name (the OpenArm URDF and its end-effector link)."
)
return RobotKinematics(
urdf_path=self.config.urdf_path,
target_frame_name=self.config.target_frame_name,
joint_names=self.arm_motor_names,
)
@property
def is_connected(self) -> bool:
"""Check if robot is connected."""
@@ -77,13 +77,11 @@ class EEReferenceAndDelta(RobotActionProcessorStep):
_command_when_disabled: np.ndarray | None = field(default=None, init=False, repr=False)
def action(self, action: RobotAction) -> RobotAction:
raw_observation = self.transition.get(TransitionKey.OBSERVATION)
observation = self.transition.get(TransitionKey.OBSERVATION).copy()
if raw_observation is None:
if observation is None:
raise ValueError("Joints observation is require for computing robot kinematics")
observation = raw_observation.copy()
if self.use_ik_solution and "IK_solution" in self.transition.get(TransitionKey.COMPLEMENTARY_DATA):
q_raw = self.transition.get(TransitionKey.COMPLEMENTARY_DATA)["IK_solution"]
else:
@@ -313,12 +311,10 @@ class InverseKinematicsEEToJoints(RobotActionProcessorStep):
"Missing required end-effector pose components: ee.x, ee.y, ee.z, ee.wx, ee.wy, ee.wz, ee.gripper_pos must all be present in action"
)
raw_observation = self.transition.get(TransitionKey.OBSERVATION)
if raw_observation is None:
observation = self.transition.get(TransitionKey.OBSERVATION).copy()
if observation is None:
raise ValueError("Joints observation is require for computing robot kinematics")
observation = raw_observation.copy()
q_raw = np.array(
[float(v) for k, v in observation.items() if isinstance(k, str) and k.endswith(".pos")],
dtype=float,
@@ -395,15 +391,13 @@ class GripperVelocityToJoint(RobotActionProcessorStep):
discrete_gripper: bool = False
def action(self, action: RobotAction) -> RobotAction:
raw_observation = self.transition.get(TransitionKey.OBSERVATION)
observation = self.transition.get(TransitionKey.OBSERVATION).copy()
gripper_vel = action.pop("ee.gripper_vel")
if raw_observation is None:
if observation is None:
raise ValueError("Joints observation is require for computing robot kinematics")
observation = raw_observation.copy()
q_raw = np.array(
[float(v) for k, v in observation.items() if isinstance(k, str) and k.endswith(".pos")],
dtype=float,
@@ -589,12 +583,10 @@ class InverseKinematicsRLStep(ProcessorStep):
"Missing required end-effector pose components: ee.x, ee.y, ee.z, ee.wx, ee.wy, ee.wz, ee.gripper_pos must all be present in action"
)
raw_observation = new_transition.get(TransitionKey.OBSERVATION)
if raw_observation is None:
observation = new_transition.get(TransitionKey.OBSERVATION).copy()
if observation is None:
raise ValueError("Joints observation is require for computing robot kinematics")
observation = raw_observation.copy()
q_raw = np.array(
[float(v) for k, v in observation.items() if isinstance(k, str) and k.endswith(".pos")],
dtype=float,
+14 -26
View File
@@ -240,7 +240,6 @@ Using JSON config file:
import abc
import logging
import os
import shutil
import sys
from dataclasses import dataclass, field
@@ -385,35 +384,24 @@ def _resolve_io_paths(
return output_repo_id, input_path, output_path
def _is_in_place(input_path: Path, output_path: Path) -> bool:
"""Whether both paths point to the same dataset directory.
Uses os.path.samefile (device+inode) which is robust to case-insensitive filesystems, hardlinks
and symlinks.
"""
try:
return os.path.samefile(input_path, output_path)
except OSError:
return False
def get_output_path(
repo_id: str,
new_repo_id: str | None,
root: Path | str | None,
new_root: Path | str | None,
) -> tuple[str, Path, Path | None]:
) -> tuple[str, Path]:
output_repo_id, input_path, output_path = _resolve_io_paths(repo_id, new_repo_id, root, new_root)
# In case of in-place modification, create a backup of the original dataset (if it exists).
backup_path: Path | None = None
if _is_in_place(input_path, output_path):
# In case of in-place modification, create a backup of the original dataset (if it exists)
if output_path == input_path:
backup_path = input_path.with_name(input_path.name + "_old")
if input_path.exists():
if backup_path.exists():
shutil.rmtree(backup_path)
shutil.move(input_path, backup_path)
return output_repo_id, output_path, backup_path
return output_repo_id, output_path
def handle_delete_episodes(cfg: EditDatasetConfig) -> None:
@@ -424,7 +412,7 @@ def handle_delete_episodes(cfg: EditDatasetConfig) -> None:
raise ValueError("episode_indices must be specified for delete_episodes operation")
dataset = LeRobotDataset(cfg.repo_id, root=cfg.root)
output_repo_id, output_dir, backup_path = get_output_path(
output_repo_id, output_dir = get_output_path(
cfg.repo_id,
new_repo_id=cfg.new_repo_id,
root=cfg.root,
@@ -432,8 +420,8 @@ def handle_delete_episodes(cfg: EditDatasetConfig) -> None:
)
# In case of in-place modification, make the dataset point to the backup directory
if backup_path is not None:
dataset.root = backup_path
if output_dir == dataset.root:
dataset.root = dataset.root.with_name(dataset.root.name + "_old")
logging.info(f"Deleting episodes {cfg.operation.episode_indices} from {cfg.repo_id}")
new_dataset = delete_episodes(
@@ -537,7 +525,7 @@ def handle_remove_feature(cfg: EditDatasetConfig) -> None:
raise ValueError("feature_names must be specified for remove_feature operation")
dataset = LeRobotDataset(cfg.repo_id, root=cfg.root)
output_repo_id, output_dir, backup_path = get_output_path(
output_repo_id, output_dir = get_output_path(
cfg.repo_id,
new_repo_id=cfg.new_repo_id,
root=cfg.root,
@@ -545,8 +533,8 @@ def handle_remove_feature(cfg: EditDatasetConfig) -> None:
)
# In case of in-place modification, make the dataset point to the backup directory
if backup_path is not None:
dataset.root = backup_path
if output_dir == dataset.root:
dataset.root = dataset.root.with_name(dataset.root.name + "_old")
logging.info(f"Removing features {cfg.operation.feature_names} from {cfg.repo_id}")
new_dataset = remove_feature(
@@ -683,7 +671,7 @@ def handle_recompute_stats(cfg: EditDatasetConfig) -> None:
cfg.new_root,
default_new_repo_id=f"{cfg.repo_id}_recomputed_stats",
)
in_place = _is_in_place(input_root, output_root)
in_place = output_root == input_root
if in_place and not cfg.operation.overwrite:
raise ValueError(
@@ -743,7 +731,7 @@ def handle_reencode_videos(cfg: EditDatasetConfig) -> None:
cfg.new_root,
default_new_repo_id=f"{cfg.repo_id}_reencoded",
)
in_place = _is_in_place(input_root, output_root)
in_place = output_root == input_root
if in_place and not cfg.operation.overwrite:
raise ValueError(
+1 -9
View File
@@ -243,17 +243,9 @@ def train(cfg: TrainPipelineConfig, accelerator: "Accelerator | None" = None):
# Accelerate auto-detects the device based on the available hardware and ignores the policy.device setting.
# Force the device to be CPU when the active config's device is set to CPU (works for both policy and reward model training).
force_cpu = cfg.trainable_config.device == "cpu"
# Drive Accelerate's autocast from policy.dtype (bf16/fp16 activate it; float32 -> full precision).
has_policy_dtype = hasattr(cfg.trainable_config, "dtype")
# Drive Accelerate's autocast from policy.dtype (bf16/fp16 activate it; float32/absent -> launcher default).
policy_dtype = getattr(cfg.trainable_config, "dtype", None)
mixed_precision = {"bfloat16": "bf16", "float16": "fp16", "float32": "no"}.get(policy_dtype)
# Policies without a `dtype` field fall back to `use_amp`, which would otherwise be
# silently ignored here while lerobot-eval honors it. Follow torch.autocast's default
# for the configured device so training and evaluation use the same precision.
if not has_policy_dtype and getattr(cfg.trainable_config, "use_amp", False):
device_type = torch.device(cfg.trainable_config.device).type
autocast_dtype = torch.get_autocast_dtype(device_type)
mixed_precision = {torch.bfloat16: "bf16", torch.float16: "fp16"}[autocast_dtype]
accelerator = Accelerator(
step_scheduler_with_optimizer=False,
mixed_precision=mixed_precision,
@@ -17,14 +17,9 @@
import pytest
from lerobot.configs import FeatureType, PipelineFeatureType, PolicyFeature
from lerobot.processor.converters import create_transition
from lerobot.robots.so_follower.robot_kinematic_processor import (
EEReferenceAndDelta,
ForwardKinematicsJointsToEEAction,
ForwardKinematicsJointsToEEObservation,
GripperVelocityToJoint,
InverseKinematicsEEToJoints,
InverseKinematicsRLStep,
)
MOTOR_NAMES = ["shoulder_pan", "shoulder_lift", "elbow_flex", "wrist_flex", "wrist_roll", "gripper"]
@@ -48,38 +43,3 @@ def test_fk_feature_schema(step_cls, bucket, feature_type):
out = step_cls(kinematics=None, motor_names=MOTOR_NAMES).transform_features(features)[bucket]
assert set(out) == EE_KEYS
assert {feature.type for feature in out.values()} == {feature_type}
EE_ACTION = dict.fromkeys(EE_KEYS, 0.0)
@pytest.mark.parametrize(
("step", "action"),
[
(
EEReferenceAndDelta(kinematics=None, end_effector_step_sizes={}, motor_names=MOTOR_NAMES),
dict(EE_ACTION),
),
(InverseKinematicsEEToJoints(kinematics=None, motor_names=MOTOR_NAMES), dict(EE_ACTION)),
(GripperVelocityToJoint(), {**EE_ACTION, "ee.gripper_vel": 0.0}),
(InverseKinematicsRLStep(kinematics=None, motor_names=MOTOR_NAMES), dict(EE_ACTION)),
],
ids=[
"ee_reference_and_delta",
"inverse_kinematics_ee_to_joints",
"gripper_velocity_to_joint",
"inverse_kinematics_rl_step",
],
)
def test_missing_observation_raises_value_error(step, action):
"""A transition without an observation must surface the documented ValueError.
`RobotProcessorPipeline.process_action` builds its transition with
`create_transition(action=...)`, which sets `TransitionKey.OBSERVATION` to None.
These steps used to call `.copy()` on that before the None check, so the guard
below them was unreachable and an AttributeError escaped instead.
"""
transition = create_transition(action=action)
with pytest.raises(ValueError, match="Joints observation"):
step(transition)
Generated
+136 -136
View File
@@ -1,5 +1,5 @@
version = 1
revision = 3
revision = 2
requires-python = ">=3.12"
resolution-markers = [
"(python_full_version >= '3.15' and platform_machine == 'AMD64' and sys_platform == 'linux') or (python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'linux')",
@@ -402,10 +402,10 @@ name = "bddl"
version = "1.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jupytext" },
{ name = "networkx" },
{ name = "numpy" },
{ name = "pytest" },
{ name = "jupytext", marker = "sys_platform == 'linux'" },
{ name = "networkx", marker = "sys_platform == 'linux'" },
{ name = "numpy", marker = "sys_platform == 'linux'" },
{ name = "pytest", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5c/37/0211f82891a9f14efcfd2b2096f8d9e4351398ad637fdd1ee59cfc580b0e/bddl-1.0.1.tar.gz", hash = "sha256:1fa4e6e5050b93888ff6fd8455c39bfb29d3864ce06b4c37c0f781f513a2ae26", size = 164809, upload-time = "2022-03-08T01:48:23.564Z" }
@@ -1010,7 +1010,7 @@ name = "cuda-bindings"
version = "12.9.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cuda-pathfinder" },
{ name = "cuda-pathfinder", marker = "sys_platform == 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/32/45/557d4ed1fa54f0c7db8aee083229f624990d69f7d00f55477eed5c7e169a/cuda_bindings-12.9.7-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0666d3c082ef8f4b2d670950589373550e9f3bf564d635dd883f24a0b40402ff", size = 7071026, upload-time = "2026-05-27T18:44:13.356Z" },
@@ -1043,37 +1043,37 @@ wheels = [
[package.optional-dependencies]
cublas = [
{ name = "nvidia-cublas-cu12" },
{ name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" },
]
cudart = [
{ name = "nvidia-cuda-runtime-cu12" },
{ name = "nvidia-cuda-runtime-cu12", marker = "sys_platform == 'linux'" },
]
cufft = [
{ name = "nvidia-cufft-cu12" },
{ name = "nvidia-cufft-cu12", marker = "sys_platform == 'linux'" },
]
cufile = [
{ name = "nvidia-cufile-cu12" },
{ name = "nvidia-cufile-cu12", marker = "sys_platform == 'linux'" },
]
cupti = [
{ name = "nvidia-cuda-cupti-cu12" },
{ name = "nvidia-cuda-cupti-cu12", marker = "sys_platform == 'linux'" },
]
curand = [
{ name = "nvidia-curand-cu12" },
{ name = "nvidia-curand-cu12", marker = "sys_platform == 'linux'" },
]
cusolver = [
{ name = "nvidia-cusolver-cu12" },
{ name = "nvidia-cusolver-cu12", marker = "sys_platform == 'linux'" },
]
cusparse = [
{ name = "nvidia-cusparse-cu12" },
{ name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" },
]
nvjitlink = [
{ name = "nvidia-nvjitlink-cu12" },
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" },
]
nvrtc = [
{ name = "nvidia-cuda-nvrtc-cu12" },
{ name = "nvidia-cuda-nvrtc-cu12", marker = "sys_platform == 'linux'" },
]
nvtx = [
{ name = "nvidia-nvtx-cu12" },
{ name = "nvidia-nvtx-cu12", marker = "sys_platform == 'linux'" },
]
[[package]]
@@ -1145,7 +1145,7 @@ name = "decord"
version = "0.6.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "numpy", marker = "(platform_machine != 'arm64' and sys_platform == 'darwin') or (platform_machine == 'AMD64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/79/936af42edf90a7bd4e41a6cac89c913d4b47fa48a26b042d5129a9242ee3/decord-0.6.0-py3-none-manylinux2010_x86_64.whl", hash = "sha256:51997f20be8958e23b7c4061ba45d0efcd86bffd5fe81c695d0befee0d442976", size = 13602299, upload-time = "2021-06-14T21:30:55.486Z" },
@@ -1283,10 +1283,10 @@ resolution-markers = [
"python_full_version == '3.14.*' and sys_platform == 'win32'",
]
dependencies = [
{ name = "absl-py" },
{ name = "attrs" },
{ name = "numpy" },
{ name = "wrapt" },
{ name = "absl-py", marker = "python_full_version >= '3.14'" },
{ name = "attrs", marker = "python_full_version >= '3.14'" },
{ name = "numpy", marker = "python_full_version >= '3.14'" },
{ name = "wrapt", marker = "python_full_version >= '3.14'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a6/83/ce29720ccf934c6cfa9b9c95ebbe96558386e66886626066632b5e44afed/dm_tree-0.1.9.tar.gz", hash = "sha256:a4c7db3d3935a5a2d5e4b383fc26c6b0cd6f78c6d4605d3e7b518800ecd5342b", size = 35623, upload-time = "2025-01-30T20:45:37.13Z" }
wheels = [
@@ -1324,10 +1324,10 @@ resolution-markers = [
"python_full_version < '3.13' and sys_platform == 'win32'",
]
dependencies = [
{ name = "absl-py" },
{ name = "attrs" },
{ name = "numpy" },
{ name = "wrapt" },
{ name = "absl-py", marker = "python_full_version < '3.14'" },
{ name = "attrs", marker = "python_full_version < '3.14'" },
{ name = "numpy", marker = "python_full_version < '3.14'" },
{ name = "wrapt", marker = "python_full_version < '3.14'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5a/66/a3ec619d22b6baffa5ab853e8dc6ec9d0c837127948af59bb15b988d7312/dm_tree-0.1.10.tar.gz", hash = "sha256:22f37b599e01cc3402a17f79c257a802aebd8d326de05b54657650845956208a", size = 35748, upload-time = "2026-03-31T17:35:39.03Z" }
wheels = [
@@ -1911,7 +1911,7 @@ name = "h5py"
version = "3.16.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "numpy", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526, upload-time = "2026-03-06T13:49:08.07Z" }
wheels = [
@@ -1955,23 +1955,23 @@ name = "hf-libero"
version = "0.1.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "bddl" },
{ name = "cloudpickle" },
{ name = "easydict" },
{ name = "einops" },
{ name = "future" },
{ name = "gymnasium" },
{ name = "hf-egl-probe" },
{ name = "hydra-core" },
{ name = "matplotlib" },
{ name = "mujoco" },
{ name = "numpy" },
{ name = "opencv-python" },
{ name = "robomimic" },
{ name = "robosuite" },
{ name = "thop" },
{ name = "transformers" },
{ name = "wandb" },
{ name = "bddl", marker = "sys_platform == 'linux'" },
{ name = "cloudpickle", marker = "sys_platform == 'linux'" },
{ name = "easydict", marker = "sys_platform == 'linux'" },
{ name = "einops", marker = "sys_platform == 'linux'" },
{ name = "future", marker = "sys_platform == 'linux'" },
{ name = "gymnasium", marker = "sys_platform == 'linux'" },
{ name = "hf-egl-probe", marker = "sys_platform == 'linux'" },
{ name = "hydra-core", marker = "sys_platform == 'linux'" },
{ name = "matplotlib", marker = "sys_platform == 'linux'" },
{ name = "mujoco", marker = "sys_platform == 'linux'" },
{ name = "numpy", marker = "sys_platform == 'linux'" },
{ name = "opencv-python", marker = "sys_platform == 'linux'" },
{ name = "robomimic", marker = "sys_platform == 'linux'" },
{ name = "robosuite", marker = "sys_platform == 'linux'" },
{ name = "thop", marker = "sys_platform == 'linux'" },
{ name = "transformers", marker = "sys_platform == 'linux'" },
{ name = "wandb", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/af/aa/4e9eb8715e0bff9cb6553db563a35d253393097d446f82bd53575e8b253d/hf_libero-0.1.4.tar.gz", hash = "sha256:c058d67ad5a2b589529c14d614282ef4cca3a7763dafa134f58a6c9039657e34", size = 2961319, upload-time = "2026-06-10T09:56:13.994Z" }
wheels = [
@@ -2122,9 +2122,9 @@ name = "hydra-core"
version = "1.3.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "antlr4-python3-runtime" },
{ name = "omegaconf" },
{ name = "packaging" },
{ name = "antlr4-python3-runtime", marker = "sys_platform == 'linux'" },
{ name = "omegaconf", marker = "sys_platform == 'linux'" },
{ name = "packaging", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/10/dd/220f0e91743136725352497e98540772a01fc7c3ab96ff16c3c74424e984/hydra_core-1.3.4.tar.gz", hash = "sha256:ad0f7b05a0242255a8984d5a4ed2f6847f7b783ed727368a2c0155ec52d6c34c", size = 3263348, upload-time = "2026-07-04T16:25:38.891Z" }
wheels = [
@@ -2677,11 +2677,11 @@ name = "jupytext"
version = "1.19.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "mdit-py-plugins" },
{ name = "nbformat" },
{ name = "packaging" },
{ name = "pyyaml" },
{ name = "markdown-it-py", marker = "sys_platform == 'linux'" },
{ name = "mdit-py-plugins", marker = "sys_platform == 'linux'" },
{ name = "nbformat", marker = "sys_platform == 'linux'" },
{ name = "packaging", marker = "sys_platform == 'linux'" },
{ name = "pyyaml", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a1/ca/473f8ebb101553fb2ea6ab1d34324d6677844c968947ac050c759d539f2c/jupytext-1.19.5.tar.gz", hash = "sha256:605026446d605aa54fd7f7fc69df6ae51c7a46053d4cebf05afdc64d66de3df0", size = 4600916, upload-time = "2026-07-21T22:00:29.198Z" }
wheels = [
@@ -3471,7 +3471,7 @@ requires-dist = [
{ name = "pyrealsense2", marker = "sys_platform != 'darwin' and extra == 'intelrealsense'", specifier = ">=2.55.1.6486,<2.57.0" },
{ name = "pyrealsense2-macosx", marker = "sys_platform == 'darwin' and extra == 'intelrealsense'", specifier = ">=2.54,<2.57.0" },
{ name = "pyserial", marker = "extra == 'pyserial-dep'", specifier = ">=3.5,<4.0" },
{ name = "pytest", marker = "extra == 'test'", specifier = ">=8.1.0,<10.0.0" },
{ name = "pytest", marker = "extra == 'test'", specifier = ">=8.1.0,<9.0.0" },
{ name = "pytest-cov", marker = "extra == 'test'", specifier = ">=5.0.0,<8.0.0" },
{ name = "pytest-timeout", marker = "extra == 'test'", specifier = ">=2.4.0,<3.0.0" },
{ name = "python-can", marker = "extra == 'can-dep'", specifier = ">=4.2.0,<5.0.0" },
@@ -3485,7 +3485,7 @@ requires-dist = [
{ name = "scikit-image", marker = "extra == 'video-benchmark'", specifier = ">=0.23.2,<0.26.0" },
{ name = "scipy", marker = "extra == 'all'", specifier = ">=1.14.0,<2.0.0" },
{ name = "scipy", marker = "extra == 'scipy-dep'", specifier = ">=1.14.0,<2.0.0" },
{ name = "setuptools", specifier = ">=71.0.0,<82.0.0" },
{ name = "setuptools", specifier = ">=71.0.0,<81.0.0" },
{ name = "teleop", marker = "extra == 'phone'", specifier = ">=0.1.0,<0.2.0" },
{ name = "termcolor", specifier = ">=2.4.0,<4.0.0" },
{ name = "timm", marker = "extra == 'timm-dep'", specifier = ">=1.0.0,<1.1.0" },
@@ -3816,7 +3816,7 @@ name = "mdit-py-plugins"
version = "0.6.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "markdown-it-py", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" }
wheels = [
@@ -4295,8 +4295,8 @@ name = "numba"
version = "0.66.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "llvmlite" },
{ name = "numpy" },
{ name = "llvmlite", marker = "sys_platform == 'linux'" },
{ name = "numpy", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/a0/570e3dc53e5602b49108f62a13e529f1eec8bfc7ef37d49c825924dcf546/numba-0.66.0.tar.gz", hash = "sha256:b900e63a0e26c05ea9a6d5a3a5a0a177cb64c5011887bf43edb8c3ed2c38d363", size = 2806181, upload-time = "2026-07-01T23:12:46.36Z" }
wheels = [
@@ -4389,7 +4389,7 @@ name = "nvidia-cudnn-cu12"
version = "9.19.0.56"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas-cu12" },
{ name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/09/b8/277c51962ee46fa3e5b203ac5f76107c650f781d6891e681e28e6f3e9fe6/nvidia_cudnn_cu12-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:08caaf27fe556aca82a3ee3b5aa49a77e7de0cfcb7ff4e5c29da426387a8267e", size = 656910700, upload-time = "2026-02-03T20:40:25.508Z" },
@@ -4401,7 +4401,7 @@ name = "nvidia-cufft-cu12"
version = "11.3.3.83"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink-cu12" },
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/60/bc/7771846d3a0272026c416fbb7e5f4c1f146d6d80704534d0b187dd6f4800/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:848ef7224d6305cdb2a4df928759dca7b1201874787083b6e7550dd6765ce69a", size = 193109211, upload-time = "2025-03-07T01:44:56.873Z" },
@@ -4431,9 +4431,9 @@ name = "nvidia-cusolver-cu12"
version = "11.7.3.90"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas-cu12" },
{ name = "nvidia-cusparse-cu12" },
{ name = "nvidia-nvjitlink-cu12" },
{ name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" },
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/32/f7cd6ce8a7690544d084ea21c26e910a97e077c9b7f07bf5de623ee19981/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:db9ed69dbef9715071232caa9b69c52ac7de3a95773c2db65bdba85916e4e5c0", size = 267229841, upload-time = "2025-03-07T01:46:54.356Z" },
@@ -4445,7 +4445,7 @@ name = "nvidia-cusparse-cu12"
version = "12.5.8.93"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink-cu12" },
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/f7/cd777c4109681367721b00a106f491e0d0d15cfa1fd59672ce580ce42a97/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b6c161cb130be1a07a27ea6923df8141f3c295852f4b260c65f18f3e0a091dc", size = 288117129, upload-time = "2025-03-07T01:47:40.407Z" },
@@ -4502,8 +4502,8 @@ name = "omegaconf"
version = "2.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "antlr4-python3-runtime" },
{ name = "pyyaml" },
{ name = "antlr4-python3-runtime", marker = "sys_platform == 'linux'" },
{ name = "pyyaml", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ce/3d/e4b57b8d9008c6ebe0d5eff901f91d5700cf7bdb8c8863df817463a7fd5e/omegaconf-2.3.1.tar.gz", hash = "sha256:e5e7de64aeebeddaf8e6d3f7a783b32ac2a01c0fbd9c878012caecb891a1f42a", size = 3298472, upload-time = "2026-06-11T05:05:12.885Z" }
wheels = [
@@ -4743,7 +4743,7 @@ name = "pexpect"
version = "4.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ptyprocess" },
{ name = "ptyprocess", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
wheels = [
@@ -5317,10 +5317,10 @@ name = "pyobjc-framework-applicationservices"
version = "12.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
{ name = "pyobjc-framework-cocoa" },
{ name = "pyobjc-framework-coretext" },
{ name = "pyobjc-framework-quartz" },
{ name = "pyobjc-core", marker = "sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'" },
{ name = "pyobjc-framework-cocoa", marker = "sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'" },
{ name = "pyobjc-framework-coretext", marker = "sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'" },
{ name = "pyobjc-framework-quartz", marker = "sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5e/4d/0ebdd8144aba94b8fe9828ccee5616a4bf53d1f8bc51cff55f3cce86d695/pyobjc_framework_applicationservices-12.2.1.tar.gz", hash = "sha256:048ea663c9ac75c44a15dc7d5b8d78cbb4c97bf1c76e83835e8d5498e184001f", size = 109342, upload-time = "2026-06-19T16:19:46.149Z" }
wheels = [
@@ -5338,7 +5338,7 @@ name = "pyobjc-framework-cocoa"
version = "12.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
{ name = "pyobjc-core", marker = "sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/51/34/fbe38a204643aa4e1b91391cdce07a34da565a69171ebcad08de7438a556/pyobjc_framework_cocoa-12.2.1.tar.gz", hash = "sha256:b94b37fe5730e5ae1fb0052912cd174e6ec329b0bfba4a012ae5db1014b5864b", size = 3125751, upload-time = "2026-06-19T16:20:05.159Z" }
wheels = [
@@ -5356,9 +5356,9 @@ name = "pyobjc-framework-coretext"
version = "12.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
{ name = "pyobjc-framework-cocoa" },
{ name = "pyobjc-framework-quartz" },
{ name = "pyobjc-core", marker = "sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'" },
{ name = "pyobjc-framework-cocoa", marker = "sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'" },
{ name = "pyobjc-framework-quartz", marker = "sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5a/9c/4c7f452059dc1d3845b8e627b9113c247a997b9b07518e848c2ab7ff3149/pyobjc_framework_coretext-12.2.1.tar.gz", hash = "sha256:af740e784d7c592c34025ec7165f4f6c1a69b5a2d9075f06e41e4f77c212aed2", size = 97349, upload-time = "2026-06-19T16:20:22.508Z" }
wheels = [
@@ -5376,8 +5376,8 @@ name = "pyobjc-framework-quartz"
version = "12.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
{ name = "pyobjc-framework-cocoa" },
{ name = "pyobjc-core", marker = "sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'" },
{ name = "pyobjc-framework-cocoa", marker = "sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3b/f6/2a8b84dbf1fe7c04dd96ea73d991678d4e09a909f51971ecc51629bb2ab4/pyobjc_framework_quartz-12.2.1.tar.gz", hash = "sha256:b3b8b6f71e66147f8ff9e6213864cc8527e3a0b1ee90835b93ce221f4802d9b0", size = 3215521, upload-time = "2026-06-19T16:21:30.199Z" }
wheels = [
@@ -5940,18 +5940,18 @@ name = "robomimic"
version = "0.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "egl-probe" },
{ name = "h5py" },
{ name = "imageio" },
{ name = "imageio-ffmpeg" },
{ name = "numpy" },
{ name = "psutil" },
{ name = "tensorboard" },
{ name = "tensorboardx" },
{ name = "termcolor" },
{ name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" } },
{ name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" } },
{ name = "tqdm" },
{ name = "egl-probe", marker = "sys_platform == 'linux'" },
{ name = "h5py", marker = "sys_platform == 'linux'" },
{ name = "imageio", marker = "sys_platform == 'linux'" },
{ name = "imageio-ffmpeg", marker = "sys_platform == 'linux'" },
{ name = "numpy", marker = "sys_platform == 'linux'" },
{ name = "psutil", marker = "sys_platform == 'linux'" },
{ name = "tensorboard", marker = "sys_platform == 'linux'" },
{ name = "tensorboardx", marker = "sys_platform == 'linux'" },
{ name = "termcolor", marker = "sys_platform == 'linux'" },
{ name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux'" },
{ name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux'" },
{ name = "tqdm", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/c3/44b1d1ea4bcb4bbed43d19e09505f4142714451ded74020d4f679cdc89fb/robomimic-0.2.0.tar.gz", hash = "sha256:ee3bb5cf9c3e1feead6b57b43c5db738fd0a8e0c015fdf6419808af8fffdc463", size = 192919, upload-time = "2021-12-17T19:00:33.279Z" }
@@ -5960,12 +5960,12 @@ name = "robosuite"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mujoco" },
{ name = "numba" },
{ name = "numpy" },
{ name = "opencv-python" },
{ name = "pillow" },
{ name = "scipy" },
{ name = "mujoco", marker = "sys_platform == 'linux'" },
{ name = "numba", marker = "sys_platform == 'linux'" },
{ name = "numpy", marker = "sys_platform == 'linux'" },
{ name = "opencv-python", marker = "sys_platform == 'linux'" },
{ name = "pillow", marker = "sys_platform == 'linux'" },
{ name = "scipy", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/25/a1/9dd07a9a5e09c6aa032faf531da985808b34437cbf6c8f358fe8f7c47118/robosuite-1.4.0.tar.gz", hash = "sha256:a8a6233d7458dbd91bf00a86cab15aa1c178bd9d1b28d515db2cf3d152cb48e6", size = 192182147, upload-time = "2022-12-01T07:31:55.791Z" }
wheels = [
@@ -6386,16 +6386,16 @@ name = "tensorboard"
version = "2.20.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "absl-py" },
{ name = "grpcio" },
{ name = "markdown" },
{ name = "numpy" },
{ name = "packaging" },
{ name = "pillow" },
{ name = "protobuf" },
{ name = "setuptools" },
{ name = "tensorboard-data-server" },
{ name = "werkzeug" },
{ name = "absl-py", marker = "sys_platform == 'linux'" },
{ name = "grpcio", marker = "sys_platform == 'linux'" },
{ name = "markdown", marker = "sys_platform == 'linux'" },
{ name = "numpy", marker = "sys_platform == 'linux'" },
{ name = "packaging", marker = "sys_platform == 'linux'" },
{ name = "pillow", marker = "sys_platform == 'linux'" },
{ name = "protobuf", marker = "sys_platform == 'linux'" },
{ name = "setuptools", marker = "sys_platform == 'linux'" },
{ name = "tensorboard-data-server", marker = "sys_platform == 'linux'" },
{ name = "werkzeug", marker = "sys_platform == 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/9c/d9/a5db55f88f258ac669a92858b70a714bbbd5acd993820b41ec4a96a4d77f/tensorboard-2.20.0-py3-none-any.whl", hash = "sha256:9dc9f978cb84c0723acf9a345d96c184f0293d18f166bb8d59ee098e6cfaaba6", size = 5525680, upload-time = "2025-07-17T19:20:49.638Z" },
@@ -6415,9 +6415,9 @@ name = "tensorboardx"
version = "2.6.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "packaging" },
{ name = "protobuf" },
{ name = "numpy", marker = "sys_platform == 'linux'" },
{ name = "packaging", marker = "sys_platform == 'linux'" },
{ name = "protobuf", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/48/a9/fc520ea91ab1f3ba51cbf3fe24f2b6364ed3b49046969e0868d46d6da372/tensorboardx-2.6.5.tar.gz", hash = "sha256:ca176db3997ee8c07d2eb77381225956a3fd1c10c91beafab1f17069adc47017", size = 4770195, upload-time = "2026-04-03T15:40:23.803Z" }
wheels = [
@@ -6452,7 +6452,7 @@ name = "thop"
version = "0.1.1.post2209072238"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" } },
{ name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/bb/0f/72beeab4ff5221dc47127c80f8834b4bcd0cb36f6ba91c0b1d04a1233403/thop-0.1.1.post2209072238-py3-none-any.whl", hash = "sha256:01473c225231927d2ad718351f78ebf7cffe6af3bed464c4f1ba1ef0f7cdda27", size = 15443, upload-time = "2022-09-07T14:38:37.211Z" },
@@ -6558,13 +6558,13 @@ resolution-markers = [
"python_full_version < '3.13' and sys_platform == 'win32'",
]
dependencies = [
{ name = "filelock" },
{ name = "fsspec" },
{ name = "jinja2" },
{ name = "networkx" },
{ name = "setuptools" },
{ name = "sympy" },
{ name = "typing-extensions" },
{ name = "filelock", marker = "sys_platform != 'linux'" },
{ name = "fsspec", marker = "sys_platform != 'linux'" },
{ name = "jinja2", marker = "sys_platform != 'linux'" },
{ name = "networkx", marker = "sys_platform != 'linux'" },
{ name = "setuptools", marker = "sys_platform != 'linux'" },
{ name = "sympy", marker = "sys_platform != 'linux'" },
{ name = "typing-extensions", marker = "sys_platform != 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338, upload-time = "2026-03-23T18:11:34.781Z" },
@@ -6598,20 +6598,20 @@ resolution-markers = [
"python_full_version < '3.13' and platform_machine != 'AMD64' and platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'linux'",
]
dependencies = [
{ name = "cuda-bindings" },
{ name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"] },
{ name = "filelock" },
{ name = "fsspec" },
{ name = "jinja2" },
{ name = "networkx" },
{ name = "nvidia-cudnn-cu12" },
{ name = "nvidia-cusparselt-cu12" },
{ name = "nvidia-nccl-cu12" },
{ name = "nvidia-nvshmem-cu12" },
{ name = "setuptools" },
{ name = "sympy" },
{ name = "triton" },
{ name = "typing-extensions" },
{ name = "cuda-bindings", marker = "sys_platform == 'linux'" },
{ name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" },
{ name = "filelock", marker = "sys_platform == 'linux'" },
{ name = "fsspec", marker = "sys_platform == 'linux'" },
{ name = "jinja2", marker = "sys_platform == 'linux'" },
{ name = "networkx", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cudnn-cu12", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cusparselt-cu12", marker = "sys_platform == 'linux'" },
{ name = "nvidia-nccl-cu12", marker = "sys_platform == 'linux'" },
{ name = "nvidia-nvshmem-cu12", marker = "sys_platform == 'linux'" },
{ name = "setuptools", marker = "sys_platform == 'linux'" },
{ name = "sympy", marker = "sys_platform == 'linux'" },
{ name = "triton", marker = "sys_platform == 'linux'" },
{ name = "typing-extensions", marker = "sys_platform == 'linux'" },
]
wheels = [
{ url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9c8f38efee365cb9d334de8a83ce52fc7e5fc9e5a7b0853285efa1b69e00b0f2", upload-time = "2026-04-27T17:41:30Z" },
@@ -6682,9 +6682,9 @@ resolution-markers = [
"python_full_version < '3.13' and sys_platform == 'win32'",
]
dependencies = [
{ name = "numpy" },
{ name = "pillow" },
{ name = "torch", version = "2.11.0", source = { registry = "https://pypi.org/simple" } },
{ name = "numpy", marker = "sys_platform != 'linux'" },
{ name = "pillow", marker = "sys_platform != 'linux'" },
{ name = "torch", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux'" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/ae/e7/56b47cc3b132aea90ccce22bcb8975dec688b002150012acc842846039d0/torchvision-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c409e1c3fdebec7a3834465086dbda8bf7680eff79abf7fd2f10c6b59520a7a4", size = 1863502, upload-time = "2026-03-23T18:12:57.326Z" },
@@ -6718,9 +6718,9 @@ resolution-markers = [
"python_full_version < '3.13' and platform_machine != 'AMD64' and platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'linux'",
]
dependencies = [
{ name = "numpy" },
{ name = "pillow" },
{ name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" } },
{ name = "numpy", marker = "sys_platform == 'linux'" },
{ name = "pillow", marker = "sys_platform == 'linux'" },
{ name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux'" },
]
wheels = [
{ url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:63e35234aed13b6edda37056f417b5c281249669db631e706811917af36b21d7", upload-time = "2026-04-09T23:21:35Z" },
@@ -7222,7 +7222,7 @@ name = "werkzeug"
version = "3.1.8"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
{ name = "markupsafe", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" }
wheels = [