From ec95effabd38c08c3d0bb2d38f93f68f7d697112 Mon Sep 17 00:00:00 2001 From: Jiwen Cai Date: Fri, 28 Aug 2026 04:43:50 +0000 Subject: [PATCH 1/5] examples(camera_viz): package under isaaccapture_examples Package the camera viewer and streamer together so sibling imports, launchers, and the service resolve the installed example. Part of NVIDIA/IsaacCapture#985. Signed-off-by: Jiwen Cai [Ivan Yang: rebased onto main past #1267. camera_streaming.rst keeps main's text with paths and run commands moved to the package layout; README.md keeps main's version; the docs/AGENTS.md rule is dropped because check-doc-refs already rejects stale :code-file: targets.] Signed-off-by: Ivan Yang --- docs/source/references/camera_streaming.rst | 43 ++++++++++--------- examples/camera_viz/camera_viz.sh | 18 ++++---- examples/camera_viz/pyproject.toml | 26 +++++++++-- .../camera_viz/__init__.py | 2 + .../camera_viz/__main__.py | 8 ++++ .../camera_viz}/camera_streamer.py | 6 +-- .../camera_viz}/camera_viz.py | 16 +++---- .../camera_viz}/cloudxr_env.py | 0 .../camera_viz}/codec/CMakeLists.txt | 0 .../camera_viz}/codec/__init__.py | 0 .../camera_viz}/codec/build.sh | 0 .../camera_viz}/codec/codec_bindings.cpp | 0 .../camera_viz}/codec/h264_decoder.cpp | 0 .../camera_viz}/codec/h264_encoder.cpp | 0 .../camera_viz}/codec/inc/h264_decoder.hpp | 0 .../camera_viz}/codec/inc/h264_encoder.hpp | 0 .../camera_viz}/codec/nv12_to_rgba.cu | 0 .../camera_viz}/codec/nv12_to_rgba.cuh | 0 .../camera_viz}/config.py | 6 +-- .../camera_viz}/controls/__init__.py | 0 .../camera_viz}/controls/bindings.py | 2 +- .../camera_viz}/controls/hud.py | 2 +- .../camera_viz}/controls/shapes.py | 2 +- .../camera_viz}/controls/stereo.py | 0 .../camera_viz}/dashboard.py | 0 .../camera_viz}/display.py | 4 +- .../camera_viz}/pipeline/__init__.py | 0 .../camera_viz}/pipeline/interface.py | 0 .../camera_viz}/pipeline/runner.py | 2 +- .../camera_viz}/placements/__init__.py | 0 .../camera_viz}/placements/_math.py | 0 .../camera_viz}/placements/lock_modes.py | 0 .../camera_viz}/sources/__init__.py | 2 +- .../camera_viz}/sources/_helpers.py | 2 +- .../camera_viz}/sources/_nv_decode.py | 2 +- .../camera_viz}/sources/oakd.py | 2 +- .../camera_viz}/sources/rtp_h264.py | 4 +- .../camera_viz}/sources/synthetic.py | 2 +- .../camera_viz}/sources/v4l2.py | 0 .../camera_viz}/sources/video_file.py | 0 .../camera_viz}/sources/zed.py | 2 +- .../camera_viz}/transports/__init__.py | 0 .../transports/_encoder_factory.py | 2 +- .../camera_viz}/transports/_nv12_convert.py | 0 .../camera_viz}/transports/_nv_encode.py | 2 +- .../camera_viz}/transports/_nv_encode_gst.py | 0 .../transports/rtp_h264_receiver.py | 0 .../camera_viz}/transports/rtp_h264_sender.py | 0 examples/camera_viz/scripts/_install_deps.sh | 11 ++++- .../scripts/camera-streamer.service.in | 2 +- tests/python/examples/camera_viz/conftest.py | 12 +++--- .../test_paired_frame_source_race.py | 4 +- .../examples/camera_viz/test_runner_health.py | 2 +- .../camera_viz/test_synthetic_offscreen.py | 2 +- .../camera_viz/test_video_file_source.py | 5 ++- 55 files changed, 115 insertions(+), 80 deletions(-) create mode 100644 examples/camera_viz/python/isaaccapture_examples/camera_viz/__init__.py create mode 100644 examples/camera_viz/python/isaaccapture_examples/camera_viz/__main__.py rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/camera_streamer.py (99%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/camera_viz.py (98%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/cloudxr_env.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/codec/CMakeLists.txt (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/codec/__init__.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/codec/build.sh (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/codec/codec_bindings.cpp (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/codec/h264_decoder.cpp (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/codec/h264_encoder.cpp (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/codec/inc/h264_decoder.hpp (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/codec/inc/h264_encoder.hpp (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/codec/nv12_to_rgba.cu (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/codec/nv12_to_rgba.cuh (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/config.py (98%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/controls/__init__.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/controls/bindings.py (99%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/controls/hud.py (99%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/controls/shapes.py (99%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/controls/stereo.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/dashboard.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/display.py (98%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/pipeline/__init__.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/pipeline/interface.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/pipeline/runner.py (99%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/placements/__init__.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/placements/_math.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/placements/lock_modes.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/sources/__init__.py (99%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/sources/_helpers.py (99%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/sources/_nv_decode.py (98%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/sources/oakd.py (99%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/sources/rtp_h264.py (99%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/sources/synthetic.py (99%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/sources/v4l2.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/sources/video_file.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/sources/zed.py (99%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/transports/__init__.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/transports/_encoder_factory.py (98%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/transports/_nv12_convert.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/transports/_nv_encode.py (98%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/transports/_nv_encode_gst.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/transports/rtp_h264_receiver.py (100%) rename examples/camera_viz/{ => python/isaaccapture_examples/camera_viz}/transports/rtp_h264_sender.py (100%) diff --git a/docs/source/references/camera_streaming.rst b/docs/source/references/camera_streaming.rst index ad3c45387c..57511b1e83 100644 --- a/docs/source/references/camera_streaming.rst +++ b/docs/source/references/camera_streaming.rst @@ -596,8 +596,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 @@ -637,7 +637,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:: @@ -870,35 +870,36 @@ Televiz as the compositor at the end of the chain: 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) + ├── 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 + │ ├── 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..47b572150a 100755 --- a/examples/camera_viz/camera_viz.sh +++ b/examples/camera_viz/camera_viz.sh @@ -198,7 +198,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 +209,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 +240,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 +256,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 +304,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 @@ -441,9 +441,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..075f1c9d9c 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,25 @@ 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", ] -# 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). +# `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 `camera_viz/`. +# +# 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. +[tool.hatch.build.targets.wheel] +only-include = ["python/isaaccapture_examples/camera_viz"] +sources = ["python"] + [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 99% rename from examples/camera_viz/camera_streamer.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/camera_streamer.py index f0d1b2e7f8..eae755f19c 100755 --- a/examples/camera_viz/camera_streamer.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/camera_streamer.py @@ -29,14 +29,14 @@ import yaml -from pipeline import FrameSource -from sources import ( +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 e0b739510d..ccf53b78f4 100755 --- a/examples/camera_viz/camera_viz.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/camera_viz.py @@ -30,18 +30,16 @@ from isaaccapture import logging_config from isaaccapture.cloudxr import CloudXRLauncher -import cloudxr_env -import config -import display -from controls import ( +from . import cloudxr_env, config, 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]]): @@ -390,7 +388,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 100% rename from examples/camera_viz/codec/__init__.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/__init__.py 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 100% rename from examples/camera_viz/controls/__init__.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/__init__.py 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/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 100% rename from examples/camera_viz/pipeline/__init__.py rename to examples/camera_viz/python/isaaccapture_examples/camera_viz/pipeline/__init__.py 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 98% 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..3cacc0e557 100644 --- a/examples/camera_viz/sources/_nv_decode.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/_nv_decode.py @@ -36,7 +36,7 @@ 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 " 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 98% 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..cd1a7e0142 100644 --- a/examples/camera_viz/transports/_encoder_factory.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_encoder_factory.py @@ -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 98% 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..adb503787f 100644 --- a/examples/camera_viz/transports/_nv_encode.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_nv_encode.py @@ -45,7 +45,7 @@ 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 " 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 17f7d8836f..30063367e8 100755 --- a/examples/camera_viz/scripts/_install_deps.sh +++ b/examples/camera_viz/scripts/_install_deps.sh @@ -560,6 +560,13 @@ 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. +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. @@ -586,7 +593,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 @@ -600,7 +607,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``. 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/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_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_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_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: From 34b90262d1498fcf4e8fee72682786c77675d91d Mon Sep 17 00:00:00 2001 From: Jiwen Cai Date: Fri, 18 Sep 2026 17:10:44 +0000 Subject: [PATCH 2/5] examples(camera_viz): fix flat imports in tests for the moved modules Keep the existing camera tests importing modules from the packaged namespace. Part of NVIDIA/IsaacCapture#985. Signed-off-by: Jiwen Cai Signed-off-by: Ivan Yang --- .../examples/camera_viz/test_cloudxr_env.py | 2 +- .../python/examples/camera_viz/test_config.py | 2 +- .../examples/camera_viz/test_controls.py | 36 ++++++++++--------- .../examples/camera_viz/test_dashboard.py | 2 +- tests/python/examples/camera_viz/test_hud.py | 4 +-- .../examples/camera_viz/test_placements.py | 4 +-- .../camera_viz/test_runner_controls.py | 6 ++-- 7 files changed, 30 insertions(+), 26 deletions(-) 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_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_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) From 615f4f6a51ca65066b8d56863831858b759e6789 Mon Sep 17 00:00:00 2001 From: Ivan Yang Date: Tue, 27 Oct 2026 16:39:08 -0700 Subject: [PATCH 3/5] examples(camera_viz): fix what the package move left behind Point the paths and run commands that landed on main after the move, or that the move missed, at the packaged layout, and close the gaps that a standalone install of the package exposes: - camera_streaming.rst: viewer flags and the manual streamer start use `python -m isaaccapture_examples.camera_viz[.camera_streamer]`; setup is documented to install the sample itself after resolving isaaccapture, and troubleshooting covers `No module named 'isaaccapture_examples'`. - Root AGENTS.md: the no-isaaccapture logging exception names the moved streamer module. - pyproject.toml: a `viewer` extra declares isaaccapture[cloudxr] and Pillow, which the viewer imports and the sender must run without; the wheel excludes the codec's in-place build tree and .so. - camera_viz.sh: run and loopback stop with "rerun setup" when the venv predates the package. - _install_deps.sh: the sender-only closing note gives the module command, the pillow comment names controls/hud.py, and the install step says why it passes --no-deps. - Native-codec error hints, the encoder-factory, viewer and controls docstrings, and the pipeline package's AttributeError use the new paths and module names. - test_synthetic_failures.py imports through the package; test_cuda_setup.py loads scripts/check_cuda.py by path, since setup runs that script with the system python3 before the venv or the package exists. Part of NVIDIA/IsaacCapture#985. Signed-off-by: Ivan Yang --- AGENTS.md | 7 +-- docs/source/references/camera_streaming.rst | 52 +++++++++++-------- examples/camera_viz/camera_viz.sh | 4 ++ examples/camera_viz/pyproject.toml | 10 ++-- .../camera_viz/camera_viz.py | 2 +- .../camera_viz/codec/__init__.py | 3 +- .../camera_viz/controls/__init__.py | 2 +- .../camera_viz/pipeline/__init__.py | 2 +- .../camera_viz/sources/_nv_decode.py | 2 +- .../camera_viz/transports/_encoder_factory.py | 4 +- .../camera_viz/transports/_nv_encode.py | 2 +- examples/camera_viz/scripts/_install_deps.sh | 8 +-- .../examples/camera_viz/test_cuda_setup.py | 11 +++- .../camera_viz/test_synthetic_failures.py | 5 +- 14 files changed, 72 insertions(+), 42 deletions(-) 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 57511b1e83..ab595b91ca 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 using ``CUDA_PATH``, the toolkit containing ``nvcc`` on ``PATH``, or ``/usr/local/cuda``, in that order. It checks the loaded NVRTC version and executes a GPU @@ -190,7 +192,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]`` @@ -206,7 +209,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: @@ -685,8 +689,8 @@ interactively keeps it out of shell history and the command line. ``--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 @@ -819,6 +823,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`. @@ -869,22 +875,22 @@ Televiz as the compositor at the end of the chain: :class: code-100col camera_viz/ - ├── camera_viz.sh — CLI: setup / loopback / run / deploy / service-* - ├── 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_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 + │ ├── 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 └── scripts/ ├── _install_deps.sh — installer (setup + deploy) └── camera-streamer.service.in — systemd unit template diff --git a/examples/camera_viz/camera_viz.sh b/examples/camera_viz/camera_viz.sh index 47b572150a..34efd6adf3 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 diff --git a/examples/camera_viz/pyproject.toml b/examples/camera_viz/pyproject.toml index 075f1c9d9c..e93cc56387 100644 --- a/examples/camera_viz/pyproject.toml +++ b/examples/camera_viz/pyproject.toml @@ -47,10 +47,12 @@ rtp = [ # 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 ./python/isaaccapture_examples/camera_viz/codec/. -# Build with -# its build.sh; ``./camera_viz.sh setup`` does this automatically. +# Build with its build.sh; ``./camera_viz.sh setup`` does this automatically. # Requires CUDA toolkit + NVIDIA driver (libnvidia-encode + libnvcuvid). # `isaaccapture_examples` is a PEP 420 namespace shared by every example dist and @@ -59,10 +61,12 @@ rtp = [ # bare top-level `camera_viz/`. # # 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. +# 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/camera_viz.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/camera_viz.py index ccf53b78f4..2fddb89353 100755 --- a/examples/camera_viz/python/isaaccapture_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 diff --git a/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/__init__.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/codec/__init__.py index bcd20c41fd..9d01b6ce70 100644 --- a/examples/camera_viz/python/isaaccapture_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/python/isaaccapture_examples/camera_viz/controls/__init__.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/controls/__init__.py index 33c391e760..87379a10af 100644 --- a/examples/camera_viz/python/isaaccapture_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/python/isaaccapture_examples/camera_viz/pipeline/__init__.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/pipeline/__init__.py index 4550408cd6..f0c610bd98 100644 --- a/examples/camera_viz/python/isaaccapture_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/python/isaaccapture_examples/camera_viz/sources/_nv_decode.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/_nv_decode.py index 3cacc0e557..80e2a0178a 100644 --- a/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/_nv_decode.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/sources/_nv_decode.py @@ -40,7 +40,7 @@ def _ensure_initialized(self) -> None: 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/python/isaaccapture_examples/camera_viz/transports/_encoder_factory.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_encoder_factory.py index cd1a7e0142..7bf26a8b1f 100644 --- a/examples/camera_viz/python/isaaccapture_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. diff --git a/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_nv_encode.py b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_nv_encode.py index adb503787f..b69a6b6e0b 100644 --- a/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_nv_encode.py +++ b/examples/camera_viz/python/isaaccapture_examples/camera_viz/transports/_nv_encode.py @@ -49,7 +49,7 @@ def _ensure_initialized(self, rgba_device_id: int) -> None: 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/scripts/_install_deps.sh b/examples/camera_viz/scripts/_install_deps.sh index 30063367e8..20c88496fa 100755 --- a/examples/camera_viz/scripts/_install_deps.sh +++ b/examples/camera_viz/scripts/_install_deps.sh @@ -529,7 +529,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") @@ -563,7 +563,8 @@ 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. +# 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 @@ -650,7 +651,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/tests/python/examples/camera_viz/test_cuda_setup.py b/tests/python/examples/camera_viz/test_cuda_setup.py index fe6d974751..fd6e3876c1 100644 --- a/tests/python/examples/camera_viz/test_cuda_setup.py +++ b/tests/python/examples/camera_viz/test_cuda_setup.py @@ -2,12 +2,21 @@ # SPDX-License-Identifier: Apache-2.0 """Focused setup regressions; keep rendering coverage in the existing GPU suite.""" +import importlib.util import sys from unittest.mock import MagicMock import pytest -from scripts import check_cuda +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) @pytest.mark.parametrize("version", ["12.6", "13.0"]) 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]) From acd5273bc549fe4c7b2d4f9707d78c10ae869274 Mon Sep 17 00:00:00 2001 From: Ivan Yang Date: Tue, 27 Oct 2026 18:11:30 -0700 Subject: [PATCH 4/5] examples(camera_viz): point the packaging comment at examples/README.md State the wheel-layout rule once, in examples/README.md, and reference it the way the converted examples on main do. Part of NVIDIA/IsaacCapture#985. Signed-off-by: Ivan Yang --- examples/camera_viz/pyproject.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/camera_viz/pyproject.toml b/examples/camera_viz/pyproject.toml index e93cc56387..709e6f38b6 100644 --- a/examples/camera_viz/pyproject.toml +++ b/examples/camera_viz/pyproject.toml @@ -55,10 +55,8 @@ viewer = ["isaaccapture[cloudxr]", "pillow>=10.0"] # Build with its build.sh; ``./camera_viz.sh setup`` does this automatically. # Requires CUDA toolkit + NVIDIA driver (libnvidia-encode + libnvcuvid). -# `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 `camera_viz/`. +# `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 From 1b5ca02a7157e8d915e375d8bbb9a9c00367115b Mon Sep 17 00:00:00 2001 From: Ivan Yang Date: Tue, 27 Oct 2026 18:47:52 -0700 Subject: [PATCH 5/5] examples(camera_viz): restart the streamer service on redeploy `systemctl --user enable --now` leaves an already-running service on the previous unit, so a redeploy that changes ExecStart (as the package move does) kept the old streamer running. Enable the unit, then restart it. Part of NVIDIA/IsaacCapture#985. Signed-off-by: Ivan Yang --- docs/source/references/camera_streaming.rst | 4 ++-- examples/camera_viz/camera_viz.sh | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/source/references/camera_streaming.rst b/docs/source/references/camera_streaming.rst index ab595b91ca..b49a4d1b58 100644 --- a/docs/source/references/camera_streaming.rst +++ b/docs/source/references/camera_streaming.rst @@ -684,8 +684,8 @@ 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 diff --git a/examples/camera_viz/camera_viz.sh b/examples/camera_viz/camera_viz.sh index 34efd6adf3..ca81d7d2c8 100755 --- a/examples/camera_viz/camera_viz.sh +++ b/examples/camera_viz/camera_viz.sh @@ -361,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"