diff --git a/AGENTS.md b/AGENTS.md index 0113244048..999cce231a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -117,9 +117,10 @@ the logging packages. **The one exception is a program that imports no `isaaccapture` at all**, and is meant to keep it that way: there are no handlers of ours for it to duplicate, and reaching for `logging_config` would add the dependency the - program exists without. `examples/camera_viz/camera_streamer.py` is the - case — a sender-only camera box runs it with no CUDA/Vulkan/OpenXR runtime - installed, so `import isaaccapture` would fail outright. Keep the logger + program exists without. The camera_viz streamer, + `examples/camera_viz/python/isaaccapture_examples/camera_viz/camera_streamer.py`, + is the case — a sender-only camera box runs it with no CUDA/Vulkan/OpenXR + runtime installed, so `import isaaccapture` would fail outright. Keep the logger names under `isaaccapture.` anyway; the naming rule above is a convention, not a runtime coupling. - **Configure through the public API instead:** `logging_config.set_console_level()`, diff --git a/docs/source/references/camera_streaming.rst b/docs/source/references/camera_streaming.rst index 815d6e5b12..293dc41119 100644 --- a/docs/source/references/camera_streaming.rst +++ b/docs/source/references/camera_streaming.rst @@ -72,12 +72,14 @@ There is no need to install the ``isaaccapture`` pip package yourself. ``setup`` sample's own environment: ``isaaccapture[cloudxr]`` — the ``cloudxr`` extra is not optional here, since XR is the default display mode and the viewer launches the runtime itself — plus every other Python dependency, into an isolated ``.venv/`` via ``uv`` (no ``--system-site-packages``). -It resolves a release matching the checkout's major/minor version, falling back to a release -candidate and then, after asking, to a source build -of the surrounding checkout (see :code-file:`scripts/_install_deps.sh +For ``isaaccapture`` it resolves a release matching the checkout's major/minor version, falling +back to a release candidate and then, after asking, to a source build of the surrounding +checkout (see :code-file:`scripts/_install_deps.sh `; ``--wheel`` and ``--build-from-source`` override -the choice). When required system packages are missing, it prints the exact ``apt-get`` line -to approve — declining, or a non-interactive run, aborts. +the choice). It also installs the sample itself there as an editable package, which is what +``python -m isaaccapture_examples.camera_viz`` imports. When required system packages are +missing, it prints the exact ``apt-get`` line to approve — declining, or a non-interactive run, +aborts. Setup selects CuPy for the toolkit at ``CUDA_PATH``, if set. Otherwise it takes the first CUDA 12 or 13 toolkit among those of each ``nvcc`` on ``PATH`` and ``/usr/local/cuda``: first one @@ -196,7 +198,8 @@ path, such as ``configs/replay.yaml``: * - Command - Purpose * - ``./camera_viz.sh run CONFIG [VIEWER_FLAGS]`` - - Run the viewer; flags after ``CONFIG`` are forwarded to ``camera_viz.py``. + - Run the viewer; flags after ``CONFIG`` are forwarded to + ``python -m isaaccapture_examples.camera_viz``. * - ``./camera_viz.sh loopback CONFIG`` - Run a local sender and receiver to test RTP. Requires ``setup --with-rtp``. * - ``./camera_viz.sh deploy CONFIG [DEPLOY_FLAGS]`` @@ -212,7 +215,8 @@ path, such as ``configs/replay.yaml``: The ``service-*`` commands manage the robot's RTP sender; CloudXR has separate :ref:`service commands `. -Run ``.venv/bin/python camera_viz.py --help`` for viewer and CloudXR launcher flags. +Run ``.venv/bin/python -m isaaccapture_examples.camera_viz --help`` for viewer and CloudXR +launcher flags. .. _camera-streaming-web-client: @@ -602,8 +606,8 @@ service, activate the example venv (or any environment with ``isaaccapture``) an python -m isaaccapture.cloudxr.service status python -m isaaccapture.cloudxr.service stop -Run ``.venv/bin/python camera_viz.py --help`` for the rest (install dir, env-config file, -WSS proxy toggle). +Run ``.venv/bin/python -m isaaccapture_examples.camera_viz --help`` for the rest (install dir, +env-config file, WSS proxy toggle). Runtime settings themselves are declared in the config rather than exported. The viewer ships defaults (pose wait off, runtime foveation on), and ``display.cloudxr`` overrides them per @@ -643,7 +647,7 @@ client-reconstructed streaming. Split mode — robot → workstation over RTP ----------------------------------------- -Split mode runs the capture side on the robot (``camera_streamer.py``) and ships RTP H.264 to +Split mode runs the capture side on the robot (the ``camera_streamer`` module) and ships RTP H.264 to the viewer on the workstation (``source: rtp``). .. warning:: @@ -686,13 +690,13 @@ interactively keeps it out of shell history and the command line. packages or JetPack CUDA wiring prompt for approval on the robot. 3. Render ``~/.config/systemd/user/camera-streamer.service``. ``--streaming-host IP`` (or ``STREAMING_HOST``) injects ``--host IP`` into its ``ExecStart`` without changing the YAML. -4. Enable lingering once with ``sudo loginctl enable-linger``, then enable and start the user - service with ``systemctl --user enable --now``. +4. Enable lingering once with ``sudo loginctl enable-linger``, then enable the user service and + restart it, so a redeploy replaces a streamer that is already running. ``--no-service`` stops after source sync and dependency installation. In that case, ``--streaming-host`` does not configure a running sender; pass ``--host`` when starting -``camera_streamer.py`` manually. The sender retries across unplug, SDK errors, and network -blips. Operate the installed unit with: +``python -m isaaccapture_examples.camera_viz.camera_streamer`` manually. The sender retries +across unplug, SDK errors, and network blips. Operate the installed unit with: .. code-block:: bash @@ -825,6 +829,8 @@ Troubleshooting - **Python cannot find dependencies** — use the sample's ``.venv/bin/python``, or select an environment with :ref:`setup --venv `. + ``No module named 'isaaccapture_examples'`` means the sample is not installed in that + environment; rerun ``./camera_viz.sh setup`` (with the same ``--venv``, if you used one). - **No video on Orin.** When the CloudXR runtime runs on Jetson Orin, set **Video Codec** to **H.264** in the CloudXR web client. See :ref:`connect-xr-headset`. @@ -875,36 +881,38 @@ Televiz as the compositor at the end of the chain: :class: code-100col camera_viz/ - ├── camera_viz.sh — CLI: setup / loopback / run / deploy / service-* - ├── camera_viz.py — receiver / viewer (drives a Televiz VizSession) - ├── camera_streamer.py — robot-side RTP sender (per-camera supervisor) - ├── config.py — YAML parsing and validation - ├── display.py — VizSession + one layer per surface - ├── cloudxr_env.py — display.cloudxr → runtime env file - ├── dashboard.py — terminal status panel - ├── controls/ — XR controller bindings, shapes, stereo geometry, HUD - ├── pipeline/ — source ABC + threaded runner - ├── placements/ — XR lock-mode strategies (world / head / lazy / gimbal) - ├── sources/ — V4L2 / OAK-D / ZED / video replay / synthetic / rtp_h264 - ├── transports/ — RTP sender + receiver (native + GStreamer) - ├── codec/ — native NVENC / NVDEC pybind module - ├── configs/ — one YAML per source kind - ├── test_data/ — sample replay clip (Git LFS) + ├── camera_viz.sh — CLI: setup / loopback / run / deploy / service-* + ├── configs/ — one YAML per source kind + ├── test_data/ — sample replay clip (Git LFS) + ├── python/isaaccapture_examples/camera_viz/ + │ ├── camera_viz.py — receiver / viewer (drives a Televiz VizSession) + │ ├── camera_streamer.py — robot-side RTP sender (per-camera supervisor) + │ ├── config.py — YAML parsing and validation + │ ├── display.py — VizSession + one layer per surface + │ ├── cloudxr_env.py — display.cloudxr → runtime env file + │ ├── cuda_env.py — applies the CUDA_PATH setup records + │ ├── dashboard.py — terminal status panel + │ ├── controls/ — XR controller bindings, shapes, stereo geometry, HUD + │ ├── pipeline/ — source ABC + threaded runner + │ ├── placements/ — XR lock-mode strategies (world / head / lazy / gimbal) + │ ├── sources/ — V4L2 / OAK-D / ZED / video replay / synthetic / rtp_h264 + │ ├── transports/ — RTP sender + receiver (native + GStreamer) + │ └── codec/ — native NVENC / NVDEC pybind module └── scripts/ ├── _install_deps.sh — installer (setup + deploy) └── camera-streamer.service.in — systemd unit template -- **Sources** (:code-dir:`sources/ `) implement a common source ABC and - hand frames to a threaded runner in :code-dir:`pipeline/ `. Each +- **Sources** (:code-dir:`sources/ `) implement a common source ABC and + hand frames to a threaded runner in :code-dir:`pipeline/ `. Each source produces GPU frames where possible — e.g. the ZED source uses ``retrieve_image(MEM.GPU)`` so BGRA8 stays in VRAM and a CUDA kernel channel-swaps into contiguous RGBA with no host round-trip. -- **The viewer** (:code-file:`camera_viz.py `) creates a +- **The viewer** (:code-file:`camera_viz.py `) creates a ``VizSession`` and adds one ``QuadLayer`` per enabled camera, then submits each frame to its layer and calls ``render()`` once per frame. Stereo cameras submit both eyes. -- **Transport** (:code-dir:`transports/ `) carries the split mode: +- **Transport** (:code-dir:`transports/ `) carries the split mode: an RTP H.264 sender on the robot and a receiver on the workstation, with a native NVENC/NVDEC codec - module (:code-dir:`codec/ `) or a GStreamer fallback. -- **Placement** (:code-dir:`placements/ `) holds the XR lock-mode + module (:code-dir:`codec/ `) or a GStreamer fallback. +- **Placement** (:code-dir:`placements/ `) holds the XR lock-mode strategies. Placement is application policy — Televiz only renders a layer at whatever pose the app sets each frame. diff --git a/examples/camera_viz/camera_viz.sh b/examples/camera_viz/camera_viz.sh index 065be329d7..ca81d7d2c8 100755 --- a/examples/camera_viz/camera_viz.sh +++ b/examples/camera_viz/camera_viz.sh @@ -163,6 +163,10 @@ _require_local_config() { log_error "no venv at $LOCAL_VENV — run ./camera_viz.sh setup first" exit 1 } + "$LOCAL_VENV/bin/python" -c 'import isaaccapture_examples.camera_viz' >/dev/null 2>&1 || { + log_error "camera_viz is not installed in $LOCAL_VENV — rerun ./camera_viz.sh setup" + exit 1 + } } # RTP deps are opt-in at setup time (``--with-rtp``), so a venv provisioned @@ -198,7 +202,7 @@ PY } # ────────────────────────────────────────────────────────────────────── -# run (the viewer; args after CONFIG forward to camera_viz.py, e.g. --mode xr) +# run (the viewer; args after CONFIG forward to the viewer, e.g. --mode xr) # ────────────────────────────────────────────────────────────────────── cmd_run() { @@ -209,7 +213,7 @@ cmd_run() { _require_rtp_deps fi log_step "Starting camera_viz — Ctrl-C to exit" - "$LOCAL_VENV/bin/python" "$HERE/camera_viz.py" "$@" + "$LOCAL_VENV/bin/python" -m isaaccapture_examples.camera_viz "$@" } # ────────────────────────────────────────────────────────────────────── @@ -240,11 +244,11 @@ cmd_loopback() { trap '_loopback_cleanup' EXIT log_step "Starting camera_streamer → 127.0.0.1 (background)" - "$LOCAL_VENV/bin/python" "$HERE/camera_streamer.py" "$1" --host 127.0.0.1 & + "$LOCAL_VENV/bin/python" -m isaaccapture_examples.camera_viz.camera_streamer "$1" --host 127.0.0.1 & _LOOPBACK_SENDER_PID=$! log_step "Starting camera_viz (foreground) — Ctrl-C to exit" - "$LOCAL_VENV/bin/python" "$HERE/camera_viz.py" "$_LOOPBACK_RECV_CONFIG" + "$LOCAL_VENV/bin/python" -m isaaccapture_examples.camera_viz "$_LOOPBACK_RECV_CONFIG" } # ────────────────────────────────────────────────────────────────────── @@ -256,7 +260,7 @@ cmd_deploy() { # anything we don't recognize would land in REMOTE_REST (where the # positional CONFIG lives). local no_service=false - # ``--streaming-host`` → injected as ``--host`` on camera_streamer.py's + # ``--streaming-host`` → injected as ``--host`` on the streamer's # CLI inside the rendered systemd unit. Lets you keep the YAML at # 127.0.0.1 for loopback and override only when deploying to a robot. local streaming_host="${STREAMING_HOST:-}" @@ -304,10 +308,10 @@ cmd_deploy() { local manual_host_flag="" if [[ -n "$streaming_host" ]]; then manual_host_flag=" --host $streaming_host" - log_info "(--streaming-host has no effect in --no-service mode — pass it to camera_streamer.py yourself.)" + log_info "(--streaming-host has no effect in --no-service mode — pass it to the streamer yourself.)" fi log_info "Run manually with:" - log_info " ssh $REMOTE_USER@$REMOTE_HOST 'cd ~/camera_viz && .venv/bin/python camera_streamer.py $config$manual_host_flag'" + log_info " ssh $REMOTE_USER@$REMOTE_HOST 'cd ~/camera_viz && .venv/bin/python -m isaaccapture_examples.camera_viz.camera_streamer $config$manual_host_flag'" log_info "Re-run without --no-service when you're ready to install the systemd unit." return 0 fi @@ -357,8 +361,10 @@ REMOTE ) ssh_run_tty "$linger_cmd" - log_step "Enabling + starting service" - ssh_run "systemctl --user enable --now ${SERVICE_NAME}.service" + # restart, not `enable --now`: a redeploy must replace a streamer that is + # already running the previous unit. + log_step "Enabling + (re)starting service" + ssh_run "systemctl --user enable ${SERVICE_NAME}.service && systemctl --user restart ${SERVICE_NAME}.service" log_ok "deployed." log_info "Tail logs with: ./camera_viz.sh service-logs --host $REMOTE_HOST --user $REMOTE_USER" @@ -441,9 +447,9 @@ REMOTE (Jetson robot) deploy [--host H --user U [--password P]] [--streaming-host IP] [--no-service] CONFIG rsync source, install deps, install + start - systemd user service running camera_streamer.py. + systemd user service running the streamer. --no-service stops after deps so you can run - camera_streamer.py by hand first. + the streamer by hand first. --streaming-host injects ``--host IP`` into the unit's ExecStart so the sender streams there regardless of streaming.host in the YAML. Same diff --git a/examples/camera_viz/pyproject.toml b/examples/camera_viz/pyproject.toml index c2dd8a13da..709e6f38b6 100644 --- a/examples/camera_viz/pyproject.toml +++ b/examples/camera_viz/pyproject.toml @@ -7,8 +7,14 @@ # ``camera_viz.sh setup``; pass ``setup --wheel `` to use a locally # built wheel instead. +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + [project] -name = "camera-viz" +# The dist name mirrors the import path, so an installed example claims no bare +# top-level name in site-packages. +name = "isaaccapture-examples-camera-viz" version = "0.0.0" description = "Televiz camera-feed visualizer" requires-python = ">=3.11,<3.14" @@ -38,13 +44,27 @@ rtp = [ # setup script apt-installs them and uses --system-site-packages), # not pip — PyGObject from PyPI requires a source-build with cairo # + girepository dev headers. pybind11 stays in the venv as a build- - # time dep for the codec at ./codec/. + # time dep for the native codec. "pybind11>=2.11", ] +# The viewer imports isaaccapture and Pillow; the sender must run without +# isaaccapture, so they are not base dependencies. +viewer = ["isaaccapture[cloudxr]", "pillow>=10.0"] -# Native NVENC/NVDEC codec lives in ./codec/. Build with -# ``codec/build.sh``; ``./camera_viz.sh setup`` does this automatically. +# Native NVENC/NVDEC codec lives in ./python/isaaccapture_examples/camera_viz/codec/. +# Build with its build.sh; ``./camera_viz.sh setup`` does this automatically. # Requires CUDA toolkit + NVIDIA driver (libnvidia-encode + libnvcuvid). +# `only-include` + `sources`, not `packages`, and no __init__.py above +# camera_viz -- see the packaging rules in examples/README.md. +# +# The native codec's .so is built in place by codec/build.sh, so it is picked up +# from the source tree rather than shipped by this build. Hatchling reads only +# this directory's .gitignore, so the codec build tree is excluded here. +[tool.hatch.build.targets.wheel] +only-include = ["python/isaaccapture_examples/camera_viz"] +sources = ["python"] +exclude = ["/python/isaaccapture_examples/camera_viz/codec/build", "*.so"] + [tool.uv] python-preference = "only-managed" diff --git a/examples/camera_viz/python/isaaccapture_examples/camera_viz/__init__.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/__init__.py new file mode 100644 index 0000000000..52a7a9daf0 --- /dev/null +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/__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/camera_viz/python/isaaccapture_examples/camera_viz/__main__.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/__main__.py new file mode 100644 index 0000000000..afc8d7fbf8 --- /dev/null +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/__main__.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import sys + +from .camera_viz import main + +sys.exit(main()) diff --git a/examples/camera_viz/camera_streamer.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/camera_streamer.py similarity index 98% rename from examples/camera_viz/camera_streamer.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/camera_streamer.py index 6d394527e9..f134595756 100755 --- a/examples/camera_viz/camera_streamer.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/camera_streamer.py @@ -29,15 +29,15 @@ import yaml -import cuda_env -from pipeline import FrameSource -from sources import ( +from . import cuda_env +from .pipeline import FrameSource +from .sources import ( PairedFrameSource, build_local_camera, resolve_video_paths, set_verbose, ) -from transports import RtpH264Sender, make_encoder +from .transports import RtpH264Sender, make_encoder logger = logging.getLogger("isaaccapture.camera_streamer") diff --git a/examples/camera_viz/camera_viz.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/camera_viz.py similarity index 98% rename from examples/camera_viz/camera_viz.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/camera_viz.py index f622d1ca02..d43b666c06 100755 --- a/examples/camera_viz/camera_viz.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/camera_viz.py @@ -12,7 +12,7 @@ RTP-mode deployment share one config. Usage: - python camera_viz.py configs/v4l2.yaml + python -m isaaccapture_examples.camera_viz configs/v4l2.yaml """ from __future__ import annotations @@ -30,19 +30,16 @@ from isaaccapture import logging_config from isaaccapture.cloudxr import CloudXRLauncher -import cloudxr_env -import config -import cuda_env -import display -from controls import ( +from . import cloudxr_env, config, cuda_env, display +from .controls import ( ControllerControls, ControlTarget, controls_config_from_yaml, make_hud, ) -from dashboard import Dashboard -from pipeline import VizRunner -from sources import resolve_video_paths, set_notify_sink, set_verbose +from .dashboard import Dashboard +from .pipeline import VizRunner +from .sources import resolve_video_paths, set_notify_sink, set_verbose def _parse_args(argv: Optional[list[str]]): @@ -392,7 +389,3 @@ def _on_signal(signum, frame): # actually in force inspectable while the runtime is up. settings_stack.close() return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/examples/camera_viz/cloudxr_env.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/cloudxr_env.py similarity index 100% rename from examples/camera_viz/cloudxr_env.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/cloudxr_env.py diff --git a/examples/camera_viz/codec/CMakeLists.txt b/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/CMakeLists.txt similarity index 100% rename from examples/camera_viz/codec/CMakeLists.txt rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/CMakeLists.txt diff --git a/examples/camera_viz/codec/__init__.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/__init__.py similarity index 84% rename from examples/camera_viz/codec/__init__.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/__init__.py index bcd20c41fd..9d01b6ce70 100644 --- a/examples/camera_viz/codec/__init__.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/__init__.py @@ -18,7 +18,8 @@ ) except ImportError as e: raise ImportError( - "camera_viz native codec not built. Run `examples/camera_viz/codec/build.sh`." + "camera_viz native codec not built. Run " + "`examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/build.sh`." ) from e __all__ = [ diff --git a/examples/camera_viz/codec/build.sh b/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/build.sh similarity index 100% rename from examples/camera_viz/codec/build.sh rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/build.sh diff --git a/examples/camera_viz/codec/codec_bindings.cpp b/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/codec_bindings.cpp similarity index 100% rename from examples/camera_viz/codec/codec_bindings.cpp rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/codec_bindings.cpp diff --git a/examples/camera_viz/codec/h264_decoder.cpp b/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/h264_decoder.cpp similarity index 100% rename from examples/camera_viz/codec/h264_decoder.cpp rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/h264_decoder.cpp diff --git a/examples/camera_viz/codec/h264_encoder.cpp b/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/h264_encoder.cpp similarity index 100% rename from examples/camera_viz/codec/h264_encoder.cpp rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/h264_encoder.cpp diff --git a/examples/camera_viz/codec/inc/h264_decoder.hpp b/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/inc/h264_decoder.hpp similarity index 100% rename from examples/camera_viz/codec/inc/h264_decoder.hpp rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/inc/h264_decoder.hpp diff --git a/examples/camera_viz/codec/inc/h264_encoder.hpp b/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/inc/h264_encoder.hpp similarity index 100% rename from examples/camera_viz/codec/inc/h264_encoder.hpp rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/inc/h264_encoder.hpp diff --git a/examples/camera_viz/codec/nv12_to_rgba.cu b/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/nv12_to_rgba.cu similarity index 100% rename from examples/camera_viz/codec/nv12_to_rgba.cu rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/nv12_to_rgba.cu diff --git a/examples/camera_viz/codec/nv12_to_rgba.cuh b/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/nv12_to_rgba.cuh similarity index 100% rename from examples/camera_viz/codec/nv12_to_rgba.cuh rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/nv12_to_rgba.cuh diff --git a/examples/camera_viz/config.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/config.py similarity index 98% rename from examples/camera_viz/config.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/config.py index abbd7df97b..5a890168e6 100644 --- a/examples/camera_viz/config.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/config.py @@ -18,9 +18,9 @@ from dataclasses import dataclass from typing import List, Optional, Tuple -from pipeline import FrameSource -from placements import PlacementConfig, PlacementStrategy, build as build_placement -from sources import PairedFrameSource, RtpH264Source, build_local_camera +from .pipeline import FrameSource +from .placements import PlacementConfig, PlacementStrategy, build as build_placement +from .sources import PairedFrameSource, RtpH264Source, build_local_camera @dataclass diff --git a/examples/camera_viz/controls/__init__.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/__init__.py similarity index 95% rename from examples/camera_viz/controls/__init__.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/__init__.py index 33c391e760..87379a10af 100644 --- a/examples/camera_viz/controls/__init__.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/__init__.py @@ -13,7 +13,7 @@ ``hud`` The head-locked panel that shows what a press just did. -Import from the package, not the modules: ``from controls import +Import from the package, not the modules: ``from .controls import ControllerControls``. """ diff --git a/examples/camera_viz/controls/bindings.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/bindings.py similarity index 99% rename from examples/camera_viz/controls/bindings.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/bindings.py index 6a6fe9b72a..bcee9766a6 100644 --- a/examples/camera_viz/controls/bindings.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/bindings.py @@ -39,7 +39,7 @@ from . import shapes, stereo from .hud import split_message -from placements import ( +from ..placements import ( PlacementConfig, PlacementStrategy, build as build_placement, diff --git a/examples/camera_viz/controls/hud.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/hud.py similarity index 99% rename from examples/camera_viz/controls/hud.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/hud.py index 6555d454d3..42e52850ac 100644 --- a/examples/camera_viz/controls/hud.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/hud.py @@ -17,7 +17,7 @@ import numpy as np from PIL import Image, ImageDraw, ImageFont -from placements import HeadLocked, PlacementConfig +from ..placements import HeadLocked, PlacementConfig _W, _H = 1280, 160 _PAD, _BAR = 36, 12 # left text inset, accent-bar width diff --git a/examples/camera_viz/controls/shapes.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/shapes.py similarity index 99% rename from examples/camera_viz/controls/shapes.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/shapes.py index 9366d65a36..275dec47cd 100644 --- a/examples/camera_viz/controls/shapes.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/shapes.py @@ -21,7 +21,7 @@ from dataclasses import replace from typing import List, Optional, Tuple -from placements import yaw_quat +from ..placements import yaw_quat def _clamp(value: float, limits: Tuple[float, float]) -> float: diff --git a/examples/camera_viz/controls/stereo.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/stereo.py similarity index 100% rename from examples/camera_viz/controls/stereo.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/stereo.py diff --git a/examples/camera_viz/cuda_env.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/cuda_env.py similarity index 100% rename from examples/camera_viz/cuda_env.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/cuda_env.py diff --git a/examples/camera_viz/dashboard.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/dashboard.py similarity index 100% rename from examples/camera_viz/dashboard.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/dashboard.py diff --git a/examples/camera_viz/display.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/display.py similarity index 98% rename from examples/camera_viz/display.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/display.py index 8662f85822..7056b9c373 100644 --- a/examples/camera_viz/display.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/display.py @@ -14,8 +14,8 @@ import isaaccapture.viz as viz -from config import SourceEntry, VALID_SHAPES -from placements import yaw_quat +from .config import SourceEntry, VALID_SHAPES +from .placements import yaw_quat # ImageLayerBase::kSlotCount (kMaxFramesInFlight + 2). Only used to # report the VRAM that shape switching adds. diff --git a/examples/camera_viz/pipeline/__init__.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/pipeline/__init__.py similarity index 91% rename from examples/camera_viz/pipeline/__init__.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/pipeline/__init__.py index 4550408cd6..f0c610bd98 100644 --- a/examples/camera_viz/pipeline/__init__.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/pipeline/__init__.py @@ -20,4 +20,4 @@ def __getattr__(name: str): from .runner import VizRunner return VizRunner - raise AttributeError(f"module 'pipeline' has no attribute {name!r}") + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/examples/camera_viz/pipeline/interface.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/pipeline/interface.py similarity index 100% rename from examples/camera_viz/pipeline/interface.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/pipeline/interface.py diff --git a/examples/camera_viz/pipeline/runner.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/pipeline/runner.py similarity index 99% rename from examples/camera_viz/pipeline/runner.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/pipeline/runner.py index 924fb85d98..b25d0b218d 100644 --- a/examples/camera_viz/pipeline/runner.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/pipeline/runner.py @@ -23,7 +23,7 @@ import isaaccapture.viz as viz -from dashboard import CameraRow, Dashboard, Snapshot +from ..dashboard import CameraRow, Dashboard, Snapshot from .interface import FrameSource diff --git a/examples/camera_viz/placements/__init__.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/placements/__init__.py similarity index 100% rename from examples/camera_viz/placements/__init__.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/placements/__init__.py diff --git a/examples/camera_viz/placements/_math.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/placements/_math.py similarity index 100% rename from examples/camera_viz/placements/_math.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/placements/_math.py diff --git a/examples/camera_viz/placements/lock_modes.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/placements/lock_modes.py similarity index 100% rename from examples/camera_viz/placements/lock_modes.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/placements/lock_modes.py diff --git a/examples/camera_viz/sources/__init__.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/__init__.py similarity index 99% rename from examples/camera_viz/sources/__init__.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/__init__.py index 89132a9c81..b43b02fc8a 100644 --- a/examples/camera_viz/sources/__init__.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/__init__.py @@ -11,7 +11,7 @@ from pathlib import Path from typing import List -from pipeline import FrameSource +from ..pipeline import FrameSource from ._helpers import PairedFrameSource, set_notify_sink, set_verbose from .oakd import OakdSource diff --git a/examples/camera_viz/sources/_helpers.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/_helpers.py similarity index 99% rename from examples/camera_viz/sources/_helpers.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/_helpers.py index 2ba8fd94ff..ec818e2066 100644 --- a/examples/camera_viz/sources/_helpers.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/_helpers.py @@ -43,7 +43,7 @@ import numpy as np -from pipeline import Frame, FrameSource, SourceSpec +from ..pipeline import Frame, FrameSource, SourceSpec _SINK = None diff --git a/examples/camera_viz/sources/_nv_decode.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/_nv_decode.py similarity index 93% rename from examples/camera_viz/sources/_nv_decode.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/_nv_decode.py index 3bcc84ec84..80e2a0178a 100644 --- a/examples/camera_viz/sources/_nv_decode.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/_nv_decode.py @@ -36,11 +36,11 @@ def _ensure_initialized(self) -> None: if self._decoder is not None: return try: - import codec + from .. import codec except ImportError as e: raise RuntimeError( "NvH264Decoder requires the native codec. Run " - "`examples/camera_viz/codec/build.sh`." + "`examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/build.sh`." ) from e cfg = codec.DecoderConfig() diff --git a/examples/camera_viz/sources/oakd.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/oakd.py similarity index 99% rename from examples/camera_viz/sources/oakd.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/oakd.py index 11b1c78865..8ceaec4dc7 100644 --- a/examples/camera_viz/sources/oakd.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/oakd.py @@ -34,7 +34,7 @@ import numpy as np -from pipeline import Frame, FrameSource, SourceSpec +from ..pipeline import Frame, FrameSource, SourceSpec from ._helpers import alloc_pinned_host, notify, notify_verbose logger = logging.getLogger("isaaccapture.camera_viz.sources.oakd") diff --git a/examples/camera_viz/sources/rtp_h264.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/rtp_h264.py similarity index 99% rename from examples/camera_viz/sources/rtp_h264.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/rtp_h264.py index 6ec5fa67a8..d5a5cf6879 100644 --- a/examples/camera_viz/sources/rtp_h264.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/rtp_h264.py @@ -19,8 +19,8 @@ import time from typing import Optional -from pipeline import Frame, FrameSource, SourceSpec -from transports import RtpH264Receiver +from ..pipeline import Frame, FrameSource, SourceSpec +from ..transports import RtpH264Receiver from ._nv_decode import NvH264Decoder diff --git a/examples/camera_viz/sources/synthetic.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/synthetic.py similarity index 99% rename from examples/camera_viz/sources/synthetic.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/synthetic.py index 1d8125c2d4..7629d4fa38 100644 --- a/examples/camera_viz/sources/synthetic.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/synthetic.py @@ -17,7 +17,7 @@ import time from typing import Optional -from pipeline import Frame, FrameSource, SourceSpec +from ..pipeline import Frame, FrameSource, SourceSpec class SyntheticSource(FrameSource): diff --git a/examples/camera_viz/sources/v4l2.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/v4l2.py similarity index 100% rename from examples/camera_viz/sources/v4l2.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/v4l2.py diff --git a/examples/camera_viz/sources/video_file.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/video_file.py similarity index 100% rename from examples/camera_viz/sources/video_file.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/video_file.py diff --git a/examples/camera_viz/sources/zed.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/zed.py similarity index 99% rename from examples/camera_viz/sources/zed.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/zed.py index f3fc67506f..0663aea746 100644 --- a/examples/camera_viz/sources/zed.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/zed.py @@ -27,7 +27,7 @@ from dataclasses import dataclass, field from typing import List, Optional -from pipeline import Frame, FrameSource, SourceSpec +from ..pipeline import Frame, FrameSource, SourceSpec from ._helpers import notify, notify_verbose diff --git a/examples/camera_viz/transports/__init__.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/__init__.py similarity index 100% rename from examples/camera_viz/transports/__init__.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/__init__.py diff --git a/examples/camera_viz/transports/_encoder_factory.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_encoder_factory.py similarity index 92% rename from examples/camera_viz/transports/_encoder_factory.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_encoder_factory.py index a301893ee4..7bf26a8b1f 100644 --- a/examples/camera_viz/transports/_encoder_factory.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_encoder_factory.py @@ -4,8 +4,8 @@ Two H.264 encoders share the same ``encode(rgba) -> List[bytes]`` API: - * :class:`NvH264Encoder` — desktop, native NVENC at - ``examples/camera_viz/codec/``. Zero-copy from CuPy into NVENC. + * :class:`NvH264Encoder` — desktop, native NVENC in this package's + ``codec/``. Zero-copy from CuPy into NVENC. * :class:`GstNvH264Encoder` — Jetson / portable, GStreamer's ``nvv4l2h264enc`` (or ``nvh264enc`` / ``x264enc`` fallbacks). ~1-3 ms D2H download at 720p. @@ -24,7 +24,7 @@ def _has_native_codec() -> bool: # Both ImportError (no package) and RuntimeError (raised by # codec/__init__.py on missing .so) fall through to GStreamer. try: - import codec # noqa: F401 + from .. import codec # noqa: F401 return True except (ImportError, RuntimeError): diff --git a/examples/camera_viz/transports/_nv12_convert.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_nv12_convert.py similarity index 100% rename from examples/camera_viz/transports/_nv12_convert.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_nv12_convert.py diff --git a/examples/camera_viz/transports/_nv_encode.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_nv_encode.py similarity index 95% rename from examples/camera_viz/transports/_nv_encode.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_nv_encode.py index 6ba6ef1812..b69a6b6e0b 100644 --- a/examples/camera_viz/transports/_nv_encode.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_nv_encode.py @@ -45,11 +45,11 @@ def _ensure_initialized(self, rgba_device_id: int) -> None: return try: - import codec + from .. import codec except ImportError as e: raise RuntimeError( "NvH264Encoder requires the native codec. Run " - "`examples/camera_viz/codec/build.sh`." + "`examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/build.sh`." ) from e # Follow the input's GPU when the caller didn't pin one. diff --git a/examples/camera_viz/transports/_nv_encode_gst.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_nv_encode_gst.py similarity index 100% rename from examples/camera_viz/transports/_nv_encode_gst.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_nv_encode_gst.py diff --git a/examples/camera_viz/transports/rtp_h264_receiver.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/rtp_h264_receiver.py similarity index 100% rename from examples/camera_viz/transports/rtp_h264_receiver.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/rtp_h264_receiver.py diff --git a/examples/camera_viz/transports/rtp_h264_sender.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/rtp_h264_sender.py similarity index 100% rename from examples/camera_viz/transports/rtp_h264_sender.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/rtp_h264_sender.py diff --git a/examples/camera_viz/scripts/_install_deps.sh b/examples/camera_viz/scripts/_install_deps.sh index 319e3849d9..9e7f912abb 100755 --- a/examples/camera_viz/scripts/_install_deps.sh +++ b/examples/camera_viz/scripts/_install_deps.sh @@ -540,7 +540,7 @@ fi # girepository-1.0 build path, and Ubuntu 22.04 only ships 1.0 # (libgirepository1.0-dev). 3.50.x supports both. Source-builds against # the C deps installed in ensure_apt_deps(); pycairo is a transitive dep. -# pillow renders the XR controls HUD (examples/camera_viz/hud.py). +# pillow renders the XR controls HUD (isaaccapture_examples/camera_viz/controls/hud.py). PKGS=("pyyaml>=6.0" "$target_cupy" "numpy>=1.23" "scipy>=1.15" "pillow>=10.0") [[ "$MODE" == full ]] && PKGS=("$ISAACCAPTURE_PKG" "${PKGS[@]}") $WITH_V4L2 && PKGS+=("opencv-python>=4.5") @@ -571,6 +571,14 @@ else uv pip install --python "$PY" --upgrade "${PKGS[@]}" fi +# The example itself, so `python -m isaaccapture_examples.camera_viz` resolves +# without the source directory on sys.path. Editable, so edits need no +# reinstall -- and the native codec .so, built in place below, is picked up +# from the source tree either way. --no-deps: the steps above already installed +# every dependency, with the cupy variant matching the CUDA major. +step "installing camera_viz" +uv pip install --python "$PY" -e "$CAMERA_VIZ_DIR" --no-deps + # ZED SDK ships get_python_api.py which downloads a matching pyzed wheel # and then tries ``pip install`` it (which fails in uv venvs, no pip). # We let that fail and install the wheel ourselves. @@ -597,7 +605,7 @@ fi # Native NVENC/NVDEC codec. Failures are non-fatal: the runtime falls # back to the GStreamer encoder when the native ``.so`` isn't importable. if $WITH_RTP; then - CODEC_DIR="$CAMERA_VIZ_DIR/codec" + CODEC_DIR="$CAMERA_VIZ_DIR/python/isaaccapture_examples/camera_viz/codec" if [[ -d "$CODEC_DIR" ]]; then step "building native NVENC/NVDEC codec" # shellcheck disable=SC1091 @@ -611,7 +619,7 @@ fi # Smoke imports. ``gi`` is in the list under RTP to confirm PyGObject # built and installed cleanly into the venv. -SMOKE_MODS="cupy yaml scipy.spatial.transform" +SMOKE_MODS="isaaccapture_examples.camera_viz cupy yaml scipy.spatial.transform" # ``websockets`` comes from the cloudxr extra and is what the XR default mode # needs to launch the runtime; check it here so a missing extra fails setup # rather than the first ``run --mode xr``. @@ -654,7 +662,8 @@ if [[ "$MODE" == full ]]; then note "cd $CAMERA_VIZ_DIR" note "./camera_viz.sh run configs/synthetic.yaml --mode window" else - note "sender ready — start it with camera_streamer.py " + note "sender ready — start it with:" + note "$PY -m isaaccapture_examples.camera_viz.camera_streamer " fi # camera_viz itself calls .venv/bin/python directly, but the docs' uv commands diff --git a/examples/camera_viz/scripts/camera-streamer.service.in b/examples/camera_viz/scripts/camera-streamer.service.in index cea9c2ebd3..616eeb177a 100644 --- a/examples/camera_viz/scripts/camera-streamer.service.in +++ b/examples/camera_viz/scripts/camera-streamer.service.in @@ -13,7 +13,7 @@ Wants=network-online.target Type=simple WorkingDirectory={{WORKDIR}} Environment=PATH={{VENV}}/bin:/usr/local/cuda/bin:/usr/bin:/bin -ExecStart={{VENV}}/bin/python {{WORKDIR}}/camera_streamer.py {{CONFIG}}{{EXTRA_ARGS}} +ExecStart={{VENV}}/bin/python -m isaaccapture_examples.camera_viz.camera_streamer {{CONFIG}}{{EXTRA_ARGS}} Restart=always RestartSec=2 StandardOutput=journal diff --git a/examples/camera_viz/scripts/check_cuda.py b/examples/camera_viz/scripts/check_cuda.py index 55f1744ba3..38b0021789 100644 --- a/examples/camera_viz/scripts/check_cuda.py +++ b/examples/camera_viz/scripts/check_cuda.py @@ -15,10 +15,9 @@ import tempfile from typing import NamedTuple -# cuda_env sits beside the entry points, one directory up. Appended, so the -# sample's own modules cannot shadow anything CuPy imports. -sys.path.append(str(Path(__file__).resolve().parents[1])) -import cuda_env # noqa: E402 +# Setup runs this with the system python3, before the sample is installed. +sys.path.append(str(Path(__file__).resolve().parents[1] / "python")) +from isaaccapture_examples.camera_viz import cuda_env # noqa: E402 DEFAULT_ROOT = Path("/usr/local/cuda") SUPPORTED_MAJORS = (12, 13) diff --git a/tests/python/examples/camera_viz/conftest.py b/tests/python/examples/camera_viz/conftest.py index 2c1ec96f82..62370f3018 100644 --- a/tests/python/examples/camera_viz/conftest.py +++ b/tests/python/examples/camera_viz/conftest.py @@ -1,10 +1,12 @@ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# Tests live under tests/python/examples/camera_viz/ but import from -# examples/camera_viz/ (the example's pipeline / sources modules). -# Prepend the parent dir so those imports resolve against the in-tree -# source rather than any installed copy. +# Resolve `isaaccapture_examples.camera_viz` against the in-tree source, and with +# it the codec .so built in place by codec/build.sh. +# +# python/, not python/isaaccapture_examples/: that is a PEP 420 namespace. Do not +# add an __init__.py to make an import work -- it breaks the installed wheel's +# ability to share the namespace. import sys from pathlib import Path @@ -15,4 +17,4 @@ from repo_paths import repo_root # noqa: E402 -sys.path.insert(0, str(repo_root() / "examples" / "camera_viz")) +sys.path.insert(0, str(repo_root() / "examples" / "camera_viz" / "python")) diff --git a/tests/python/examples/camera_viz/test_cloudxr_env.py b/tests/python/examples/camera_viz/test_cloudxr_env.py index 2b242f5ed9..2dc6e162b9 100644 --- a/tests/python/examples/camera_viz/test_cloudxr_env.py +++ b/tests/python/examples/camera_viz/test_cloudxr_env.py @@ -10,7 +10,7 @@ import pytest -import cloudxr_env +from isaaccapture_examples.camera_viz import cloudxr_env # ── YAML -> env ─────────────────────────────────────────────────────── diff --git a/tests/python/examples/camera_viz/test_config.py b/tests/python/examples/camera_viz/test_config.py index ca323d4465..4595795006 100644 --- a/tests/python/examples/camera_viz/test_config.py +++ b/tests/python/examples/camera_viz/test_config.py @@ -6,7 +6,7 @@ import pytest -import config +from isaaccapture_examples.camera_viz import config def test_shape_config_carries_the_equirect_heading(): diff --git a/tests/python/examples/camera_viz/test_controls.py b/tests/python/examples/camera_viz/test_controls.py index dc5d41660b..d0157c07a0 100644 --- a/tests/python/examples/camera_viz/test_controls.py +++ b/tests/python/examples/camera_viz/test_controls.py @@ -14,15 +14,15 @@ import pytest -from controls import ( +from isaaccapture_examples.camera_viz.controls import ( LOCK_MODE_CYCLE, SHAPE_CYCLE, ControllerControls, ControlsConfig, ControlTarget, + controls_config_from_yaml, ) -from controls import controls_config_from_yaml -from placements import ( +from isaaccapture_examples.camera_viz.placements import ( PlacementConfig, build as build_placement, heading_deg, @@ -278,7 +278,7 @@ def test_offset_can_never_reach_divergent_parallax(): must diverge, which they physically cannot do. The ceiling is derived from the measured IPD, so a stick held to the stop stays clear of it however generous the configured limit is.""" - from controls import MAX_OFFSET_FRACTION_OF_IPD + from isaaccapture_examples.camera_viz.controls import MAX_OFFSET_FRACTION_OF_IPD target = _stereo_target(plane_distance=0.0) controls, _ = _make( @@ -712,14 +712,14 @@ def test_held_stick_log_is_throttled(capsys): def test_summarize_says_a_shared_value_once(): """Bindings apply to every camera at once, so values normally agree — repeating them per camera is what overflowed the HUD bar.""" - from controls import summarize + from isaaccapture_examples.camera_viz.controls import summarize assert summarize([("zed", "52.0 mm")]) == "52.0 mm" assert summarize([("a", "52.0 mm"), ("b", "52.0 mm")]) == "52.0 mm (2 cameras)" def test_summarize_lists_cameras_that_disagree(): - from controls import summarize + from isaaccapture_examples.camera_viz.controls import summarize out = summarize([("front", "52.0 mm"), ("rear", "10.0 mm")]) assert "front 52.0 mm" in out and "rear 10.0 mm" in out @@ -727,14 +727,18 @@ def test_summarize_lists_cameras_that_disagree(): def test_summarize_handles_nothing_changing(): - from controls import summarize + from isaaccapture_examples.camera_viz.controls import summarize assert summarize([]) == "" def test_multi_camera_baseline_message_stays_one_line(capsys): """End-to-end: three cameras must not produce three repeated values.""" - from controls.hud import _TEXT_W, _TITLE, split_message + from isaaccapture_examples.camera_viz.controls.hud import ( + _TEXT_W, + _TITLE, + split_message, + ) targets = [_stereo_target(n) for n in ("front", "left", "right")] controls, _ = _make(targets, ControlsConfig(deadzone=0.0)) @@ -757,13 +761,13 @@ def test_multi_camera_baseline_message_stays_one_line(capsys): def test_suggestion_comes_from_the_plane_distance_and_ipd(): """d = ipd * (1 - Z / FAR_TARGET_M): a 1 m plane at a 63 mm IPD suggests 6.3 * 0.9 = 5.67 cm.""" - from controls import FAR_TARGET_M + from isaaccapture_examples.camera_viz.controls import FAR_TARGET_M target = _stereo_target() target.placement_config = PlacementConfig(distance=1.0) controls, _ = _make([target]) controls.step(0.0, 63.0) - from controls import PLANE_DISTANCE_STEP_CM + from isaaccapture_examples.camera_viz.controls import PLANE_DISTANCE_STEP_CM raw = 6.3 * (1.0 - 1.0 / FAR_TARGET_M) expected = round(raw / PLANE_DISTANCE_STEP_CM) * PLANE_DISTANCE_STEP_CM @@ -798,7 +802,7 @@ def test_suggestion_never_exceeds_the_divergence_ceiling(): target.placement_config = PlacementConfig(distance=0.05) # absurdly near controls, _ = _make([target]) controls.step(0.0, 63.0) - from controls import PLANE_DISTANCE_STEP_CM + from isaaccapture_examples.camera_viz.controls import PLANE_DISTANCE_STEP_CM # Rounding to a step can land half a step above the raw ceiling. assert controls._suggested_plane_distance_cm(target) <= ( @@ -845,7 +849,7 @@ def test_stick_still_moves_at_display_frame_rates(): def test_the_layer_only_ever_sees_whole_steps(): - from controls import PLANE_DISTANCE_STEP_CM + from isaaccapture_examples.camera_viz.controls import PLANE_DISTANCE_STEP_CM target = _stereo_target(plane_distance=0.0) controls, _ = _make( @@ -912,7 +916,7 @@ def test_equirect_gap_is_skipped_because_it_does_nothing(): def test_quad_and_cylinder_axes_do_different_things(): """The point of the rework: each shape's two axes must not collapse into one control.""" - from controls import SHAPE_PARAMS + from isaaccapture_examples.camera_viz.controls import SHAPE_PARAMS for shape, (x_name, y_name) in SHAPE_PARAMS.items(): assert x_name != y_name, shape @@ -926,7 +930,7 @@ def test_height_change_moves_the_plane_now_not_at_the_next_resnap(lock_mode): """world cached the finished placement and ignored retuning outright; lazy only recomputed on a re-snap, so a height change sat unapplied until you happened to look away.""" - from placements import PlacementConfig, build + from isaaccapture_examples.camera_viz.placements import PlacementConfig, build head_pos, head_q = (0.0, 1.5, 0.0), (1.0, 0.0, 0.0, 0.0) cfg = PlacementConfig(distance=1.0) @@ -940,7 +944,7 @@ def test_height_change_moves_the_plane_now_not_at_the_next_resnap(lock_mode): @pytest.mark.parametrize("lock_mode", ["world", "head", "gimbal", "lazy"]) def test_size_change_lands_immediately_too(lock_mode): - from placements import PlacementConfig, build + from isaaccapture_examples.camera_viz.placements import PlacementConfig, build head_pos, head_q = (0.0, 1.5, 0.0), (1.0, 0.0, 0.0, 0.0) cfg = PlacementConfig(distance=1.0) @@ -953,7 +957,7 @@ def test_size_change_lands_immediately_too(lock_mode): def test_world_lock_still_pins_the_plane_across_a_retune(): """Retuning must not become an excuse to re-place a world-locked plane.""" - from placements import PlacementConfig, build + from isaaccapture_examples.camera_viz.placements import PlacementConfig, build cfg = PlacementConfig(distance=1.0) strategy = build("world", cfg) diff --git a/tests/python/examples/camera_viz/test_cuda_setup.py b/tests/python/examples/camera_viz/test_cuda_setup.py index 0a42c3402f..3927f87b8d 100644 --- a/tests/python/examples/camera_viz/test_cuda_setup.py +++ b/tests/python/examples/camera_viz/test_cuda_setup.py @@ -2,14 +2,23 @@ # SPDX-License-Identifier: Apache-2.0 """Focused setup regressions; keep rendering coverage in the existing GPU suite.""" +import importlib.util import os import sys from unittest.mock import MagicMock import pytest -import cuda_env -from scripts import check_cuda +from isaaccapture_examples.camera_viz import cuda_env +from repo_paths import repo_root + +# A setup script, not part of the package: setup runs it with the system python3 +# before the venv exists, so load it by path rather than by import name. +_spec = importlib.util.spec_from_file_location( + "check_cuda", repo_root() / "examples" / "camera_viz" / "scripts" / "check_cuda.py" +) +check_cuda = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(check_cuda) def _nvcc(root, release, headers=True): diff --git a/tests/python/examples/camera_viz/test_dashboard.py b/tests/python/examples/camera_viz/test_dashboard.py index e89b1ef9e8..114cd84146 100644 --- a/tests/python/examples/camera_viz/test_dashboard.py +++ b/tests/python/examples/camera_viz/test_dashboard.py @@ -8,7 +8,7 @@ import os import re -from dashboard import CameraRow, Dashboard, Snapshot +from isaaccapture_examples.camera_viz.dashboard import CameraRow, Dashboard, Snapshot ESCAPE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]") diff --git a/tests/python/examples/camera_viz/test_hud.py b/tests/python/examples/camera_viz/test_hud.py index bafc68a462..2de18f3f8d 100644 --- a/tests/python/examples/camera_viz/test_hud.py +++ b/tests/python/examples/camera_viz/test_hud.py @@ -7,8 +7,8 @@ import numpy as np -from controls import hud -from controls.hud import ( +from isaaccapture_examples.camera_viz.controls import hud +from isaaccapture_examples.camera_viz.controls.hud import ( _BODY, _H, _TITLE, diff --git a/tests/python/examples/camera_viz/test_paired_frame_source_race.py b/tests/python/examples/camera_viz/test_paired_frame_source_race.py index 4a353be8ce..e90416d86b 100644 --- a/tests/python/examples/camera_viz/test_paired_frame_source_race.py +++ b/tests/python/examples/camera_viz/test_paired_frame_source_race.py @@ -17,8 +17,8 @@ import time from typing import Optional -from pipeline import Frame, FrameSource, SourceSpec -from sources._helpers import PairedFrameSource +from isaaccapture_examples.camera_viz.pipeline import Frame, FrameSource, SourceSpec +from isaaccapture_examples.camera_viz.sources._helpers import PairedFrameSource class FakeSource(FrameSource): diff --git a/tests/python/examples/camera_viz/test_placements.py b/tests/python/examples/camera_viz/test_placements.py index fd937cb4a4..4bd471da90 100644 --- a/tests/python/examples/camera_viz/test_placements.py +++ b/tests/python/examples/camera_viz/test_placements.py @@ -14,8 +14,8 @@ import pytest -from placements import PlacementConfig, build -from placements._math import rotate_vec +from isaaccapture_examples.camera_viz.placements import PlacementConfig, build +from isaaccapture_examples.camera_viz.placements._math import rotate_vec MODES = ("world", "head", "gimbal", "lazy") diff --git a/tests/python/examples/camera_viz/test_runner_controls.py b/tests/python/examples/camera_viz/test_runner_controls.py index dfd2f0a5aa..8e99fddeaf 100644 --- a/tests/python/examples/camera_viz/test_runner_controls.py +++ b/tests/python/examples/camera_viz/test_runner_controls.py @@ -10,7 +10,7 @@ from __future__ import annotations -from pipeline.runner import VizRunner +from isaaccapture_examples.camera_viz.pipeline.runner import VizRunner class FakeLayer: @@ -115,7 +115,7 @@ def test_the_panel_is_drawn_off_the_submit_thread(): that stops the video feed, so the panel gets its own thread.""" import inspect - from pipeline.runner import VizRunner + from isaaccapture_examples.camera_viz.pipeline.runner import VizRunner submit_src = inspect.getsource(VizRunner._submit_loop_inner) assert "_print_stats" not in submit_src @@ -127,7 +127,7 @@ def test_a_broken_panel_write_does_not_kill_the_stats_thread(monkeypatch): panel is decoration; losing it must not take the stats thread with it.""" import threading - from pipeline import runner as runner_mod + from isaaccapture_examples.camera_viz.pipeline import runner as runner_mod monkeypatch.setattr(runner_mod, "LIVE_STATS_PERIOD_S", 0.005) diff --git a/tests/python/examples/camera_viz/test_runner_health.py b/tests/python/examples/camera_viz/test_runner_health.py index 903e1ad781..f5816de2da 100644 --- a/tests/python/examples/camera_viz/test_runner_health.py +++ b/tests/python/examples/camera_viz/test_runner_health.py @@ -6,7 +6,7 @@ import pytest -from pipeline import VizRunner +from isaaccapture_examples.camera_viz.pipeline import VizRunner class _AliveThread: diff --git a/tests/python/examples/camera_viz/test_synthetic_failures.py b/tests/python/examples/camera_viz/test_synthetic_failures.py index 5186392d53..4dee3116ae 100644 --- a/tests/python/examples/camera_viz/test_synthetic_failures.py +++ b/tests/python/examples/camera_viz/test_synthetic_failures.py @@ -7,7 +7,10 @@ import pytest -from sources.synthetic import SyntheticSource, SyntheticStereoSource +from isaaccapture_examples.camera_viz.sources.synthetic import ( + SyntheticSource, + SyntheticStereoSource, +) @pytest.mark.parametrize("source_type", [SyntheticSource, SyntheticStereoSource]) diff --git a/tests/python/examples/camera_viz/test_synthetic_offscreen.py b/tests/python/examples/camera_viz/test_synthetic_offscreen.py index 1eea586a6a..7a99d2214e 100644 --- a/tests/python/examples/camera_viz/test_synthetic_offscreen.py +++ b/tests/python/examples/camera_viz/test_synthetic_offscreen.py @@ -17,7 +17,7 @@ import isaaccapture.viz as viz -from sources import build_local_camera +from isaaccapture_examples.camera_viz.sources import build_local_camera def _gpu_and_cupy_available() -> bool: diff --git a/tests/python/examples/camera_viz/test_video_file_source.py b/tests/python/examples/camera_viz/test_video_file_source.py index 0b1186218c..52b750d2c5 100644 --- a/tests/python/examples/camera_viz/test_video_file_source.py +++ b/tests/python/examples/camera_viz/test_video_file_source.py @@ -16,7 +16,10 @@ cv2 = pytest.importorskip("cv2") -from sources import build_local_camera, resolve_video_paths # noqa: E402 +from isaaccapture_examples.camera_viz.sources import ( # noqa: E402 + build_local_camera, + resolve_video_paths, +) def _cuda_available() -> bool: