Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d3d4f70
Add tiptop-rerun CLI and extract run_tiptop from tiptop_h5
williamshen-nz Apr 10, 2026
231d8e3
Rename save_dir to run_dir in viz_tiptop_run for consistency
williamshen-nz Apr 10, 2026
dfe0f7f
Add tiptop-rerun to docs and update viz-tiptop-run flag naming
williamshen-nz Apr 10, 2026
bfff8d3
Combine tiptop_h5 and tiptop_rerun into tiptop_offline
williamshen-nz Apr 10, 2026
0f0ff03
Unify offline output directory and simplify gitignore
williamshen-nz Apr 10, 2026
16d01a5
Use tiptop_outputs in tiptop-rerun doc examples
williamshen-nz Apr 10, 2026
d9d36d3
Rename rerun_tiptop to run_tiptop_rerun for consistency
williamshen-nz Apr 10, 2026
28ca5fd
Trim redundant 'perception and planning' from docstrings and docs
williamshen-nz Apr 10, 2026
c14f665
Remove ambiguous __main__ block from tiptop_offline
williamshen-nz Apr 10, 2026
bffce33
Remove section comments and inline _load_cutamp_config
williamshen-nz Apr 10, 2026
5ea71aa
Merge remote-tracking branch 'origin/main' into will/tiptop-rerun
williamshen-nz Apr 21, 2026
0fb3463
Address PR #16 review comments
williamshen-nz Apr 21, 2026
74a4799
Fall back to built-in defaults when cuTAMP config is missing in rerun
williamshen-nz Apr 21, 2026
c3811a3
Address PR #16 second review round
williamshen-nz Apr 21, 2026
69e82ff
Shorten verbose inline comments added in the rerun work
williamshen-nz Apr 21, 2026
f0c4969
Move logging setup to entrypoint level; save merged config in runs
williamshen-nz Apr 21, 2026
0ae7d56
Simplify setup_logging: root always at DEBUG
williamshen-nz Apr 21, 2026
65997a0
Cache config source path; save_run_outputs copies it to preserve comm…
williamshen-nz Apr 22, 2026
6e280f9
Consolidate config cache mutation into one helper
williamshen-nz Apr 22, 2026
caaea80
Privatize tiptop_config_path
williamshen-nz Apr 22, 2026
4ee752c
Inline the default tiptop.yml path
williamshen-nz Apr 22, 2026
061ca7c
Collapse config cache into set_tiptop_cfg_from_file
williamshen-nz Apr 22, 2026
c102ad9
Tighten docstrings in tiptop_cfg and file handler helpers
williamshen-nz Apr 22, 2026
c9eec0d
Drop OmegaConf.from_cli from config loader
williamshen-nz Apr 22, 2026
d8c97ba
Drop unused force_reload arg from tiptop_cfg
williamshen-nz Apr 22, 2026
d9f7817
Note call-order requirement in set_tiptop_cfg_from_file docstring
williamshen-nz Apr 22, 2026
06e5273
Merge remote-tracking branch 'origin/main' into will/tiptop-rerun
williamshen-nz May 30, 2026
abbcbe8
Add CHANGELOG.md for v0.2.0 release
williamshen-nz May 30, 2026
640434b
Revise CHANGELOG: breaking-changes section, working PR links, drop Un…
williamshen-nz Jun 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ __pycache__/
# tiptop cache and outputs
tiptop/.cache/
tiptop_outputs/
tiptop_outputs*
tiptop_h5_outputs/
tiptop_server_outputs/
tiptop_*_outputs/

# JetBrains
.idea/
Expand Down
65 changes: 65 additions & 0 deletions CHANGELOG.md
Comment thread
williamshen-nz marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Changelog

All notable changes to TiPToP are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0] - 2026-06-05

### Breaking Changes

