Visualization
- What the Visualization module provides and which part of it is supported API
- How
HolovizOpdeclares layers, binds them to input ports, and presents frames - How the operator’s temporal contract keeps the display running when inputs lag
- How to build the module and link it from an installed SDK
Holoscan Visualization is the core module that provides
holoscan::visualization::HolovizOp, a sink operator that composites image,
geometry, text, and color-lookup-table layers and presents them as frames
arrive or at a fixed display rate. The rendering backend is HoloViz, a CUDA/Vulkan renderer
written for the Holoscan 5.x frame path: layer resources are allocated once
from declared maxima, and the per-frame path performs no allocation.
HolovizOp — its parameters, input ports, and the layer and sink descriptor
types it accepts — is the only public, supported API of this module in
Holoscan SDK 5.0. The HoloViz layer renderer and rendering hardware interface (RHI)
beneath the operator are internal: their headers install with the module but
carry no source or ABI compatibility promise, and they may change in any
release. Write applications against the operator.
The operator
HolovizOp is constructed with a fixed LayerStack, a SinkDesc, and an
optional FrameTrigger and freshness bound (see
Temporal contract below):
Each call to add_*_layer declares one input port named by its first
argument. Image layers are Input<holoscan::schema::ImageT> ports reached
through image_layer_input(i); geometry, text, and LUT layers are
Input<holoscan::Tensor> ports reached through tensor_layer_input(i). Both
take the i-th declared layer’s index, whatever mix of kinds surrounds it. Up
to kMaxLayerPorts (8) layers of any kind mix may be declared per operator
instance.
An image producer publishes schema::ImageT with
Output<schema::ImageT>::emit_tensor(loan, descriptor, options): the loan
carries the pixels and the descriptor the frame’s width, height,
encoding, color_space, and, for NV12, the two plane_layouts records that
locate the Y and UV planes. HolovizOp checks these against the layer’s
ImageLayerDesc and drops a frame that disagrees rather than render it with
the wrong format, matrix, or transfer function. A color_space of
UNSPECIFIED makes no claim and is accepted into any layer.
Layers
Layer descriptors are the declared maxima the renderer sizes its pools to. A frame may use any subregion or count up to that maximum without triggering a reallocation; exceeding it is rejected as a mis-declaration.
Image layers accept the formats in the ImageLayerDesc header table: 8- and
16-bit RGBA/BGRA and single-channel formats, RGB8, and the YUV formats kNV12
and kYUYV422, which require yuv_model and yuv_range in the descriptor.
sRGB decoding (ColorSpace::kSRGBNonlinear) is available for 8-bit RGBA/BGRA.
Sinks
SinkDesc states where rendered frames go. All three kinds have a present
path:
WindowSinkDescpresents into a compositor-managed window (decorated or fullscreen).ExclusiveDisplaySinkDescscans out directly to a display no display server owns (Linux DRM only). See Enable exclusive display for the display-side prerequisites.CudaSinkDescrenders into CUDA device memory instead of a display: every frame is emitted onHolovizOp::output()as aschema::ImageTwhosedatatensor comes from a runtime tensor pool, so a second operator (for example anotherHolovizOpthat drives a window) consumes it without a copy. The frame carries its wire semantics:width/heightare the sink’s extent,encoding/color_spacefollow the sinkformat(MONO8,RGBA8,BGRA8,MONO16,RGBA16; the sRGB formats are stampedSRGB), and the tensor is the format’s native element type in{H, W}(single-channel) or{H, W, 4}layout —uint16 {H, W}forkR16Unorm, for instance. The deployment must bind the output port’s CUDA device withCompileOptions::deployment.bind_tensor_output_device. Thecuda_sink_exampleundermodules/visualization/examples/operatorschains twoHolovizOpinstances this way.
Temporal contract
By default HolovizOp renders one frame per sample admitted on layer 0
(LayerTrigger{0}), so presentation follows the driving producer. Pass a
ClockTrigger{.period = ...} as the third constructor argument to render at a
fixed cadence instead; a layer-less operator requires it.
LayerTrigger{n}lowers toOnEachon layer n’s port, which carries no selector;ClockTriggerlowers toOnClockwith every layer selected.- Every other layer input uses
LatestWithin{.max_age = max_input_age}(default 100 ms). - A missing or stale image layer applies
ReuseLastfor the same bound, so the last frame stays on screen; other layer kinds applySafeDefaultand are skipped for that activation.
When a side producer publishes slower than the trigger layer — a 30 fps
overlay under a 60 fps camera — pass a wider max_input_age to the
constructor; otherwise that layer is missing on most activations. Connect
the trigger edge as a depth-1 drop-oldest mailbox so a sink stall costs at
most one stale frame. See
Temporal contracts
for the selector and missing-data semantics.
Graphs require explicit partition membership for the operator, for example
graph.partition("holoviz").add(holoviz).
HoloViz backend errors are translated into the Holoscan error domain at the
operator lifecycle boundary, so they surface through the same
errors-as-values
path as any other operator failure.
Build, test, and consume
HOLOSCAN_BUILD_VISUALIZATION defaults to ON. Rendering needs a
CUDA-capable GPU and a Vulkan 1.3 capable driver; headless presentation
additionally needs libegl1. The Vulkan SDK is not a build prerequisite. Build
and test through the SDK-level runner:
The module can also be configured as a top-level project against an installed
SDK (cmake -S modules/visualization -B build -Dholoscan_ROOT=<install>), which
is how module CI exercises it on Linux and Windows.
Installed consumers link the operator target from the main Holoscan package;
the HoloViz renderer is carried as a private dependency of the operator, while
holoscan::sensor_io (the schema::ImageT port type) is a public one that the
visualization component loads for you:
Standalone consumers can also load the Visualization module package:
Replace the example path with the SDK’s shared package directory. The module
package config is installed under lib/cmake/holoscan, rather than a
package-named directory, so a bare find_package(holoscan-visualization) may
not find it through the usual package-name search. Set
holoscan-visualization_DIR to the shared directory, or pass it through
HINTS as shown.
Troubleshooting
HoloViz reports these conditions through the operator’s error or the log. The messages below are quoted as HoloViz prints them; placeholders are in angle brackets.
For errors from ExclusiveDisplaySinkDesc (display ownership, DRM master,
display names, and integrated-GPU mode limits), see the troubleshooting table in
Enable exclusive display.
Recap
- The Visualization module ships
HolovizOp; the HoloViz renderer and RHI underneath are internal and may change. - Layers and the sink are declared once at construction; each layer becomes a
typed input port —
schema::ImageTfor image layers viaimage_layer_input(i),Tensorfor the rest viatensor_layer_input(i). - The operator renders per arriving frame on the trigger layer by default (or
on a
ClockTrigger), reuses the last image when a layer is late, and skips other stale layers. - Build with
HOLOSCAN_BUILD_VISUALIZATION; consume throughholoscan::visualization::op_holovizfrom the installed Holoscan package. - Next: Holoscan Modules.