Skip to content

Support argus camera for camera_viz - #833

Open
tengw-nv wants to merge 1 commit into
mainfrom
teng/df82f4a
Open

Support argus camera for camera_viz#833
tengw-nv wants to merge 1 commit into
mainfrom
teng/df82f4a

Conversation

@tengw-nv

@tengw-nv tengw-nv commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

Implement argus camera driver support in the camera_viz example

Fixes #(issue)

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Testing

Tested with camera that supports argus driver

Checklist

  • I have read and understood the contribution guidelines
  • I have run the linter and formatter with SKIP=check-copyright-year pre-commit run --all-files
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix/feature works (or explained why not)
  • I have signed off all my commits (git commit -s) per the DCO

Summary by CodeRabbit

  • New Features

    • Added Jetson Argus camera support with mono and stereo capture.
    • Added CUDA-accelerated YUV-to-RGBA frame conversion.
    • Added SHW5G stereo camera configuration.
    • Added optional --with-argus setup support and improved setup guidance.
    • Added stereo emission mode that waits for fresh frames from both eyes.
  • Bug Fixes

    • Prevented partial startup when one stereo camera fails to start.
    • Added clearer guidance when the native camera component is unavailable.
  • Tests

    • Added coverage for synchronized stereo frame emission.

@tengw-nv
tengw-nv requested a review from farbod-nv July 28, 2026 21:59
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c930e9d1-2aee-4ddc-94cf-520f4fc52ba9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an optional Jetson Argus camera backend with C++/CUDA capture, YUV-to-RGBA conversion, pybind11 bindings, and Python FrameSource integration. Argus supports mono and stereo sensors, CUDA-backed frame views, configurable paired emission, and SHW5G configuration. Build and installation scripts now support opt-in Argus provisioning, while documentation and deployment help describe the new options.

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.51% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding Argus camera support to camera_viz.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch teng/df82f4a

Comment @coderabbitai help to get the list of available commands.

