From a21b0be8ba24f477b8e837d8af2ea795e6c5d0bd Mon Sep 17 00:00:00 2001 From: Jiwen Cai Date: Fri, 28 Aug 2026 04:53:46 +0000 Subject: [PATCH 1/4] examples(oxr): package under isaaccapture_examples Package the OpenXR examples while preserving the GPU test runner paths and plugin discovery. Part of NVIDIA/IsaacCapture#985. Signed-off-by: Jiwen Cai [Ivan Yang: rebased onto main past #1348, which moved body_tracking.rst, oak.rst and trackers.rst into subdirectories and left redirect stubs. The stubs keep main's text; the same path edits are applied to body_tracking/index.rst, cameras/oak.rst and trackers/index.rst. python/README.md is deleted as in the original; the prerequisites link that #1361 added to it is already in examples/oxr/README.md.] Signed-off-by: Ivan Yang --- docs/source/device/body_tracking/index.rst | 2 +- docs/source/device/cameras/oak.rst | 2 +- docs/source/device/trackers/index.rst | 14 +-- examples/oxr/CMakeLists.txt | 4 +- examples/oxr/README.md | 35 +++--- examples/oxr/pyproject.toml | 26 +++++ examples/oxr/python/README.md | 100 ------------------ .../isaaccapture_examples/oxr/__init__.py | 2 + .../oxr}/modular_example.py | 0 .../oxr}/modular_example_with_mcap.py | 0 .../oxr}/test_controller_tracker.py | 0 .../oxr}/test_extensions.py | 0 .../oxr}/test_full_body_tracker.py | 0 .../oxr}/test_hand_inactive_on_plugin_stop.py | 4 +- .../oxr}/test_modular.py | 0 .../oxr}/test_oak_camera.py | 4 +- .../oxr}/test_package_version.py | 2 +- .../oxr}/test_session_sharing.py | 0 .../oxr}/test_synthetic_hands.py | 4 +- examples/oxr/python/pyproject.toml | 12 --- scripts/run_tests_with_cloudxr.sh | 8 +- src/plugins/oak/README.md | 2 +- tests/AGENTS.md | 2 +- 23 files changed, 69 insertions(+), 154 deletions(-) create mode 100644 examples/oxr/pyproject.toml delete mode 100644 examples/oxr/python/README.md create mode 100644 examples/oxr/python/isaaccapture_examples/oxr/__init__.py rename examples/oxr/python/{ => isaaccapture_examples/oxr}/modular_example.py (100%) rename examples/oxr/python/{ => isaaccapture_examples/oxr}/modular_example_with_mcap.py (100%) rename examples/oxr/python/{ => isaaccapture_examples/oxr}/test_controller_tracker.py (100%) rename examples/oxr/python/{ => isaaccapture_examples/oxr}/test_extensions.py (100%) rename examples/oxr/python/{ => isaaccapture_examples/oxr}/test_full_body_tracker.py (100%) rename examples/oxr/python/{ => isaaccapture_examples/oxr}/test_hand_inactive_on_plugin_stop.py (96%) rename examples/oxr/python/{ => isaaccapture_examples/oxr}/test_modular.py (100%) rename examples/oxr/python/{ => isaaccapture_examples/oxr}/test_oak_camera.py (98%) rename examples/oxr/python/{ => isaaccapture_examples/oxr}/test_package_version.py (89%) rename examples/oxr/python/{ => isaaccapture_examples/oxr}/test_session_sharing.py (100%) rename examples/oxr/python/{ => isaaccapture_examples/oxr}/test_synthetic_hands.py (96%) delete mode 100644 examples/oxr/python/pyproject.toml diff --git a/docs/source/device/body_tracking/index.rst b/docs/source/device/body_tracking/index.rst index a041f264a9..ccb86eed26 100644 --- a/docs/source/device/body_tracking/index.rst +++ b/docs/source/device/body_tracking/index.rst @@ -203,7 +203,7 @@ joint ``is_valid`` flags to determine which joints have valid poses. For a minimal C++ reader see ``examples/schemaio/full_body_printer.cpp``, which creates the tracker, queries the required OpenXR extensions, and prints the joint data each frame through ``DeviceIOSession``. The Python equivalent is -``examples/oxr/python/test_full_body_tracker.py``. Neither embeds +``examples/oxr/python/isaaccapture_examples/oxr/test_full_body_tracker.py``. Neither embeds ``CloudXRLauncher``, so start the runtime first and source its environment in the shell that launches them: diff --git a/docs/source/device/cameras/oak.rst b/docs/source/device/cameras/oak.rst index f2047989c3..ae72a05348 100644 --- a/docs/source/device/cameras/oak.rst +++ b/docs/source/device/cameras/oak.rst @@ -172,7 +172,7 @@ controllers, etc.): while True: session.step() # metadata is recorded each update -See also :code-file:`examples/oxr/python/test_oak_camera.py` (``--mode +See also :code-file:`examples/oxr/python/isaaccapture_examples/oxr/test_oak_camera.py` (``--mode schema-pusher``) for a standalone PluginManager + DeviceIOSession example of the same SchemaPusher flow. diff --git a/docs/source/device/trackers/index.rst b/docs/source/device/trackers/index.rst index 0ef313247a..f0edfb2ad4 100644 --- a/docs/source/device/trackers/index.rst +++ b/docs/source/device/trackers/index.rst @@ -209,7 +209,7 @@ Tracks the HMD head pose via the OpenXR view space. - Examples: - :code-file:`examples/oxr/cpp/oxr_simple_api_demo.cpp` - - :code-file:`examples/oxr/python/modular_example.py` + - :code-file:`examples/oxr/python/isaaccapture_examples/oxr/modular_example.py` HandTracker ~~~~~~~~~~~ @@ -225,12 +225,12 @@ Tracks articulated hand joints (26 joints per hand, following the OpenXR - :code-file:`tests/cpp/core/schema/test_hand.cpp` - :code-file:`tests/python/core/schema/test_hand.py` - - :code-file:`examples/oxr/python/test_synthetic_hands.py` + - :code-file:`examples/oxr/python/isaaccapture_examples/oxr/test_synthetic_hands.py` - Examples: - :code-file:`examples/oxr/cpp/oxr_simple_api_demo.cpp` - - :code-file:`examples/oxr/python/modular_example.py` + - :code-file:`examples/oxr/python/isaaccapture_examples/oxr/modular_example.py` - :code-file:`examples/retargeting/python/isaaccapture_examples/retargeting/sources_example.py` ControllerTracker @@ -247,7 +247,7 @@ axis inputs. Uses standard OpenXR action bindings. - :code-file:`tests/cpp/core/schema/test_controller.cpp` - :code-file:`tests/python/core/schema/test_controller.py` - - :code-file:`examples/oxr/python/test_controller_tracker.py` + - :code-file:`examples/oxr/python/isaaccapture_examples/oxr/test_controller_tracker.py` - Examples: @@ -273,7 +273,7 @@ reads the PICO ``XR_BD_body_tracking`` extension directly. - :code-file:`tests/cpp/core/schema/test_full_body.cpp` - :code-file:`tests/python/core/schema/test_full_body.py` - - :code-file:`examples/oxr/python/test_full_body_tracker.py` + - :code-file:`examples/oxr/python/isaaccapture_examples/oxr/test_full_body_tracker.py` - Examples: @@ -409,7 +409,7 @@ utility internally. - :code-file:`tests/cpp/core/schema/test_oak.cpp` - :code-file:`tests/python/core/schema/test_camera.py` - - :code-file:`examples/oxr/python/test_oak_camera.py` + - :code-file:`examples/oxr/python/isaaccapture_examples/oxr/test_oak_camera.py` - Examples: @@ -500,7 +500,7 @@ Usage Examples For end-to-end usage patterns combining trackers with a ``DeviceIOSession``, see: - **C++**: :code-file:`examples/oxr/cpp/oxr_simple_api_demo.cpp` -- **Python**: :code-file:`examples/oxr/python/modular_example.py` +- **Python**: :code-file:`examples/oxr/python/isaaccapture_examples/oxr/modular_example.py` For higher-level usage with the teleop session manager and retargeting, see: diff --git a/examples/oxr/CMakeLists.txt b/examples/oxr/CMakeLists.txt index 6dcfb26495..1becdfcf51 100644 --- a/examples/oxr/CMakeLists.txt +++ b/examples/oxr/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 cmake_minimum_required(VERSION 3.20) @@ -7,4 +7,4 @@ cmake_minimum_required(VERSION 3.20) add_subdirectory(cpp) include(${CMAKE_SOURCE_DIR}/cmake/InstallPythonExample.cmake) -install_python_example(DESTINATION examples/oxr/python) +install_python_example(DESTINATION examples/oxr) diff --git a/examples/oxr/README.md b/examples/oxr/README.md index f09146c916..f2c205ed65 100644 --- a/examples/oxr/README.md +++ b/examples/oxr/README.md @@ -31,8 +31,8 @@ examples/oxr/ │ ├── CMakeLists.txt │ ├── oxr_session_sharing.cpp │ └── oxr_simple_api_demo.cpp -└── python/ # Python examples - ├── pyproject.toml # uv configuration +├── pyproject.toml # the distribution +└── python/isaaccapture_examples/oxr/ ├── modular_example.py ├── test_modular.py ├── test_extensions.py @@ -41,34 +41,25 @@ examples/oxr/ ## Python Examples -Python examples use `uv` for dependency management and execution. - ### Running Python Examples -After building and installing, navigate to the installed examples: +Install the example once, then run any module from anywhere: ```bash -# From install directory (recommended) -cd install/examples/oxr/python +uv pip install -e ./examples/oxr export XR_RUNTIME_JSON=/path/to/cloudxr/openxr_cloudxr-dev.json -uv run test_modular.py +python -m isaaccapture_examples.oxr.test_modular ``` -Or run from source directory: - -```bash -# From source directory (requires build to be complete) -cd examples/oxr/python -export XR_RUNTIME_JSON=/path/to/cloudxr/openxr_cloudxr-dev.json -uv run test_modular.py -``` +The installed tree works the same way — `uv pip install -e +install/examples/oxr` resolves against the wheel that build produced. ### Available Python Examples -- **modular_example.py** - Basic hand + head tracking -- **test_modular.py** - Complete API test -- **test_extensions.py** - Extension query demonstration -- **test_session_sharing.py** - Session sharing between DeviceIOSession instances +- **modular_example** - Basic hand + head tracking +- **test_modular** - Complete API test +- **test_extensions** - Extension query demonstration +- **test_session_sharing** - Session sharing between DeviceIOSession instances ## C++ Examples @@ -110,9 +101,9 @@ cmake --build build cmake --install build # Run Python example -cd install/examples/oxr/python +uv pip install -e install/examples/oxr export XR_RUNTIME_JSON=/path/to/cloudxr/openxr_cloudxr-dev.json -uv run test_modular.py +python -m isaaccapture_examples.oxr.test_modular ``` ### C++ Example diff --git a/examples/oxr/pyproject.toml b/examples/oxr/pyproject.toml new file mode 100644 index 0000000000..cf42d8ed78 --- /dev/null +++ b/examples/oxr/pyproject.toml @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-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 = "isaaccapture-examples-oxr" +version = "0.0.0" # Internal examples - not versioned +description = "OpenXR Tracking Python Examples" +requires-python = ">=3.11,<3.14" +dependencies = [ + "isaaccapture", + "numpy>=1.23.0", +] + +# `isaaccapture_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 `oxr/`. +[tool.hatch.build.targets.wheel] +only-include = ["python/isaaccapture_examples/oxr"] +sources = ["python"] diff --git a/examples/oxr/python/README.md b/examples/oxr/python/README.md deleted file mode 100644 index 5ff2ca5f07..0000000000 --- a/examples/oxr/python/README.md +++ /dev/null @@ -1,100 +0,0 @@ - - -# OpenXR Python Examples - -Python examples demonstrating the OpenXR tracking API using the `isaaccapture` Python wheel. - -## Prerequisites - -1. **Build and install the project** from the project root, after the - [source-build prerequisites](https://nvidia.github.io/IsaacCapture/main/development/build_from_source/index.html): - ```bash - cd ../../.. - cmake -B build - cmake --build build - cmake --install build - ``` - This creates the Python wheel in `build/wheels/` and installs it to `install/wheels/`. - -2. **Install uv** (if not already installed): - ```bash - curl -LsSf https://astral.sh/uv/install.sh | sh - ``` - -## Running Examples - -### From Install Directory (Recommended) - -After building and installing, run examples from the install directory: - -```bash -cd ../../../install/examples/oxr/python -export XR_RUNTIME_JSON=/path/to/your/openxr_runtime.json -uv run test_modular.py -``` - -### From Source Directory - -You can also run from the source directory: - -```bash -export XR_RUNTIME_JSON=/path/to/your/openxr_runtime.json -uv run test_modular.py -``` - -The `pyproject.toml` configuration automatically locates the built wheel. - -## Available Examples - -### modular_example.py -Basic demonstration of hand and head tracking. - -```bash -uv run modular_example.py -``` - -### test_modular.py -Comprehensive test of the modular tracking API. - -```bash -uv run test_modular.py -``` - -### test_extensions.py -Demonstrates querying available OpenXR extensions. - -```bash -uv run test_extensions.py -``` - -### test_session_sharing.py -Shows how to share sessions between multiple DeviceIOSession instances. - -```bash -uv run test_session_sharing.py -``` - -## Troubleshooting - -### Wheel not found -If you get an error about the wheel not being found, build the project: -```bash -cd ../../.. -cmake -B build -cmake --build build -``` - -### uv not installed -```bash -curl -LsSf https://astral.sh/uv/install.sh | sh -# Then restart your shell -``` - -### OpenXR runtime errors -Set the XR_RUNTIME_JSON environment variable: -```bash -export XR_RUNTIME_JSON=/path/to/your/openxr_runtime.json -``` diff --git a/examples/oxr/python/isaaccapture_examples/oxr/__init__.py b/examples/oxr/python/isaaccapture_examples/oxr/__init__.py new file mode 100644 index 0000000000..52a7a9daf0 --- /dev/null +++ b/examples/oxr/python/isaaccapture_examples/oxr/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/examples/oxr/python/modular_example.py b/examples/oxr/python/isaaccapture_examples/oxr/modular_example.py similarity index 100% rename from examples/oxr/python/modular_example.py rename to examples/oxr/python/isaaccapture_examples/oxr/modular_example.py diff --git a/examples/oxr/python/modular_example_with_mcap.py b/examples/oxr/python/isaaccapture_examples/oxr/modular_example_with_mcap.py similarity index 100% rename from examples/oxr/python/modular_example_with_mcap.py rename to examples/oxr/python/isaaccapture_examples/oxr/modular_example_with_mcap.py diff --git a/examples/oxr/python/test_controller_tracker.py b/examples/oxr/python/isaaccapture_examples/oxr/test_controller_tracker.py similarity index 100% rename from examples/oxr/python/test_controller_tracker.py rename to examples/oxr/python/isaaccapture_examples/oxr/test_controller_tracker.py diff --git a/examples/oxr/python/test_extensions.py b/examples/oxr/python/isaaccapture_examples/oxr/test_extensions.py similarity index 100% rename from examples/oxr/python/test_extensions.py rename to examples/oxr/python/isaaccapture_examples/oxr/test_extensions.py diff --git a/examples/oxr/python/test_full_body_tracker.py b/examples/oxr/python/isaaccapture_examples/oxr/test_full_body_tracker.py similarity index 100% rename from examples/oxr/python/test_full_body_tracker.py rename to examples/oxr/python/isaaccapture_examples/oxr/test_full_body_tracker.py diff --git a/examples/oxr/python/test_hand_inactive_on_plugin_stop.py b/examples/oxr/python/isaaccapture_examples/oxr/test_hand_inactive_on_plugin_stop.py similarity index 96% rename from examples/oxr/python/test_hand_inactive_on_plugin_stop.py rename to examples/oxr/python/isaaccapture_examples/oxr/test_hand_inactive_on_plugin_stop.py index 7fadcdc1dd..b6d644fbf8 100644 --- a/examples/oxr/python/test_hand_inactive_on_plugin_stop.py +++ b/examples/oxr/python/isaaccapture_examples/oxr/test_hand_inactive_on_plugin_stop.py @@ -20,7 +20,9 @@ import isaaccapture.oxr as oxr import isaaccapture.plugin_manager as pm -PLUGIN_ROOT_DIR = Path(__file__).resolve().parent.parent.parent.parent / "plugins" +# Six levels up is the tree root -- the checkout, or the install prefix +# when running from install/examples/. +PLUGIN_ROOT_DIR = Path(__file__).resolve().parents[5] / "plugins" ACTIVE_WAIT_S = 15.0 # max time to wait for hands to become active INACTIVE_WAIT_S = 5.0 # max time to wait for hands to become inactive after stop diff --git a/examples/oxr/python/test_modular.py b/examples/oxr/python/isaaccapture_examples/oxr/test_modular.py similarity index 100% rename from examples/oxr/python/test_modular.py rename to examples/oxr/python/isaaccapture_examples/oxr/test_modular.py diff --git a/examples/oxr/python/test_oak_camera.py b/examples/oxr/python/isaaccapture_examples/oxr/test_oak_camera.py similarity index 98% rename from examples/oxr/python/test_oak_camera.py rename to examples/oxr/python/isaaccapture_examples/oxr/test_oak_camera.py index 4fac39e6af..b10efc30c5 100755 --- a/examples/oxr/python/test_oak_camera.py +++ b/examples/oxr/python/isaaccapture_examples/oxr/test_oak_camera.py @@ -26,7 +26,9 @@ import isaaccapture.deviceio as deviceio import isaaccapture.oxr as oxr -PLUGIN_ROOT_DIR = Path(__file__).resolve().parent.parent.parent.parent / "plugins" +# Six levels up is the tree root -- the checkout, or the install prefix +# when running from install/examples/. +PLUGIN_ROOT_DIR = Path(__file__).resolve().parents[5] / "plugins" MODE_NO_METADATA = "no-metadata" MODE_SCHEMA_PUSHER = "schema-pusher" diff --git a/examples/oxr/python/test_package_version.py b/examples/oxr/python/isaaccapture_examples/oxr/test_package_version.py similarity index 89% rename from examples/oxr/python/test_package_version.py rename to examples/oxr/python/isaaccapture_examples/oxr/test_package_version.py index 13d3e875a5..6e9197b82c 100755 --- a/examples/oxr/python/test_package_version.py +++ b/examples/oxr/python/isaaccapture_examples/oxr/test_package_version.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Validate isaaccapture module version reporting against CI-provided expected version.""" diff --git a/examples/oxr/python/test_session_sharing.py b/examples/oxr/python/isaaccapture_examples/oxr/test_session_sharing.py similarity index 100% rename from examples/oxr/python/test_session_sharing.py rename to examples/oxr/python/isaaccapture_examples/oxr/test_session_sharing.py diff --git a/examples/oxr/python/test_synthetic_hands.py b/examples/oxr/python/isaaccapture_examples/oxr/test_synthetic_hands.py similarity index 96% rename from examples/oxr/python/test_synthetic_hands.py rename to examples/oxr/python/isaaccapture_examples/oxr/test_synthetic_hands.py index 9c5f07fe66..1521702209 100644 --- a/examples/oxr/python/test_synthetic_hands.py +++ b/examples/oxr/python/isaaccapture_examples/oxr/test_synthetic_hands.py @@ -25,7 +25,9 @@ # The test will look for plugins in the install directory relative to this script # This script is in install/examples/oxr/python # Plugins are in install/plugins -PLUGIN_ROOT_DIR = Path(__file__).resolve().parent.parent.parent.parent / "plugins" +# Six levels up is the tree root -- the checkout, or the install prefix +# when running from install/examples/. +PLUGIN_ROOT_DIR = Path(__file__).resolve().parents[5] / "plugins" def run_test(): diff --git a/examples/oxr/python/pyproject.toml b/examples/oxr/python/pyproject.toml deleted file mode 100644 index e5fb2720da..0000000000 --- a/examples/oxr/python/pyproject.toml +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -[project] -name = "oxr-examples" -version = "0.0.0" # Internal examples - not versioned -description = "OpenXR Tracking Python Examples" -requires-python = ">=3.11,<3.14" -dependencies = [ - "isaaccapture", - "numpy>=1.23.0", -] diff --git a/scripts/run_tests_with_cloudxr.sh b/scripts/run_tests_with_cloudxr.sh index a99b870bd5..97ebe7f9d3 100755 --- a/scripts/run_tests_with_cloudxr.sh +++ b/scripts/run_tests_with_cloudxr.sh @@ -24,10 +24,12 @@ set -euo pipefail # the ctest configuration in `build-ubuntu.yml` instead, so that they can be # run on a runner without GPU support immediately after the build. #============================================================================== +# Paths are relative to /app/tests in the test image, which is a copy of +# examples/oxr/python/ -- so they carry the namespace prefix. CXR_PYTHON_GPU_TESTS=( - "test_package_version.py" - "test_extensions.py" - "test_modular.py" + "isaaccapture_examples/oxr/test_package_version.py" + "isaaccapture_examples/oxr/test_extensions.py" + "isaaccapture_examples/oxr/test_modular.py" ) CXR_NATIVE_GPU_TESTS=( diff --git a/src/plugins/oak/README.md b/src/plugins/oak/README.md index ff1eb17ee6..7133f2a47b 100644 --- a/src/plugins/oak/README.md +++ b/src/plugins/oak/README.md @@ -147,7 +147,7 @@ with TeleopSession(config) as session: session.step() # metadata is recorded each update ``` -See also `examples/oxr/python/test_oak_camera.py --mode schema-pusher` for a +See also `examples/oxr/python/isaaccapture_examples/oxr/test_oak_camera.py --mode schema-pusher` for a standalone PluginManager + DeviceIOSession example of the same SchemaPusher flow. diff --git a/tests/AGENTS.md b/tests/AGENTS.md index 5d1ea1a328..3c114d236e 100644 --- a/tests/AGENTS.md +++ b/tests/AGENTS.md @@ -77,6 +77,6 @@ Shared C++ fixtures (not executables) live under `tests/cpp/viz/support/` - Vehicle teleop worker tests belong in `examples/vehicle_teleop/tests/`, alongside the example they exercise. - `src/core/codegen/test_*.py` — unittest, co-located with the generator. -- `examples/oxr/python/test_*.py` — standalone scripts for CloudXR GPU CI. +- `examples/oxr/python/isaaccapture_examples/oxr/test_*.py` — standalone scripts for CloudXR GPU CI. - ROS2 Docker integration helpers under `examples/teleop_ros2/.../integration_tests/`. - `scripts/sbom/tests/` — the SBOM collector's tests, run from its own uv project. From d5576d0ff2b5ee6051f44626d7316f06323d1ef1 Mon Sep 17 00:00:00 2001 From: Ivan Yang Date: Tue, 27 Oct 2026 21:21:27 -0700 Subject: [PATCH 2/4] examples(oxr): move the CloudXR GPU test scripts to tests/ The test_*.py scripts are CI checks, not example code, so they move to tests/python/xr_runtime/ instead of shipping in the example package. The package keeps modular_example and modular_example_with_mcap. - deps/cloudxr/Dockerfile.test copies tests/python/xr_runtime/ to /app/tests, so run_tests_with_cloudxr.sh lists the scripts by bare name again and the image's default command still finds test_extensions.py. - The synthetic-hands, hand-inactive and OAK scripts find plugins through install_tree.plugins_dir(): the nearest install/plugins above the script, which is /install/plugins or /app/install/plugins in the test image. They are no longer installed beside install/plugins. - Docs and the OAK plugin README link the scripts at their new path. - The example depends on isaaccapture[cloudxr], which both modules import for CloudXRLauncher, and no longer on numpy, which none imports. Its README lists the two modules and says to install isaaccapture first. - tests/AGENTS.md says what the directory is and who runs it. Signed-off-by: Ivan Yang --- deps/cloudxr/Dockerfile.test | 4 +-- docs/source/device/body_tracking/index.rst | 2 +- docs/source/device/cameras/oak.rst | 2 +- docs/source/device/trackers/index.rst | 8 +++--- examples/oxr/README.md | 27 ++++++++++--------- examples/oxr/pyproject.toml | 9 +++---- scripts/run_tests_with_cloudxr.sh | 9 +++---- src/plugins/oak/README.md | 2 +- tests/AGENTS.md | 4 ++- tests/python/xr_runtime/install_tree.py | 19 +++++++++++++ .../xr_runtime}/test_controller_tracker.py | 0 .../python/xr_runtime}/test_extensions.py | 0 .../xr_runtime}/test_full_body_tracker.py | 0 .../test_hand_inactive_on_plugin_stop.py | 6 ++--- .../python/xr_runtime}/test_modular.py | 0 .../python/xr_runtime}/test_oak_camera.py | 5 ++-- .../xr_runtime}/test_package_version.py | 0 .../xr_runtime}/test_session_sharing.py | 0 .../xr_runtime}/test_synthetic_hands.py | 10 ++----- 19 files changed, 58 insertions(+), 49 deletions(-) create mode 100644 tests/python/xr_runtime/install_tree.py rename {examples/oxr/python/isaaccapture_examples/oxr => tests/python/xr_runtime}/test_controller_tracker.py (100%) rename {examples/oxr/python/isaaccapture_examples/oxr => tests/python/xr_runtime}/test_extensions.py (100%) rename {examples/oxr/python/isaaccapture_examples/oxr => tests/python/xr_runtime}/test_full_body_tracker.py (100%) rename {examples/oxr/python/isaaccapture_examples/oxr => tests/python/xr_runtime}/test_hand_inactive_on_plugin_stop.py (96%) rename {examples/oxr/python/isaaccapture_examples/oxr => tests/python/xr_runtime}/test_modular.py (100%) rename {examples/oxr/python/isaaccapture_examples/oxr => tests/python/xr_runtime}/test_oak_camera.py (98%) rename {examples/oxr/python/isaaccapture_examples/oxr => tests/python/xr_runtime}/test_package_version.py (100%) rename {examples/oxr/python/isaaccapture_examples/oxr => tests/python/xr_runtime}/test_session_sharing.py (100%) rename {examples/oxr/python/isaaccapture_examples/oxr => tests/python/xr_runtime}/test_synthetic_hands.py (91%) diff --git a/deps/cloudxr/Dockerfile.test b/deps/cloudxr/Dockerfile.test index a63dc15f1e..9fbdb5eb3f 100644 --- a/deps/cloudxr/Dockerfile.test +++ b/deps/cloudxr/Dockerfile.test @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # Test container for Isaac Teleop tests (Python and native C++) @@ -25,7 +25,7 @@ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv COPY install/ /app/install/ # Copy test files -COPY examples/oxr/python/ /app/tests/ +COPY tests/python/xr_runtime/ /app/tests/ # Keep the exact CI wheel and its transition dependency available in one install. WORKDIR /app/tests diff --git a/docs/source/device/body_tracking/index.rst b/docs/source/device/body_tracking/index.rst index ccb86eed26..13c0696b85 100644 --- a/docs/source/device/body_tracking/index.rst +++ b/docs/source/device/body_tracking/index.rst @@ -203,7 +203,7 @@ joint ``is_valid`` flags to determine which joints have valid poses. For a minimal C++ reader see ``examples/schemaio/full_body_printer.cpp``, which creates the tracker, queries the required OpenXR extensions, and prints the joint data each frame through ``DeviceIOSession``. The Python equivalent is -``examples/oxr/python/isaaccapture_examples/oxr/test_full_body_tracker.py``. Neither embeds +``tests/python/xr_runtime/test_full_body_tracker.py``. Neither embeds ``CloudXRLauncher``, so start the runtime first and source its environment in the shell that launches them: diff --git a/docs/source/device/cameras/oak.rst b/docs/source/device/cameras/oak.rst index ae72a05348..e584f7245e 100644 --- a/docs/source/device/cameras/oak.rst +++ b/docs/source/device/cameras/oak.rst @@ -172,7 +172,7 @@ controllers, etc.): while True: session.step() # metadata is recorded each update -See also :code-file:`examples/oxr/python/isaaccapture_examples/oxr/test_oak_camera.py` (``--mode +See also :code-file:`tests/python/xr_runtime/test_oak_camera.py` (``--mode schema-pusher``) for a standalone PluginManager + DeviceIOSession example of the same SchemaPusher flow. diff --git a/docs/source/device/trackers/index.rst b/docs/source/device/trackers/index.rst index f0edfb2ad4..bee082b049 100644 --- a/docs/source/device/trackers/index.rst +++ b/docs/source/device/trackers/index.rst @@ -225,7 +225,7 @@ Tracks articulated hand joints (26 joints per hand, following the OpenXR - :code-file:`tests/cpp/core/schema/test_hand.cpp` - :code-file:`tests/python/core/schema/test_hand.py` - - :code-file:`examples/oxr/python/isaaccapture_examples/oxr/test_synthetic_hands.py` + - :code-file:`tests/python/xr_runtime/test_synthetic_hands.py` - Examples: @@ -247,7 +247,7 @@ axis inputs. Uses standard OpenXR action bindings. - :code-file:`tests/cpp/core/schema/test_controller.cpp` - :code-file:`tests/python/core/schema/test_controller.py` - - :code-file:`examples/oxr/python/isaaccapture_examples/oxr/test_controller_tracker.py` + - :code-file:`tests/python/xr_runtime/test_controller_tracker.py` - Examples: @@ -273,7 +273,7 @@ reads the PICO ``XR_BD_body_tracking`` extension directly. - :code-file:`tests/cpp/core/schema/test_full_body.cpp` - :code-file:`tests/python/core/schema/test_full_body.py` - - :code-file:`examples/oxr/python/isaaccapture_examples/oxr/test_full_body_tracker.py` + - :code-file:`tests/python/xr_runtime/test_full_body_tracker.py` - Examples: @@ -409,7 +409,7 @@ utility internally. - :code-file:`tests/cpp/core/schema/test_oak.cpp` - :code-file:`tests/python/core/schema/test_camera.py` - - :code-file:`examples/oxr/python/isaaccapture_examples/oxr/test_oak_camera.py` + - :code-file:`tests/python/xr_runtime/test_oak_camera.py` - Examples: diff --git a/examples/oxr/README.md b/examples/oxr/README.md index f2c205ed65..7b59617e40 100644 --- a/examples/oxr/README.md +++ b/examples/oxr/README.md @@ -33,33 +33,33 @@ examples/oxr/ │ └── oxr_simple_api_demo.cpp ├── pyproject.toml # the distribution └── python/isaaccapture_examples/oxr/ + ├── __init__.py ├── modular_example.py - ├── test_modular.py - ├── test_extensions.py - └── test_session_sharing.py + └── modular_example_with_mcap.py ``` +The `test_*.py` scripts that CloudXR GPU CI runs against this API are in +[`tests/python/xr_runtime/`](../../tests/python/xr_runtime/). + ## Python Examples ### Running Python Examples -Install the example once, then run any module from anywhere: +Install `isaaccapture` built from this tree into the active environment +first, for example +`uv pip install "isaaccapture[cloudxr]" --find-links=./install/wheels/`. +Then install the example once and run a module from anywhere: ```bash uv pip install -e ./examples/oxr export XR_RUNTIME_JSON=/path/to/cloudxr/openxr_cloudxr-dev.json -python -m isaaccapture_examples.oxr.test_modular +python -m isaaccapture_examples.oxr.modular_example ``` -The installed tree works the same way — `uv pip install -e -install/examples/oxr` resolves against the wheel that build produced. - ### Available Python Examples - **modular_example** - Basic hand + head tracking -- **test_modular** - Complete API test -- **test_extensions** - Extension query demonstration -- **test_session_sharing** - Session sharing between DeviceIOSession instances +- **modular_example_with_mcap** - The same trackers recorded to an MCAP file, then replayed ## C++ Examples @@ -101,9 +101,10 @@ cmake --build build cmake --install build # Run Python example -uv pip install -e install/examples/oxr +uv pip install "isaaccapture[cloudxr]" --find-links=./install/wheels/ +uv pip install -e ./examples/oxr export XR_RUNTIME_JSON=/path/to/cloudxr/openxr_cloudxr-dev.json -python -m isaaccapture_examples.oxr.test_modular +python -m isaaccapture_examples.oxr.modular_example ``` ### C++ Example diff --git a/examples/oxr/pyproject.toml b/examples/oxr/pyproject.toml index cf42d8ed78..a000590c7a 100644 --- a/examples/oxr/pyproject.toml +++ b/examples/oxr/pyproject.toml @@ -13,14 +13,11 @@ version = "0.0.0" # Internal examples - not versioned description = "OpenXR Tracking Python Examples" requires-python = ">=3.11,<3.14" dependencies = [ - "isaaccapture", - "numpy>=1.23.0", + "isaaccapture[cloudxr]", ] -# `isaaccapture_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 `oxr/`. +# `only-include` + `sources`, not `packages`, and no __init__.py above +# oxr -- see the packaging rules in examples/README.md. [tool.hatch.build.targets.wheel] only-include = ["python/isaaccapture_examples/oxr"] sources = ["python"] diff --git a/scripts/run_tests_with_cloudxr.sh b/scripts/run_tests_with_cloudxr.sh index 97ebe7f9d3..19505b429c 100755 --- a/scripts/run_tests_with_cloudxr.sh +++ b/scripts/run_tests_with_cloudxr.sh @@ -24,12 +24,11 @@ set -euo pipefail # the ctest configuration in `build-ubuntu.yml` instead, so that they can be # run on a runner without GPU support immediately after the build. #============================================================================== -# Paths are relative to /app/tests in the test image, which is a copy of -# examples/oxr/python/ -- so they carry the namespace prefix. +# Scripts in tests/python/xr_runtime/, which the test image copies to /app/tests. CXR_PYTHON_GPU_TESTS=( - "isaaccapture_examples/oxr/test_package_version.py" - "isaaccapture_examples/oxr/test_extensions.py" - "isaaccapture_examples/oxr/test_modular.py" + "test_package_version.py" + "test_extensions.py" + "test_modular.py" ) CXR_NATIVE_GPU_TESTS=( diff --git a/src/plugins/oak/README.md b/src/plugins/oak/README.md index 7133f2a47b..49e9bb3ad7 100644 --- a/src/plugins/oak/README.md +++ b/src/plugins/oak/README.md @@ -147,7 +147,7 @@ with TeleopSession(config) as session: session.step() # metadata is recorded each update ``` -See also `examples/oxr/python/isaaccapture_examples/oxr/test_oak_camera.py --mode schema-pusher` for a +See also `tests/python/xr_runtime/test_oak_camera.py --mode schema-pusher` for a standalone PluginManager + DeviceIOSession example of the same SchemaPusher flow. diff --git a/tests/AGENTS.md b/tests/AGENTS.md index 3c114d236e..745a132d71 100644 --- a/tests/AGENTS.md +++ b/tests/AGENTS.md @@ -77,6 +77,8 @@ Shared C++ fixtures (not executables) live under `tests/cpp/viz/support/` - Vehicle teleop worker tests belong in `examples/vehicle_teleop/tests/`, alongside the example they exercise. - `src/core/codegen/test_*.py` — unittest, co-located with the generator. -- `examples/oxr/python/isaaccapture_examples/oxr/test_*.py` — standalone scripts for CloudXR GPU CI. +- `python/xr_runtime/` — standalone scripts for CloudXR GPU CI, not pytest: no + `pyproject.toml` or CTest wiring. `scripts/run_tests_with_cloudxr.sh` lists the + ones CI runs; the test image copies the directory flat to `/app/tests`. - ROS2 Docker integration helpers under `examples/teleop_ros2/.../integration_tests/`. - `scripts/sbom/tests/` — the SBOM collector's tests, run from its own uv project. diff --git a/tests/python/xr_runtime/install_tree.py b/tests/python/xr_runtime/install_tree.py new file mode 100644 index 0000000000..c6a038e22d --- /dev/null +++ b/tests/python/xr_runtime/install_tree.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Locate the install tree that the plugin scripts here run against.""" + +from pathlib import Path + + +def plugins_dir() -> Path: + """Return the nearest ancestor's ``install/plugins``, else that relative path. + + That is ``/install/plugins`` after ``cmake --install build``, and + ``/app/install/plugins`` in the CloudXR test image. + """ + for parent in Path(__file__).resolve().parents: + candidate = parent / "install" / "plugins" + if candidate.is_dir(): + return candidate + return Path("install/plugins") diff --git a/examples/oxr/python/isaaccapture_examples/oxr/test_controller_tracker.py b/tests/python/xr_runtime/test_controller_tracker.py similarity index 100% rename from examples/oxr/python/isaaccapture_examples/oxr/test_controller_tracker.py rename to tests/python/xr_runtime/test_controller_tracker.py diff --git a/examples/oxr/python/isaaccapture_examples/oxr/test_extensions.py b/tests/python/xr_runtime/test_extensions.py similarity index 100% rename from examples/oxr/python/isaaccapture_examples/oxr/test_extensions.py rename to tests/python/xr_runtime/test_extensions.py diff --git a/examples/oxr/python/isaaccapture_examples/oxr/test_full_body_tracker.py b/tests/python/xr_runtime/test_full_body_tracker.py similarity index 100% rename from examples/oxr/python/isaaccapture_examples/oxr/test_full_body_tracker.py rename to tests/python/xr_runtime/test_full_body_tracker.py diff --git a/examples/oxr/python/isaaccapture_examples/oxr/test_hand_inactive_on_plugin_stop.py b/tests/python/xr_runtime/test_hand_inactive_on_plugin_stop.py similarity index 96% rename from examples/oxr/python/isaaccapture_examples/oxr/test_hand_inactive_on_plugin_stop.py rename to tests/python/xr_runtime/test_hand_inactive_on_plugin_stop.py index b6d644fbf8..180c3db2f0 100644 --- a/examples/oxr/python/isaaccapture_examples/oxr/test_hand_inactive_on_plugin_stop.py +++ b/tests/python/xr_runtime/test_hand_inactive_on_plugin_stop.py @@ -14,15 +14,13 @@ """ import time -from pathlib import Path import isaaccapture.deviceio as deviceio import isaaccapture.oxr as oxr import isaaccapture.plugin_manager as pm +from install_tree import plugins_dir -# Six levels up is the tree root -- the checkout, or the install prefix -# when running from install/examples/. -PLUGIN_ROOT_DIR = Path(__file__).resolve().parents[5] / "plugins" +PLUGIN_ROOT_DIR = plugins_dir() ACTIVE_WAIT_S = 15.0 # max time to wait for hands to become active INACTIVE_WAIT_S = 5.0 # max time to wait for hands to become inactive after stop diff --git a/examples/oxr/python/isaaccapture_examples/oxr/test_modular.py b/tests/python/xr_runtime/test_modular.py similarity index 100% rename from examples/oxr/python/isaaccapture_examples/oxr/test_modular.py rename to tests/python/xr_runtime/test_modular.py diff --git a/examples/oxr/python/isaaccapture_examples/oxr/test_oak_camera.py b/tests/python/xr_runtime/test_oak_camera.py similarity index 98% rename from examples/oxr/python/isaaccapture_examples/oxr/test_oak_camera.py rename to tests/python/xr_runtime/test_oak_camera.py index b10efc30c5..eda26eac31 100755 --- a/examples/oxr/python/isaaccapture_examples/oxr/test_oak_camera.py +++ b/tests/python/xr_runtime/test_oak_camera.py @@ -25,10 +25,9 @@ import isaaccapture.plugin_manager as pm import isaaccapture.deviceio as deviceio import isaaccapture.oxr as oxr +from install_tree import plugins_dir -# Six levels up is the tree root -- the checkout, or the install prefix -# when running from install/examples/. -PLUGIN_ROOT_DIR = Path(__file__).resolve().parents[5] / "plugins" +PLUGIN_ROOT_DIR = plugins_dir() MODE_NO_METADATA = "no-metadata" MODE_SCHEMA_PUSHER = "schema-pusher" diff --git a/examples/oxr/python/isaaccapture_examples/oxr/test_package_version.py b/tests/python/xr_runtime/test_package_version.py similarity index 100% rename from examples/oxr/python/isaaccapture_examples/oxr/test_package_version.py rename to tests/python/xr_runtime/test_package_version.py diff --git a/examples/oxr/python/isaaccapture_examples/oxr/test_session_sharing.py b/tests/python/xr_runtime/test_session_sharing.py similarity index 100% rename from examples/oxr/python/isaaccapture_examples/oxr/test_session_sharing.py rename to tests/python/xr_runtime/test_session_sharing.py diff --git a/examples/oxr/python/isaaccapture_examples/oxr/test_synthetic_hands.py b/tests/python/xr_runtime/test_synthetic_hands.py similarity index 91% rename from examples/oxr/python/isaaccapture_examples/oxr/test_synthetic_hands.py rename to tests/python/xr_runtime/test_synthetic_hands.py index 1521702209..24a69da48c 100644 --- a/examples/oxr/python/isaaccapture_examples/oxr/test_synthetic_hands.py +++ b/tests/python/xr_runtime/test_synthetic_hands.py @@ -15,19 +15,13 @@ """ import time -from pathlib import Path import isaaccapture.deviceio as deviceio import isaaccapture.oxr as oxr import isaaccapture.plugin_manager as pm +from install_tree import plugins_dir -# Paths -# The test will look for plugins in the install directory relative to this script -# This script is in install/examples/oxr/python -# Plugins are in install/plugins -# Six levels up is the tree root -- the checkout, or the install prefix -# when running from install/examples/. -PLUGIN_ROOT_DIR = Path(__file__).resolve().parents[5] / "plugins" +PLUGIN_ROOT_DIR = plugins_dir() def run_test(): From 0a8435d7460a1dfad4a1cdc28faf58f3e389a493 Mon Sep 17 00:00:00 2001 From: Ivan Yang Date: Tue, 27 Oct 2026 22:20:32 -0700 Subject: [PATCH 3/4] examples(oxr): fix run instructions, plugin lookup and output paths - README: the Python modules attach to or start CloudXR through CloudXRLauncher, which overwrites XR_RUNTIME_JSON, so the export is gone from both Python blocks. The README says how to stop the service, what --accept-eula does and how to use another runtime. The C++ blocks source ~/.cloudxr/run/cloudxr.env, as references/cloudxr.rst says, instead of exporting a file name the runtime never writes. - README: install isaaccapture as Build from source step 4 does, pinned to X.Y+local with --reinstall-package, into a venv made with the build's Python. CI runs only the scripts listed in run_tests_with_cloudxr.sh, and the README says so and how to run one by hand. - install_tree.plugins_dir() stops at the checkout root (VERSION plus CMakeLists.txt) or the test image's /app instead of walking to /, so a nested worktree no longer picks up another checkout's plugins. The three plugin scripts take --plugin-root for any other install prefix. - modular_example_with_mcap and test_oak_camera write their MCAP under ./recordings/, which the root .gitignore covers, and create it. - modular_example_with_mcap's docstring names McapRecordingConfig, not parameters DeviceIOSession.run() no longer takes. - The OAK page and plugin README call test_oak_camera.py a test script and give its run line; tests/AGENTS.md uses a repo-root path. Signed-off-by: Ivan Yang --- .gitignore | 6 +-- docs/source/device/cameras/oak.rst | 11 +++-- examples/oxr/README.md | 45 +++++++++++++------ .../oxr/modular_example_with_mcap.py | 9 ++-- src/plugins/oak/README.md | 11 +++-- tests/AGENTS.md | 6 +-- tests/python/xr_runtime/install_tree.py | 29 ++++++++---- .../test_hand_inactive_on_plugin_stop.py | 23 +++++----- tests/python/xr_runtime/test_oak_camera.py | 27 +++++------ .../python/xr_runtime/test_synthetic_hands.py | 22 ++++----- 10 files changed, 119 insertions(+), 70 deletions(-) diff --git a/.gitignore b/.gitignore index 123dda2cda..c3c70f5efa 100644 --- a/.gitignore +++ b/.gitignore @@ -63,9 +63,9 @@ project.spdx # MuJoCo writes this to the working directory, not next to the model. MUJOCO_LOG.TXT -# examples/mcap_record_replay, examples/noitom and examples/oglo_tactile write here -# by default (Path.cwd() / "recordings"), and their READMEs have you run from the -# repo root. +# examples/mcap_record_replay, noitom, oglo_tactile and oxr, and the +# tests/python/xr_runtime scripts, write here by default (Path.cwd() / "recordings"), +# and their READMEs have you run from the repo root. /recordings/ # Runtime device files injected by MCP server infrastructure diff --git a/docs/source/device/cameras/oak.rst b/docs/source/device/cameras/oak.rst index e584f7245e..41a4022591 100644 --- a/docs/source/device/cameras/oak.rst +++ b/docs/source/device/cameras/oak.rst @@ -172,9 +172,14 @@ controllers, etc.): while True: session.step() # metadata is recorded each update -See also :code-file:`tests/python/xr_runtime/test_oak_camera.py` (``--mode -schema-pusher``) for a standalone PluginManager + DeviceIOSession example of -the same SchemaPusher flow. +The test script :code-file:`tests/python/xr_runtime/test_oak_camera.py` runs the +same SchemaPusher flow with ``PluginManager`` and ``DeviceIOSession`` directly. +After ``cmake --install build``, with the CloudXR environment loaded (see +:ref:`load-cloudxr-environment-variables`), run it from the repository root: + +.. code-block:: bash + + python tests/python/xr_runtime/test_oak_camera.py --mode schema-pusher Configuration ------------- diff --git a/examples/oxr/README.md b/examples/oxr/README.md index 7b59617e40..dcd6e27abb 100644 --- a/examples/oxr/README.md +++ b/examples/oxr/README.md @@ -38,24 +38,37 @@ examples/oxr/ └── modular_example_with_mcap.py ``` -The `test_*.py` scripts that CloudXR GPU CI runs against this API are in -[`tests/python/xr_runtime/`](../../tests/python/xr_runtime/). +The `test_*.py` scripts in [`tests/python/xr_runtime/`](../../tests/python/xr_runtime/) +exercise the same API. CloudXR GPU CI runs the ones listed in +[`scripts/run_tests_with_cloudxr.sh`](../../scripts/run_tests_with_cloudxr.sh). +To run one by hand, use `python tests/python/xr_runtime/