Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 16 additions & 8 deletions examples/noitom/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ MCAP script because the recording uses the standard `full_body` channel.

![Noitom full-body recording](assets/record.gif)

Record the Noitom full-body stream:
Install the example once, then record the Noitom full-body stream:

```bash
uv run python examples/noitom/record_noitom_full_body.py \
uv pip install -e ./examples/noitom
python -m isaacteleop_examples.noitom.record_noitom_full_body \
10 examples/noitom/recordings/noitom_full_body.mcap
```

Expand All @@ -70,14 +71,22 @@ uv run python replay_full_body.py ../../noitom/recordings/noitom_full_body.mcap
Run Isaac Lab with the external task registration callback. The task launches
`noitom_mocap_plugin` through IsaacTeleop's plugin manager by default.

The callback runs inside Isaac Lab's own interpreter, so install the example
into *that* environment rather than a venv of your own — this replaces the
`PYTHONPATH=` prefix these commands used to carry:

```bash
cd ~/dependence/IsaacLab3-0
./isaaclab.sh -p -m pip install -e ~/IsaacTeleop/examples/noitom
```

```bash
cd ~/dependence/IsaacLab3-0
PYTHONPATH=~/IsaacTeleop/examples/noitom:$PYTHONPATH \
./isaaclab.sh -p scripts/environments/teleoperation/teleop_se3_agent.py \
./isaaclab.sh -p scripts/environments/teleoperation/teleop_se3_agent.py \
--task Isaac-PickPlace-Locomanipulation-G1-Noitom-Abs-v0 \
--visualizer kit \
--xr \
--external_callback noitom_tasks.register_tasks
--external_callback isaacteleop_examples.noitom.noitom_tasks.register_tasks
```

For advanced manual plugin control, start the plugin yourself and disable
Expand All @@ -87,12 +96,11 @@ auto-launch in the Isaac Lab terminal:
./install/plugins/noitom_mocap/noitom_mocap_plugin

NOITOM_MOCAP_AUTO_LAUNCH=0 \
PYTHONPATH=~/IsaacTeleop/examples/noitom:$PYTHONPATH \
./isaaclab.sh -p scripts/environments/teleoperation/teleop_se3_agent.py \
./isaaclab.sh -p scripts/environments/teleoperation/teleop_se3_agent.py \
--task Isaac-PickPlace-Locomanipulation-G1-Noitom-Abs-v0 \
--visualizer kit \
--xr \
--external_callback noitom_tasks.register_tasks
--external_callback isaacteleop_examples.noitom.noitom_tasks.register_tasks
```

If you run a dedicated CloudXR runtime yourself, source its environment before
Expand Down
29 changes: 29 additions & 0 deletions examples/noitom/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
# The dist name mirrors the import path, so an installed example claims no bare
# top-level name in site-packages.
name = "isaacteleop-examples-noitom"
version = "0.0.0" # Internal example - not versioned
description = "Noitom mocap suit examples: retargeting, Isaac Lab task, MCAP recording"
requires-python = ">=3.11,<3.14"
# isaaclab and gymnasium are deliberately absent: noitom_tasks.py runs inside an
# Isaac Lab environment that already provides them, and neither installs from
# PyPI. Install this example into that interpreter, not a fresh venv.
dependencies = [
"isaacteleop",
"numpy>=1.23.0",
]

# `isaacteleop_examples` is a PEP 420 namespace shared by every example dist and
# must stay without an __init__.py. `only-include` + `sources`, not `packages`:
# the latter keeps only the last path component and would root the wheel at a
# bare top-level `noitom/`.
[tool.hatch.build.targets.wheel]
only-include = ["python/isaacteleop_examples/noitom"]
sources = ["python"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from isaacteleop.schema import BodyJoint

from noitom_retargeting import noitom_position_to_isaac
from .noitom_retargeting import noitom_position_to_isaac

# G1_29DOF_CFG spawn rot (0,0,0.7071,0.7071): pelvis +X aligns with world +Y (table forward).
G1_ROBOT_FORWARD_XY = np.array([0.0, 1.0], dtype=np.float64)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1456,7 +1456,7 @@ def _aligned_skeleton_positions(
settings: NoitomRetargetingSettings,
calib_view: NoitomCalibrationView,
) -> dict[int, np.ndarray]:
from noitom_reference_draw import (
from .noitom_reference_draw import (
ReferenceSkeletonLengths,
aligned_reference_skeleton_from_frame,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@
from isaacteleop.retargeting_engine.tensor_types import DLDataType, NDArrayType
from isaacteleop.teleop_session_manager import PluginConfig

from noitom_retargeting import (
from .noitom_retargeting import (
ArmIkTargets,
NoitomG1Retargeter,
NoitomRetargetingSettings,
noitom_position_to_isaac,
)
from noitom_reference_draw import (
from .noitom_reference_draw import (
ReferenceSkeletonLengths,
aligned_reference_skeleton_from_frame,
)
Expand Down Expand Up @@ -168,7 +168,9 @@ def _noitom_settings_from_env() -> NoitomG1Settings:


def _plugin_search_paths() -> list[Path]:
base = Path(__file__).resolve().parents[2]
# Six levels up is the repo root -- or the install prefix, which has the
# same shape under examples/<name>/python/isaacteleop_examples/<name>/.
base = Path(__file__).resolve().parents[5]
candidates = [
base / "plugins",
base / "install" / "plugins",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@


def _plugin_search_paths() -> list[Path]:
base = Path(__file__).resolve().parents[2]
# Six levels up is the repo root -- or the install prefix, which has the
# same shape under examples/<name>/python/isaacteleop_examples/<name>/.
base = Path(__file__).resolve().parents[5]
candidates = [
base / "plugins",
base / "install" / "plugins",
Expand Down Expand Up @@ -70,7 +72,9 @@ def _resolve_output(path_arg: str | None) -> Path:
if path_arg:
path = Path(path_arg)
else:
out_dir = Path(__file__).resolve().parent / "recordings"
# CWD-relative, matching mcap_record_replay: anchoring to __file__ would
# write inside the package, and into site-packages once installed.
out_dir = Path.cwd() / "recordings"
path = out_dir / f"noitom_full_body_{datetime.now():%Y%m%d_%H%M%S}.mcap"
path.parent.mkdir(parents=True, exist_ok=True)
return path
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/noitom_mocap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ Record through the Noitom example wrapper and replay with the shared MCAP
script:

```bash
uv run python examples/noitom/record_noitom_full_body.py \
uv pip install -e ./examples/noitom
python -m isaacteleop_examples.noitom.record_noitom_full_body \
10 examples/noitom/recordings/noitom_full_body.mcap

uv pip install -e ./examples/mcap_record_replay
Expand Down
Loading