@tengw-nv
tengw-nv requested a review from jiwenc-nv July 28, 2026 21:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/camera_viz/argus/argus_camera.cpp`:
- Around line 783-799: Prevent published device buffers from being reused while
the consumer still holds them: update the latest() buffer handoff and
pick_write_index() selection to track the index currently leased by the consumer
and exclude it from writes until release, or implement an equivalent FrameView
lease/refcount lifecycle. Preserve existing publication behavior while ensuring
producer reuse waits for or skips any buffer still in use.
- Around line 731-753: Update the CU_EGL_FRAME_TYPE_PITCH handling around
launch_yuv420_pitch_to_rgba so planar U/V strides are not derived from
frame.pitch / 2. Read each plane’s pitch from the available buffer metadata and
pass those per-plane values, or explicitly reject planar pitch frames when
metadata is unavailable; preserve the existing non-planar path.
- Around line 671-684: Update the acquire call in the frame acquisition flow
around cuEGLStreamConsumerAcquireFrame to pass config_.acquire_timeout_ms
converted from milliseconds to microseconds. Treat both CUDA_ERROR_TIMEOUT and
CUDA_ERROR_LAUNCH_TIMEOUT as retryable timeout results that return false, while
preserving existing handling for other CUDA errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8c1004b5-6560-4cd1-a0a7-d91a0c7813c4

📥 Commits

Reviewing files that changed from the base of the PR and between 9f1df1a and e222bb2.

📒 Files selected for processing (16)
  • examples/camera_viz/README.md
  • examples/camera_viz/argus/CMakeLists.txt
  • examples/camera_viz/argus/__init__.py
  • examples/camera_viz/argus/argus_bindings.cpp
  • examples/camera_viz/argus/argus_camera.cpp
  • examples/camera_viz/argus/argus_camera.hpp
  • examples/camera_viz/argus/build.sh
  • examples/camera_viz/argus/yuv_to_rgba.cu
  • examples/camera_viz/argus/yuv_to_rgba.cuh
  • examples/camera_viz/camera_viz.sh
  • examples/camera_viz/configs/argus_shw5g.yaml
  • examples/camera_viz/scripts/_install_deps.sh
  • examples/camera_viz/sources/__init__.py
  • examples/camera_viz/sources/_helpers.py
  • examples/camera_viz/sources/argus.py
  • examples/camera_viz/tests/test_paired_frame_source_race.py

Comment on lines +671 to +684
CUresult result = cuEGLStreamConsumerAcquireFrame(
&stream.connection, &out.resource, &out.stream, config_.acquire_timeout_ms);
if (result == CUDA_ERROR_TIMEOUT)
{
return false;
}
if (result != CUDA_SUCCESS)
{
if (!running_.load())
{
return false;
}
check_cuda(result, "cuEGLStreamConsumerAcquireFrame");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file and inspect the relevant slice with line numbers.
ast-grep outline examples/camera_viz/argus/argus_camera.cpp --view expanded || true
sed -n '640,710p' examples/camera_viz/argus/argus_camera.cpp

# Find the config definition and any timeout-related helpers/usages.
rg -n "acquire_timeout_ms|CUDA_ERROR_LAUNCH_TIMEOUT|CUDA_ERROR_TIMEOUT|cuEGLStreamConsumerAcquireFrame|CUDA_EGL_INFINITE_TIMEOUT" -S .

Repository: NVIDIA/IsaacTeleop

Length of output: 5645


🌐 Web query:

CUDA EGLStreamConsumerAcquireFrame timeout microseconds CUDA_ERROR_LAUNCH_TIMEOUT documentation

💡 Result:

In the CUDA EGLStream API, the function cuEGLStreamConsumerAcquireFrame (and its runtime equivalent cudaEGLStreamConsumerAcquireFrame) takes a timeout parameter specified in microseconds (usec) [1][2]. This parameter defines the maximum time the consumer will wait for a new frame to be acquired from the EGLStream [2]. Regarding CUDA_ERROR_LAUNCH_TIMEOUT: 1. Usage and Context: While the function documentation highlights the timeout parameter, CUDA_ERROR_LAUNCH_TIMEOUT is explicitly cited as a possible return value for operations involving EGLStream connections [2][3]. Specifically, this error can occur if a consumer has not returned a frame to the EGLStream or if an acquisition attempt times out [2][4]. 2. Error Handling: When CUDA_ERROR_LAUNCH_TIMEOUT is returned, it indicates that the system failed to acquire or release a frame within the expected time window [2][3]. NVIDIA documentation suggests that in such cases, the application can safely retry the operation [2][5]. 3. Timeout Semantics: If the timeout parameter is set to CUDA_EGL_INFINITE_TIMEOUT, the acquisition call will wait indefinitely for a frame [2][4]. If a standard timeout value is set and the operation expires, the CUDA consumer may attempt to acquire an older frame if the EGL_SUPPORT_REUSE_NV flag is set (which is enabled by default) [2][4]. For developers, it is important to note that the timeout mechanism is not designed to be shorter than the producer's frame rate interval [6]. Setting a timeout value smaller than the expected time between frames may lead to frequent CUDA_ERROR_LAUNCH_TIMEOUT results or unwanted acquisition of stale frames [6][2].

Citations:


🌐 Web query:

site:docs.nvidia.com cuEGLStreamConsumerAcquireFrame timeout usec return CUDA_ERROR_LAUNCH_TIMEOUT

💡 Result:

The return value CUDA_ERROR_LAUNCH_TIMEOUT when calling cuEGLStreamConsumerAcquireFrame (or its runtime API equivalent, cudaEGLStreamConsumerAcquireFrame) indicates that the consumer has not successfully acquired a new frame from the EGLStream within the specified timeout duration [1][2][3]. Key details regarding this error and API behavior: 1. Meaning: This error is explicitly documented to occur if the consumer has not returned a frame to the EGL stream before the timeout [1][3]. 2. Recommended Action: If this error is returned, the application is expected to retry the operation [1][2][4]. 3. Timeout Parameter: The timeout parameter is specified in microseconds (usec) [1][2][4]. Setting this parameter to CUDA_EGL_INFINITE_TIMEOUT (defined as 0xFFFFFFFF) causes the call to wait indefinitely [2][4][5]. 4. Frame Reuse: After the timeout occurs, the CUDA consumer may still attempt to acquire an "old" frame if one is available and the EGL_SUPPORT_REUSE_NV flag is set (which is enabled by default) [2][3][4]. If you wish to disable this behavior, you must explicitly set EGL_SUPPORT_REUSE_NV to EGL_FALSE during EGLStream initialization [3][6][4]. For further implementation details, refer to the official NVIDIA CUDA Toolkit documentation under the EGL Interoperability section [1][3][4].

Citations:


Convert the acquire timeout to microseconds and handle CUDA_ERROR_LAUNCH_TIMEOUT as a retryable timeout. cuEGLStreamConsumerAcquireFrame expects a usec timeout, but config_.acquire_timeout_ms is passed through unchanged here, so any finite timeout is 1000× too short. The timeout branch is also checking the wrong error code; a normal timeout can fall into check_cuda() and stop the producer thread.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/camera_viz/argus/argus_camera.cpp` around lines 671 - 684, Update
the acquire call in the frame acquisition flow around
cuEGLStreamConsumerAcquireFrame to pass config_.acquire_timeout_ms converted
from milliseconds to microseconds. Treat both CUDA_ERROR_TIMEOUT and
CUDA_ERROR_LAUNCH_TIMEOUT as retryable timeout results that return false, while
preserving existing handling for other CUDA errors.