- `viz-tiptop-run` renamed its `--save-dir` flag to `--run-dir` ([#16]).
- `tiptop_cfg()` no longer accepts `force_reload` and no longer merges CLI overrides
from `sys.argv` via `OmegaConf.from_cli`. Load a specific config with the new
`set_tiptop_cfg_from_file()` before the first `tiptop_cfg()` call. The
`tiptop.config.tiptop_config_path` constant is no longer exported ([#16]).

### Added

- `tiptop-rerun` CLI: re-runs the pipeline from a saved run directory, reusing the
recorded observation. Task instruction and planning parameters default to the
original run's values and can be overridden via flags ([#16]).
- Pick-only tasks: the pipeline now supports plans that just pick an object, including
a prompt to safely catch the object on the real robot after a pick ([#27]).
- Rerun-disabled mode and a config option to run without applying M2T2 bounds ([#27]).
- `save_dir` is now included in the `tiptop-server` response ([#27]).
- `set_tiptop_cfg_from_file()` and `get_tiptop_cfg_path()` in `tiptop.config` for
loading a config from an explicit path and querying the cached config's source path ([#16]).
- Integration tests for the offline H5 pipeline, runnable via the `test-integration`
pixi task ([#14]).

### Changed

- `tiptop-h5` and the offline rerun logic are consolidated into `tiptop/tiptop_offline.py`
(entry points `h5_entrypoint` and `rerun_entrypoint`); `tiptop/tiptop_h5.py` is removed.
The `tiptop-h5` CLI and its flags are unchanged ([#16]).
- Offline runs save the merged config into the run directory so re-runs are reproducible ([#16]).
- Logging setup moved to the entrypoint level ([#16]).
- Updated the Gemini perception model from `gemini-robotics-er-1.5-preview` to
`gemini-robotics-er-1.6-preview` ([#26]).
- Updated cuTAMP to 0.0.4 and added a configurable max number of motion-refinement
attempts ([#19], [#20]).
- `tiptop-server` serializes pipeline runs with an asyncio lock so concurrent requests
no longer interleave ([#23]).
- Point-cloud erosion falls back to no erosion when it would leave too few points ([#27]).

### Fixed

- Fixed broken intrinsics imports in `calibrate_wrist_cam` ([#21]).

## [0.1.0]

Initial tagged release.

[0.2.0]: https://github.com/tiptop-robot/tiptop/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/tiptop-robot/tiptop/releases/tag/v0.1.0

[#14]: https://github.com/tiptop-robot/tiptop/pull/14
[#16]: https://github.com/tiptop-robot/tiptop/pull/16
[#19]: https://github.com/tiptop-robot/tiptop/pull/19
[#20]: https://github.com/tiptop-robot/tiptop/pull/20
[#21]: https://github.com/tiptop-robot/tiptop/pull/21
[#23]: https://github.com/tiptop-robot/tiptop/pull/23
[#26]: https://github.com/tiptop-robot/tiptop/pull/26
[#27]: https://github.com/tiptop-robot/tiptop/pull/27
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ gdown <link-url>

- **Discuss before implementing** — When the approach isn't obvious (e.g., where to put docs, how to structure config), talk through options before writing code
- **Comments and docstrings must be accurate** — Don't write comments that describe implementation details irrelevant to the reader or are vaguely wrong. If a comment doesn't add real information, drop it
- **Don't pile iteration rationale into comments/docstrings** — When you revise code, don't tack on explanations of why you chose this shape or cross-module consequences ("path is cached so reruns forward the original", "preserves comments"). Keep comments minimal and code-local; rationale and history belong in the PR description
- **Don't add dead code paths** — If every case goes down the same branch, don't add the other branch "just in case." Keep what's tested, remove what isn't

## Documentation Style Guide
Expand Down
50 changes: 45 additions & 5 deletions docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ After warmup and health checks you'll be prompted to enter a natural language ta

### tiptop-h5

Offline evaluation mode. Loads a pre-recorded RGB-D observation from an H5 file (droid-sim-evals format), runs perception and planning without a real robot, and saves the resulting plan as a JSON file for downstream evaluation.
Offline evaluation mode. Loads a pre-recorded RGB-D observation from an H5 file (droid-sim-evals format) and runs the TiPToP pipeline without a real robot, saving a serialized plan JSON for downstream evaluation.

**Prerequisites:**
- M2T2 server must be running
Expand Down Expand Up @@ -118,6 +118,46 @@ tiptop-h5 \

---

### tiptop-rerun

Re-runs TiPToP from a saved run directory. Loads the observation (RGB, depth, intrinsics, camera pose, joint positions, and gripper mask) from a previous run. Task instruction and planning parameters default to the original run's values but can be overridden.

**Prerequisites:**
- M2T2 server must be running
- `GOOGLE_API_KEY` environment variable must be set
- A saved TiPToP run directory (from `tiptop-run`, `tiptop-h5`, or `tiptop-rerun`)

**Available flags:**

- `--run-dir STR` - Path to a saved TiPToP run directory (required)
- `--task-instruction STR` - Task instruction override; defaults to the original run's instruction
- `--output-dir STR` - Top-level directory to save results; a timestamped subdirectory will be created (default: "tiptop_rerun_outputs")
- `--max-planning-time FLOAT` - Override max planning time; defaults to the original run's value
- `--opt-steps-per-skeleton INT` - Override optimization steps per skeleton; defaults to the original run's value
- `--num-particles INT` - Override number of particles; defaults to the original run's value
- `--cutamp-visualize / --no-cutamp-visualize` - Enable cuTAMP planning visualization in Rerun, significantly slows down planning (default: False)
- `--rr-spawn / --no-rr-spawn` - Spawn a Rerun viewer; set to False to skip visualization (default: True)

**Example usage:**

```bash
# Re-run from a previous run with original parameters
tiptop-rerun --run-dir tiptop_outputs/eval/2026-04-05_13-30-20/

# Re-run with a different task instruction
tiptop-rerun \
--run-dir tiptop_outputs/eval/2026-04-05_13-30-20/ \
--task-instruction "put the cube on the table"

# Re-run with more particles and longer planning time
tiptop-rerun \
--run-dir tiptop_outputs/eval/2026-01-24_15-30-00/ \
--num-particles 512 \
--max-planning-time 120.0
```

---

### tiptop-server

Runs the TiPToP perception and planning pipeline as a WebSocket server. Clients send RGB-D observations and receive serialized trajectory plans, allowing consumers on separate machines or running different Python versions to query TiPToP. A `/health` HTTP endpoint is available for liveness checks.
Expand Down Expand Up @@ -407,7 +447,7 @@ Replays and visualizes the outputs of a saved TiPToP run in Rerun. Loads percept

**Available flags:**

- `--save-dir STR` - Path to the saved run directory (required)
- `--run-dir STR` - Path to a saved TiPToP run directory (required)
- `--visualize-grasps / --no-visualize-grasps` - Visualize M2T2 grasp candidates (default: True)
- `--visualize-plan / --no-visualize-plan` - Animate the TiPToP plan trajectory with object poses (default: True)
- `--num-grasps-per-object INT` - Maximum number of grasp candidates to display per object (default: 30)
Expand All @@ -417,13 +457,13 @@ Replays and visualizes the outputs of a saved TiPToP run in Rerun. Loads percept

```bash
# Visualize a run directory
viz-tiptop-run --save-dir tiptop_outputs/eval/2026-01-24_15-30-00/
viz-tiptop-run --run-dir tiptop_outputs/eval/2026-01-24_15-30-00/

# Visualize perception only, skip plan animation
viz-tiptop-run --save-dir tiptop_outputs/eval/2026-01-24_15-30-00/ --no-visualize-plan
viz-tiptop-run --run-dir tiptop_outputs/eval/2026-01-24_15-30-00/ --no-visualize-plan

# Show fewer grasps
viz-tiptop-run --save-dir tiptop_outputs/eval/2026-01-24_15-30-00/ --num-grasps-per-object 10
viz-tiptop-run --run-dir tiptop_outputs/eval/2026-01-24_15-30-00/ --num-grasps-per-object 10
```

A Rerun window will open automatically. Use the `tiptop_execution` timeline to step through the planned trajectory. The `cam` entity shows the camera pose and image at capture time, and `world/` contains all objects and their poses throughout execution.
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ gripper-close = "tiptop.scripts.gripper_actuate:gripper_close_entrypoint"
# Main tiptop demo
tiptop-run = "tiptop.tiptop_run:entrypoint"

# tiptop h5 run
tiptop-h5 = "tiptop.tiptop_h5:entrypoint"
# Offline tiptop (no real robot)
tiptop-h5 = "tiptop.tiptop_offline:h5_entrypoint"
tiptop-rerun = "tiptop.tiptop_offline:rerun_entrypoint"

# tiptop as a service
tiptop-server = "tiptop.tiptop_websocket_server:entrypoint"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_tiptop_h5.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def test_tiptop_h5_pipeline(tmp_path, h5_assets, h5_filename, task_instruction):
assert h5_path.exists(), f"Test asset not found: {h5_path}"

# Local import to avoid slow transitive imports affecting other tests
from tiptop.tiptop_h5 import run_tiptop_h5
from tiptop.tiptop_offline import run_tiptop_h5

run_tiptop_h5(
h5_path=str(h5_path),
Expand Down
33 changes: 23 additions & 10 deletions tiptop/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,36 @@

config_dir = Path(__file__).parent
config_assets_dir = config_dir / "assets"
tiptop_config_path = config_dir / "tiptop.yml"
calib_info_path = config_assets_dir / "calibration_info.json"

_cached_cfg = None # Cache for lazy loading
_cached_cfg: DictConfig | None = None
_cached_cfg_path: Path | None = None


def tiptop_cfg(force_reload: bool = False) -> DictConfig:
"""Load TiPToP config from file."""
global _cached_cfg
if _cached_cfg is None or force_reload:
_cached_cfg = OmegaConf.load(tiptop_config_path)
# Merge CLI overrides from sys.argv
cli = OmegaConf.from_cli()
_cached_cfg = OmegaConf.merge(_cached_cfg, cli)
def set_tiptop_cfg_from_file(cfg_path: Path) -> DictConfig:
"""Load and cache the TiPToP config from a specific file. Call before any tiptop_cfg() usage."""
global _cached_cfg, _cached_cfg_path
cfg = OmegaConf.load(cfg_path)
_cached_cfg = cfg
_cached_cfg_path = Path(cfg_path)
return cfg


def tiptop_cfg() -> DictConfig:
"""Return the cached TiPToP config, loading the default config file on first call."""
if _cached_cfg is None:
return set_tiptop_cfg_from_file(config_dir / "tiptop.yml")
return _cached_cfg


def get_tiptop_cfg_path() -> Path:
"""Return the source path of the currently-cached config. Loads the default config if not yet cached."""
if _cached_cfg_path is None:
tiptop_cfg()
assert _cached_cfg_path is not None
Comment thread
williamshen-nz marked this conversation as resolved.
return _cached_cfg_path


def load_calibration_info():
if not os.path.exists(calib_info_path):
raise FileNotFoundError(f"{calib_info_path} not found.")
Expand Down
4 changes: 2 additions & 2 deletions tiptop/recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from PIL import Image

import tiptop
from tiptop.config import tiptop_config_path
from tiptop.config import get_tiptop_cfg_path
from tiptop.perception.cameras.zed_camera import ZedCamera, convert_svo_to_mp4
from tiptop.perception.utils import get_o3d_pcd
from tiptop.perception.visualization import visualize_detections, visualize_masks
Expand Down Expand Up @@ -213,7 +213,7 @@ def save_run_outputs(save_dir: Path, env, grasps: dict) -> None:
_log.info(f"Saved grasps to {perception_dir}/grasps.pt")

# tiptop config for reproducibility
shutil.copy2(tiptop_config_path, save_dir / "tiptop.yml")
shutil.copy2(get_tiptop_cfg_path(), save_dir / "tiptop.yml")
_log.info(f"Saved tiptop config to {save_dir}/tiptop.yml")
Comment thread
williamshen-nz marked this conversation as resolved.


Expand Down
28 changes: 14 additions & 14 deletions tiptop/scripts/viz_tiptop_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def viz_tiptop_plan(tiptop_plan: dict, cutamp_env: TAMPEnvironment, robot_rr: Re


def viz_tiptop_run(
save_dir: str,
run_dir: str,
visualize_grasps: bool = True,
visualize_plan: bool = True,
num_grasps_per_object: int = 30,
Expand All @@ -125,21 +125,21 @@ def viz_tiptop_run(
Visualize TiPToP outputs (perception and plan) from a saved run directory in Rerun.

Args:
save_dir: Path to the saved run directory containing metadata.json, rgb.png, perception/, etc.
run_dir: Path to a saved TiPToP run directory (contains metadata.json, rgb.png, etc.).
visualize_grasps: Whether to visualize the M2T2 grasp candidates.
visualize_plan: Whether to visualize the TiPToP plan trajectory, including object poses while grasped.
num_grasps_per_object: Maximum number of grasp candidates to display per object.
log_transform_arrows: Whether to log coordinate frame arrows on object transforms.
"""
setup_logging()
save_dir = Path(save_dir)
perception_dir = save_dir / "perception"
run_dir = Path(run_dir)
perception_dir = run_dir / "perception"

# Load metadata
metadata_path = save_dir / "metadata.json"
metadata_path = run_dir / "metadata.json"
if not metadata_path.exists():
raise FileNotFoundError(f"Could not find metadata.json in {save_dir}")
with open(save_dir / "metadata.json") as f:
raise FileNotFoundError(f"Could not find metadata.json in {run_dir}")
with open(run_dir / "metadata.json") as f:
metadata = json.load(f)
if metadata["version"] != "1.0.0":
raise NotImplementedError(f"Version {metadata['version']} not supported")
Expand All @@ -149,7 +149,7 @@ def viz_tiptop_run(
rr.init(application_id="viz_tiptop_outputs", spawn=True)

# Load tiptop config from this run
tiptop_cfg = OmegaConf.load(save_dir / "tiptop.yml")
tiptop_cfg = OmegaConf.load(run_dir / "tiptop.yml")
robot_type = tiptop_cfg["robot"]["type"]
robot_rr = get_robot_rerun(robot_type=robot_type)
robot_rr.set_joint_positions(metadata["observation"]["q_at_capture"])
Expand All @@ -161,7 +161,7 @@ def viz_tiptop_run(
intrinsics = json.load(f)
K = np.array(intrinsics["intrinsics"])
rr.log("cam", rr.Pinhole(image_from_camera=K), static=True)
rgb = Image.open(save_dir / "rgb.png")
rgb = Image.open(run_dir / "rgb.png")
rr.log("cam/rgb", rr.Image(rgb), static=True)

# Mask out depth where gripper is present
Expand All @@ -175,14 +175,14 @@ def viz_tiptop_run(
rr.log("cam/depth", rr.DepthImage(depth, meter=1000.0), static=True)

# Bounding boxes and mask visualization
bboxes_viz = Image.open(save_dir / "bboxes_viz.png")
masks_viz = Image.open(save_dir / "masks_viz.png")
bboxes_viz = Image.open(run_dir / "bboxes_viz.png")
masks_viz = Image.open(run_dir / "masks_viz.png")
rr.log("bboxes_viz", rr.Image(bboxes_viz), static=True)
rr.log("masks_viz", rr.Image(masks_viz), static=True)

# Point cloud
pcd = o3d.io.read_point_cloud(perception_dir / "pointcloud.ply")
rr.log("pcd", rr.Points3D(positions=pcd.points, colors=pcd.colors))
rr.log("pcd", rr.Points3D(positions=pcd.points, colors=pcd.colors), static=True)

# Grasps
if visualize_grasps:
Expand Down Expand Up @@ -210,9 +210,9 @@ def viz_tiptop_run(
return

# Load and visualize the tiptop plan
tiptop_plan_path = save_dir / "tiptop_plan.json"
tiptop_plan_path = run_dir / "tiptop_plan.json"
if not tiptop_plan_path.exists():
_log.warning(f"Could not find tiptop_plan.json in {save_dir}")
_log.warning(f"Could not find tiptop_plan.json in {run_dir}")
return
tiptop_plan = load_tiptop_plan(tiptop_plan_path)
if tiptop_plan["version"] != "1.0.0":
Expand Down
Loading