Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions docs/user_guide/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,57 @@ lastly, running SIPL accelerated network python example on AGX Thor:
$ python3 ./examples/sipl_player.py --json-config ./examples/sipl_config/vb1940_single.json
```

## RealSense D555 player example

This example is similar to the IMX274 player example above, using Realsense D555
camera instead of IMX274. To run the high-speed video player with Realsense D555, in
the demo container with a ConnectX accelerated network controller,

```sh
$ python3 examples/linux_d555_player.py
$ python3 examples/linux_d555_dual_stream.py
$ python3 examples/linux_d555_peoplenet.py
```

### Known Limitations

1. **Resolution Changes**: Switching resolutions (not FPS) requires a camera reboot. The first resolution set after reboot will be used.

2. **IP Address Discovery**: The camera IP may not be the default `192.168.0.2`. To discover the actual IP address, run the `hololink-enumerate` tool from within the Docker container:

```sh
$ hololink-enumerate
```

The output should display information similar to:

```
mac_id=98:4F:EE:1A:F4:A9 hsb_ip_version=0x2501 fpga_crc=0xffff ip_address=192.168.11.55 fpga_uuid=889b7ce3-65a5-4247-8b05-4ff1904c3359 serial_number=70255f4343534c interface=eno1 board=hololink-lite
mac_id=98:4F:EE:1A:F4:A9 hsb_ip_version=0x2501 fpga_crc=0xffff ip_address=192.168.11.55 fpga_uuid=889b7ce3-65a5-4247-8b05-4ff1904c3359 serial_number=70255f4343534c interface=eno1 board=hololink-lite
mac_id=98:4F:EE:1A:F4:A9 hsb_ip_version=0x2501 fpga_crc=0xffff ip_address=192.168.11.55 fpga_uuid=889b7ce3-65a5-4247-8b05-4ff1904c3359 serial_number=70255f4343534c interface=eno1 board=hololink-lite
^C
```
Comment on lines +118 to +123

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language specifier to the code block.

The fenced code block showing the hololink-enumerate output is missing a language specifier. Add text or console to comply with markdown linting rules.

📝 Proposed fix
-```
+```text
 mac_id=98:4F:EE:1A:F4:A9 hsb_ip_version=0x2501 fpga_crc=0xffff ip_address=192.168.11.55 fpga_uuid=889b7ce3-65a5-4247-8b05-4ff1904c3359 serial_number=70255f4343534c interface=eno1 board=hololink-lite
 mac_id=98:4F:EE:1A:F4:A9 hsb_ip_version=0x2501 fpga_crc=0xffff ip_address=192.168.11.55 fpga_uuid=889b7ce3-65a5-4247-8b05-4ff1904c3359 serial_number=70255f4343534c interface=eno1 board=hololink-lite
 mac_id=98:4F:EE:1A:F4:A9 hsb_ip_version=0x2501 fpga_crc=0xffff ip_address=192.168.11.55 fpga_uuid=889b7ce3-65a5-4247-8b05-4ff1904c3359 serial_number=70255f4343534c interface=eno1 board=hololink-lite
 ^C

</details>

As per coding guidelines, all markdown files must be formatted according to `ci/lint.sh` rules.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.18.1)</summary>

118-118: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

In @docs/user_guide/examples.md around lines 118 - 123, The fenced code block
that shows the hololink-enumerate output is missing a language specifier; update
the triple-backtick opening fence for the block containing the
hololink-enumerate output (the example showing mac_id=98:4F:EE:1A:F4:A9 ... and
^C) to include a language such as text or console (e.g., change totext)
so the markdown complies with lint rules.


</details>

<!-- fingerprinting:phantom:poseidon:puma -->

<!-- This is an auto-generated comment by CodeRabbit -->


Note the `ip_address` and `interface` fields from the output.

### Host Network Configuration

To configure the host subnet to match the camera's IP address and enable communication:

```sh
EN0=eno1 # Replace with your interface name from hololink-enumerate output
sudo nmcli con add con-name hololink-$EN0 ifname $EN0 type ethernet ip4 192.168.11.101/24
sudo nmcli connection up hololink-$EN0
```

Replace `192.168.11.101/24` with an IP address in the same subnet as your camera's IP address (e.g., if camera is `192.168.11.55`, use `192.168.11.101/24`).

After configuration, verify connectivity by pinging the camera:

```sh
$ ping 192.168.11.55 # Replace with your camera's IP address
```

## Running the TAO PeopleNet example

The tao-peoplenet example demonstrates running inference on a live video feed.
Expand Down
322 changes: 322 additions & 0 deletions examples/linux_d555_dual_stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
# SPDX-FileCopystream2Text: Copystream2 (c) 2023-2024 NVIDIA CORPORATION & AFFILIATES. All stream2s reserved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typos in license header.

The license header contains copy-paste errors: "FileCopystream2Text" should be "FileCopyrightText", and "stream2s reserved" should be "rights reserved".

Proposed fix
-# SPDX-FileCopystream2Text: Copystream2 (c) 2023-2024 NVIDIA CORPORATION & AFFILIATES. All stream2s reserved.
+# SPDX-FileCopyrightText: Copyright (c) 2023-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# SPDX-FileCopystream2Text: Copystream2 (c) 2023-2024 NVIDIA CORPORATION & AFFILIATES. All stream2s reserved.
# SPDX-FileCopyrightText: Copyright (c) 2023-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
🤖 Prompt for AI Agents
In @examples/linux_d555_dual_stream.py at line 1, Fix the typos in the file
license header: change "FileCopystream2Text" to "FileCopyrightText" and replace
"stream2s reserved" with "rights reserved" so the header reads correctly (keep
the SPDX prefix and existing copyright holder text intact).

# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# See README.md for detailed information.

import argparse
import ctypes

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused import.

The ctypes module is imported but never used in this file (flagged by Flake8).

Proposed fix
 import argparse
-import ctypes
 import logging
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import ctypes
import argparse
import logging
🧰 Tools
🪛 Flake8 (7.3.0)

[error] 19-19: 'ctypes' imported but unused

(F401)

🤖 Prompt for AI Agents
In @examples/linux_d555_dual_stream.py at line 19, The file imports the unused
module name "ctypes" causing a Flake8 warning; remove the unused import line
(the standalone "import ctypes") from examples/linux_d555_dual_stream.py so the
module is no longer imported but otherwise leave the rest of the file unchanged.

import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
import os
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

import holoscan
from cuda import cuda

import hololink as hololink_module

class HoloscanApplication(holoscan.core.Application):
def __init__(
self,
headless,
fullscreen,
cuda_context,
cuda_device_ordinal,
hololink_channel_stream_depth,
camera_stream_depth,
hololink_channel_stream_rgb,
camera_stream_rgb,
frame_limit,
window_height,
window_width,
window_title,
):
logging.info("__init__")
super().__init__()
self._headless = headless
self._fullscreen = fullscreen
self._cuda_context = cuda_context
self._cuda_device_ordinal = cuda_device_ordinal
self._hololink_channel_stream_depth = hololink_channel_stream_depth
self._camera_stream_depth = camera_stream_depth
self._hololink_channel_stream_rgb = hololink_channel_stream_rgb
self._camera_stream_rgb = camera_stream_rgb
self._frame_limit = frame_limit
self._window_height = window_height
self._window_width = window_width
self._window_title = window_title
self.is_metadata_enabled = True
self.metadata_policy = holoscan.core.MetadataPolicy.REJECT

def compose(self):
logging.info("compose")
if self._frame_limit:
self._count_stream1 = holoscan.conditions.CountCondition(
self,
name="count_stream1",
count=self._frame_limit,
)
condition_stream1 = self._count_stream1
self._count_stream2 = holoscan.conditions.CountCondition(
self,
name="count_stream2",
count=self._frame_limit,
)
condition_stream2 = self._count_stream2
else:
self._ok_stream1 = holoscan.conditions.BooleanCondition(
self, name="ok_stream1", enable_tick=True
)
condition_stream1 = self._ok_stream1
self._ok_stream2 = holoscan.conditions.BooleanCondition(
self, name="ok_stream2", enable_tick=True
)
condition_stream2 = self._ok_stream2

image_decoder_allocator_pool = holoscan.resources.UnboundedAllocator(self)


image_decoder_stream1 = hololink_module.operators.ImageDecoderOp(
self,
name="image_decoder_stream1",
out_tensor_name="right",
allocator=image_decoder_allocator_pool,
cuda_device_ordinal=self._cuda_device_ordinal,
)
self._camera_stream_depth.configure_converter(image_decoder_stream1)

image_decoder_stream2 = hololink_module.operators.ImageDecoderOp(
self,
name="image_decoder_stream2",
out_tensor_name="left",
allocator=image_decoder_allocator_pool,
cuda_device_ordinal=self._cuda_device_ordinal,
)
self._camera_stream_rgb.configure_converter(image_decoder_stream2)

frame_size = image_decoder_stream1.get_csi_length()
frame_context = self._cuda_context

receiver_operator_stream1 = hololink_module.operators.LinuxReceiverOperator(
self,
condition_stream1,
name="receiver_stream1",
frame_size=frame_size,
frame_context=frame_context,
udp_port=54739 + hololink_module.sensors.d555.d555_mode.RealSense_StreamId.DEPTH.value,
hololink_channel=self._hololink_channel_stream_depth,
device=self._camera_stream_depth,
)

receiver_operator_stream2 = hololink_module.operators.LinuxReceiverOperator(
self,
condition_stream2,
name="receiver_stream2",
frame_size=frame_size,
frame_context=frame_context,
udp_port=54739 + hololink_module.sensors.d555.d555_mode.RealSense_StreamId.RGB.value,
hololink_channel=self._hololink_channel_stream_rgb,
device=self._camera_stream_rgb,
)

left_spec = holoscan.operators.HolovizOp.InputSpec(
"left", holoscan.operators.HolovizOp.InputType.COLOR
)
left_spec_view = holoscan.operators.HolovizOp.InputSpec.View()
left_spec_view.offset_x = 0
left_spec_view.offset_y = 0
left_spec_view.width = 0.5
left_spec_view.height = 1
left_spec.views = [left_spec_view]

right_spec = holoscan.operators.HolovizOp.InputSpec(
"right", holoscan.operators.HolovizOp.InputType.COLOR
)
right_spec_view = holoscan.operators.HolovizOp.InputSpec.View()
right_spec_view.offset_x = 0.5
right_spec_view.offset_y = 0
right_spec_view.width = 0.5
right_spec_view.height = 1
right_spec.views = [right_spec_view]

visualizer = holoscan.operators.HolovizOp(
self,
name="holoviz",
headless=self._headless,
framebuffer_srgb=False,
tensors=[left_spec, right_spec],
height=self._window_height,
width=self._window_width,
window_title=self._window_title,
)

self.add_flow(receiver_operator_stream1, image_decoder_stream1, {("output", "input")})
self.add_flow(receiver_operator_stream2, image_decoder_stream2, {("output", "input")})
self.add_flow(image_decoder_stream1, visualizer, {("output", "receivers")})
self.add_flow(image_decoder_stream2, visualizer, {("output", "receivers")})


def main():
parser = argparse.ArgumentParser()
modes_depth = hololink_module.sensors.d555.d555_mode.RealSense_Depth_Mode
mode_depth_choices = [mode.value for mode in modes_depth]
mode_help = " ".join([f"{mode.value}:{mode.name}" for mode in modes_depth])
parser.add_argument(
"--camera-mode-depth",
type=int,
choices=mode_depth_choices,
default=mode_depth_choices[4], # 1280x720 60fps depth
help=mode_help,
)
modes_rgb = hololink_module.sensors.d555.d555_mode.RealSense_RGB_Mode
mode_rgb_choices = [mode.value for mode in modes_rgb]
mode_help = " ".join([f"{mode.value}:{mode.name}" for mode in modes_rgb])
parser.add_argument(
"--camera-mode-rgb",
type=int,
choices=mode_rgb_choices,
default=mode_rgb_choices[6], # 1280x720 60fps rgb
help=mode_help,
)
parser.add_argument("--headless", action="store_true", help="Run in headless mode")
parser.add_argument(
"--fullscreen", action="store_true", help="Run in fullscreen mode"
)
parser.add_argument(
"--frame-limit",
type=int,
default=None,
help="Exit after receiving this many frames",
)
default_configuration = os.path.join(
os.path.dirname(__file__), "example_configuration.yaml"
)
parser.add_argument(
"--configuration",
default=default_configuration,
help="Configuration file",
)
parser.add_argument(
"--hololink",
default="192.168.0.2",
help="IP address of Hololink board",
)
parser.add_argument(
"--log-level",
type=int,
default=20,
help="Logging level to display",
)
parser.add_argument(
"--window-height",
type=int,
default=720, # arbitrary default
help="Set the height of the displayed window",
)
parser.add_argument(
"--window-width",
type=int,
default=1280 * 2, # arbitrary default
help="Set the width of the displayed window",
)
parser.add_argument(
"--title",
help="Set the window title",
)
args = parser.parse_args()
hololink_module.logging_level(args.log_level)
logging.getLogger().setLevel(args.log_level)
logging.info("Initializing.")
# Get a handle to the GPU
(cu_result,) = cuda.cuInit(0)
assert cu_result == cuda.CUresult.CUDA_SUCCESS
cu_device_ordinal = 0
cu_result, cu_device = cuda.cuDeviceGet(cu_device_ordinal)
assert cu_result == cuda.CUresult.CUDA_SUCCESS
cu_result, cu_context = cuda.cuDevicePrimaryCtxRetain(cu_device)
assert cu_result == cuda.CUresult.CUDA_SUCCESS

# Get a handle to data sources. First, find an enumeration packet
# from the IP address we want to use.
channel_metadata = hololink_module.Enumerator.find_channel(channel_ip=args.hololink)
overrides = hololink_module.Metadata({
"vsync_enable": 0, # or 1
"block_enable": 0, # or 1
})
channel_metadata.update(overrides)
logging.info(f"{channel_metadata=}")
# Now make separate connection metadata for stream1 and stream2; and set them to
# use sensor 0 and 1 respectively. This will borrow the data plane
# configuration we found on that interface.
channel_metadata_stream1 = hololink_module.Metadata(channel_metadata)
hololink_module.DataChannel.use_sensor(channel_metadata_stream1, 0)
channel_metadata_stream2 = hololink_module.Metadata(channel_metadata)
hololink_module.DataChannel.use_sensor(channel_metadata_stream2, 1)
#

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix indentation.

The comment on line 268 has incorrect indentation (not a multiple of 4, and unexpected indentation). This was flagged by Flake8.

Proposed fix
     hololink_module.DataChannel.use_sensor(channel_metadata_stream2, 1)
-     #
+    #
     hololink_channel_stream_depth = hololink_module.DataChannel(channel_metadata_stream1)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#
hololink_module.DataChannel.use_sensor(channel_metadata_stream2, 1)
#
hololink_channel_stream_depth = hololink_module.DataChannel(channel_metadata_stream1)
🧰 Tools
🪛 Flake8 (7.3.0)

[error] 268-268: indentation is not a multiple of 4 (comment)

(E114)


[error] 268-268: unexpected indentation (comment)

(E116)

🤖 Prompt for AI Agents
In @examples/linux_d555_dual_stream.py at line 268, Fix the incorrect
indentation for the lone comment "#" in examples/linux_d555_dual_stream.py
(around the block containing that comment) so it uses an indentation level that
is a multiple of 4 and matches the surrounding block scope (either align to the
enclosing block's indentation or move to column 0 if top-level) to satisfy
Flake8.

hololink_channel_stream_depth = hololink_module.DataChannel(channel_metadata_stream1)
hololink_channel_stream_rgb = hololink_module.DataChannel(channel_metadata_stream2)
# Get a handle to the camera
camera_stream_depth = hololink_module.sensors.d555.d555.RealsenseCamD555(hololink_channel_stream_depth, hololink_module.sensors.d555.d555_mode.RealSense_StreamId.DEPTH)
camera_stream_rgb = hololink_module.sensors.d555.d555.RealsenseCamD555(hololink_channel_stream_rgb, hololink_module.sensors.d555.d555_mode.RealSense_StreamId.RGB)

logging.info("camera mode stream Depth: %s", args.camera_mode_depth)
logging.info("camera mode stream RGB: %s", args.camera_mode_rgb)

camera_mode_depth = hololink_module.sensors.d555.d555_mode.RealSense_Depth_Mode(
args.camera_mode_depth
)

camera_mode_rgb = hololink_module.sensors.d555.d555_mode.RealSense_RGB_Mode(
args.camera_mode_rgb
)

window_title = f"Holoviz - {args.hololink}"
if args.title is not None:
window_title = args.title

# Set up the application
application = HoloscanApplication(
args.headless,
args.fullscreen,
cu_context,
cu_device_ordinal,
hololink_channel_stream_depth,
camera_stream_depth,
hololink_channel_stream_rgb,
camera_stream_rgb,
args.frame_limit,
args.window_height,
args.window_width,
window_title,
)
application.config(args.configuration)
# # Run it.
hololink = hololink_channel_stream_depth.hololink()
assert hololink is hololink_channel_stream_rgb.hololink()
hololink.start()

camera_stream_depth.configure(camera_mode_depth)
camera_stream_rgb.configure(camera_mode_rgb)
os.environ["GXF_MEMORY_DEBUG"] = "1"
application.run()
hololink.stop()

(cu_result,) = cuda.cuDevicePrimaryCtxRelease(cu_device)
assert cu_result == cuda.CUresult.CUDA_SUCCESS


if __name__ == "__main__":
main()
Loading