Comment on lines +731 to +753
if (frame.frameType == CU_EGL_FRAME_TYPE_PITCH)
{
const auto* y_plane = static_cast<const uint8_t*>(frame.frame.pPitch[0]);
const auto* uv_or_u_plane = static_cast<const uint8_t*>(frame.frame.pPitch[1]);
const auto* v_plane = planar ? static_cast<const uint8_t*>(frame.frame.pPitch[2]) : nullptr;
const int y_pitch = static_cast<int>(frame.pitch);
const int uv_pitch = static_cast<int>(planar ? frame.pitch / 2 : frame.pitch);
const int v_pitch = static_cast<int>(planar ? frame.pitch / 2 : 0);
launch_yuv420_pitch_to_rgba(y_plane,
uv_or_u_plane,
v_plane,
y_pitch,
uv_pitch,
v_pitch,
config_.width,
config_.height,
dest.ptr,
static_cast<int>(dest.pitch),
layout,
full_range,
reinterpret_cast<cudaStream_t>(convert_stream_));
return pending_textures;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does CUeglFrame pitch apply to chroma planes for YUV420 planar CU_EGL_FRAME_TYPE_PITCH frames on Jetson?

💡 Result:

For CUeglFrame structures of type CU_EGL_FRAME_TYPE_PITCH used on NVIDIA Jetson platforms, the pitch field in the CUeglFrame structure strictly refers only to the pitch of the first plane (typically the Y plane for YUV formats) [1][2][3]. It does not apply to subsequent chroma planes [1][2]. Authoritative NVIDIA documentation and developer guidance specify that for multiplanar EGL frames, only the pitch of the first plane is to be considered by the application [1][2]. The pitch values, if any, associated with additional planes (such as U or V in YUV420) within the CUeglFrame structure cannot be relied upon and are often incorrect [2][4]. To obtain the correct pitch for secondary planes (chroma planes) on Jetson, you should use platform-specific APIs such as NvBufferGetParams from the Jetson Multimedia API, which correctly retrieves the pitch for each individual plane [1][2][4]. Relying on calculations based solely on the primary plane's pitch or the values populated in the CUeglFrame structure for secondary planes will generally lead to incorrect memory access and data corruption (e.g., visual artifacts/striping) [2][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant function and any nearby pitch handling.
file="examples/camera_viz/argus/argus_camera.cpp"

echo "=== Context around lines 700-780 ==="
sed -n '700,780p' "$file" | cat -n

echo
echo "=== Search for planar/pitch handling in this file ==="
rg -n "planar|uv_pitch|v_pitch|CU_EGL_FRAME_TYPE_PITCH|pPitch\\[" "$file"

Repository: NVIDIA/IsaacTeleop

Length of output: 5532


Don’t derive planar chroma pitch from frame.pitch / 2. CUeglFrame::pitch only covers the first plane; for planar YUV420 frames, U/V can have different strides. This can misindex chroma and read past the buffer. Fetch per-plane pitches from the buffer metadata or reject planar CU_EGL_FRAME_TYPE_PITCH frames.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/camera_viz/argus/argus_camera.cpp` around lines 731 - 753, Update
the CU_EGL_FRAME_TYPE_PITCH handling around launch_yuv420_pitch_to_rgba so
planar U/V strides are not derived from frame.pitch / 2. Read each plane’s pitch
from the available buffer metadata and pass those per-plane values, or
explicitly reject planar pitch frames when metadata is unavailable; preserve the
existing non-planar path.

Comment on lines +783 to +799
void ArgusCamera::publish(uint32_t write_idx, uint64_t timestamp_ns)
{
std::lock_guard<std::mutex> guard(publish_mutex_);
publish_idx_ = static_cast<int>(write_idx);
published_timestamp_ns_ = timestamp_ns;
++published_sequence_;
}

uint32_t ArgusCamera::pick_write_index() const
{
std::lock_guard<std::mutex> guard(publish_mutex_);
if (publish_idx_ < 0)
{
return 0;
}
return static_cast<uint32_t>((publish_idx_ + 1) % 3);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Published buffers can be overwritten while the Python consumer still reads them.

latest() (Line 286-310) hands raw device pointers out of buffers_[eye][publish_idx_], and sources/argus.py keeps using them (CUDA view on stream 0) after publish_mutex_ is released. pick_write_index() only avoids the currently published index, so after two more publishes the producer wraps back onto the buffer the consumer is still reading — an unsynchronized producer/consumer race on device memory, visible as torn frames under any consumer stall.

Either track the index handed out by latest() and exclude it from pick_write_index(), or give FrameView an explicit lease (release call / refcount) that the producer honours before reusing a slot.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/camera_viz/argus/argus_camera.cpp` around lines 783 - 799, Prevent
published device buffers from being reused while the consumer still holds them:
update the latest() buffer handoff and pick_write_index() selection to track the
index currently leased by the consumer and exclude it from writes until release,
or implement an equivalent FrameView lease/refcount lifecycle. Preserve existing
publication behavior while ensuring producer reuse waits for or skips any buffer
still in use.

// Argus YUV420 -> RGBA8 conversion for CUDA EGLStream consumers.

#include "yuv_to_rgba.cuh"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a bit funny what we till need to hand roll rgh/yuv conversion... maybe we can move this to viz lib in the future.

@farbod-nv

farbod-nv commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
  1. deploy deletes the Argus module and never rebuilds it — camera_viz.sh:283

  cmd_deploy hardcodes bash scripts/_install_deps.sh --sender-only --jetson with no --with-argus, and there's no flag to
  inject one. Worse, rsync_to_remote (camera_viz.sh:110-113) excludes argus/build/ but the built artifact lands in the source
  dir (argus/CMakeLists.txt:114 LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}), and argus/_camera_viz_argus*.so is not
  excluded. So rsync --delete removes a working .so from the Jetson on every deploy, and the deps step doesn't build it back.
  The Jetson deploy path is exactly the Argus target. Needs --exclude='argus/*.so' plus a way to pass --with-argus through
  deploy.

  2. Missing sync between EGL acquire and the conversion kernel — argus_camera.cpp:672

  AcquiredFrame::stream is default-initialized to nullptr, so cuEGLStreamConsumerAcquireFrame(..., &out.stream, ...) orders
  the frame's availability/migration on the legacy default stream. The kernel then launches on convert_stream_, created with
  CU_STREAM_NON_BLOCKING (:349) — which by definition does not implicitly synchronize with the legacy default stream. Set
  out.stream = convert_stream_ before the acquire (and pass the same stream to release), or drop CU_STREAM_NON_BLOCKING.

^ just an agent review. ignore if already addressed/irrelevent.

Let's also add argus to the public docs too. Otherwise LGTM

Signed-off-by: Teng Wei <tengw@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants