Code-Level API Evolution
🚧 Documentation is under development
The Holoscan SDK guide is currently under active development. Some sections may be incomplete or change without notice.
Questions? Contact RidgeRun or email to support@ridgerun.com.
-
Overview
-
Build, Run, and Validate
-
Core Concepts
-
Operators and Graphs
-
Tensor Data and Memory
-
Interprocess Communication
-
-
Application and Operator Development
- Building an HSDK Application
- Writing a Minimal Operator
- Typed Parameters and Configuration
- Reading, Writing, and Forwarding Data
- Writing Tensor Operators
- Complete Operator Patterns
- Authoring Runtime Behavior
- Runtime Parameter Updates
- Lifecycle Callbacks and Restart Control
- Performance and Debugging
- SDK Source Map
-
Operator Reference
-
Core Operators
-
Holoscan Camera
-
Holoscan RidgeRun
- Overview
- Build and Installation
- ArgusCameraOp
- V4L2SourceOp
- V4L2UserPtrSourceOp
- BgraToPlanarTensorOp
- RawBayerToBgraOp
- RawBayerToYoloTensorOp
- YuyvToBgraOp
- TensorRtInferenceOp
- DrmDisplayOp
- DrmBgraDisplayOp
- X11DisplayOp
- CudaCompositorOp
- DepthColorizerOp
- FakesinkOp
- FrameSkipperOp
- InterprocessSrcOp
- TestPatternOp
-
-
Migration from Holoscan 4.x
-
Examples
-
Core
-
Holoscan Camera
-
Holoscan RidgeRun
-
-
Benchmark and Use Cases
-
Holoscan vs GStreamer
- Overview
-
Capture and Display
-
Capture and Inference
- Compare the workloads
- Implemented Applications
- Framework Selection
- Interprocess
-
Code-Level API Evolution
After the graph and timing decisions are clear, update the operator code in small, testable changes. Start with one scalar path, confirm its output and completion behavior, then add parameters, Tensor payloads, device work, and external boundaries as the application requires.
The graph structure and timing choices pages provide the decisions used by the example below.
Translate one operator at a time
| Code area | Migration action |
|---|---|
| Operator declaration | Use Operator<Params> when the operator has typed configuration. Keep input and output payload types in the port declarations.
|
| Port and storage setup | Declare ports and capacity in setup().
|
| Activation behavior | Return a holoscan::Contract from contract() so the runtime can prepare the activation.
|
| Work and error handling | Process the selected data in compute() and return checked receive, emit, and processing errors.
|
| Application startup | Use holoscan::compile() to create an ExecutionPlan, check plan.ok(), and run the accepted plan with holoscan::run(). Keep device, model, permission, and external-peer checks at the application boundary.
|
A complete small example
The following program has a clock-driven scalar source and an input-driven sink. The source emits ten values, the sink prints them, and the application checks compilation before running. The public types used here are holoscan::Graph, holoscan::RealtimeClock, and holoscan::ComputeCalls. Build it with the environment described in Run SDK Examples. Its console output demonstrates data flow; it is not a performance measurement.
#include <chrono>
#include <cstdint>
#include <iostream>
#include <utility>
#include <holoscan/core/compile.hpp>
#include <holoscan/core/graph.hpp>
#include <holoscan/core/operator.hpp>
#include <holoscan/core/operator_spec.hpp>
#include <holoscan/core/run.hpp>
#include <holoscan/core/temporal_contract.hpp>
#include <holoscan/time/realtime_clock.hpp>
class MeasurementSource final : public holoscan::Operator<> {
public:
holoscan::Output<std::uint32_t> output;
void setup(holoscan::OperatorSpec& spec) override {
spec.output(output, "output").max_emits_per_compute(1U);
}
holoscan::Contract contract() const override {
holoscan::Contract result;
result.trigger(holoscan::OnClock{.period = std::chrono::milliseconds{10}})
.complete_after(holoscan::ComputeCalls{.limit = 10U});
return result;
}
holoscan::expected<void, holoscan::Error> compute(
holoscan::ExecutionContext&) override {
return output.emit(++next_);
}
private:
std::uint32_t next_{};
};
class MeasurementSink final : public holoscan::Operator<> {
public:
holoscan::Input<std::uint32_t> input;
void setup(holoscan::OperatorSpec& spec) override {
spec.input(input, "input").queue_depth(4U);
}
holoscan::Contract contract() const override {
holoscan::Contract result;
result.trigger(holoscan::OnEach{input});
return result;
}
holoscan::expected<void, holoscan::Error> compute(
holoscan::ExecutionContext&) override {
auto sample = input.receive();
if (!sample) {
return holoscan::make_unexpected(std::move(sample).error());
}
std::cout << sample->data << '\n';
return {};
}
};
int main() {
holoscan::Graph graph{"measurement-migration"};
auto source = graph.op<MeasurementSource>("source");
auto sink = graph.op<MeasurementSink>("sink");
graph.add_flow(source->output, sink->input,
holoscan::ConnectionOptions{.queue_depth = 4U});
graph.set_default_clock(graph.add_clock<holoscan::RealtimeClock>("clock"));
const holoscan::ExecutionPlan plan = holoscan::compile(graph);
if (!plan.ok()) {
std::cerr << plan.json() << '\n';
return 1;
}
return holoscan::run(plan);
}
The source uses the checked Output::emit() result. A failed publication reaches the callback result. The sink checks Input::receive() before reading the value. The complete_after() rule limits successful source calls; the clock period only determines when the source becomes eligible.
Add parameters and Tensor data after the small path works
For configuration, define a parameter aggregate, use Operator<Params>, set defaults in the aggregate, and apply authoring values before compilation. Typed Parameters and Configuration covers defaults, validation, graph-side overrides, and authorized runtime updates.
For Tensor payloads, define the representation, shape, layout, memory placement, and capacity before writing the operator body. Acquire output storage, write it on the framework-provided stream, commit it, and publish the result. Tensor Contracts and Memory Types explains the data contract, and Writing Tensor Operators shows the checked operator path.
For an external peer, migrate the endpoint and payload contract after the in-process path works. External Endpoints and Endpoint Catalogs covers the boundary and Standalone ChannelRuntime Clients covers a separately launched client.
Check each boundary before scaling the port
Run the small path and check:
- the first value and final value are produced as expected
- the source completes at the intended point
- a failed receive or publication is visible to the application
- a missing or late input follows the selected temporal policy
- a Tensor or external payload keeps its declared format and ownership
When those checks pass, continue into the relevant operator, Tensor, camera, or interprocess guide. The migration is complete when the intended behavior survives these cases, not when every old symbol has simply disappeared.