Ping Multi Port
In this section, we look at how to create an application with a more complex workflow where operators may have multiple input/output ports that send/receive a user-defined data type.
In this example we will cover:
- How to send/receive messages with a custom data type.
- How to add a port that can receive any number of inputs.
The example source code and run instructions can be found in the examples directory on GitHub, or under /opt/nvidia/holoscan/examples in the NGC container and the Debian package, alongside their executables.
Operators and Workflow
Here is the diagram of the operators and workflow used in this example.
In this example, PingTxOp sends a stream of odd integers to the out1 port, and even integers to the out2 port. PingMxOp receives these values using in1 and in2 ports, multiplies them by a constant factor, then forwards them to a single port - receivers - on PingRxOp.
User Defined Data Types
In the previous ping examples, the port types for our operators were integers, but the Holoscan SDK can send any arbitrary data type. In this example, we’ll see how to configure
operators for our user-defined ValueData class.
C++
Python
The ValueData class wraps a simple integer (line 6, 16), but could have been arbitrarily complex.
The HOLOSCAN_LOG_<LEVEL>() macros can be used for logging with fmtlib syntax (lines 7, 9 above) as demonstrated across this example. See the Logging section for more details.
Defining an Explicit Number of Inputs and Outputs
After defining our custom ValueData class, we configure our operators’ ports to send/receive messages of this type, similarly to the previous example.
This is the first operator - PingTxOp - sending ValueData objects on two ports, out1 and out2:
C++
Python
- We configure the output ports with the
ValueDatatype on lines10and11usingspec.output<std::shared_ptr<ValueData>>(). Therefore, the data type for the output ports is an object to a shared pointer to aValueDataobject. - The values are then sent out using
op_output.emit()on lines16and19. The port name is required since there is more than one port on this operator.
Data types of the output ports are shared pointers (std::shared_ptr), hence the call to std::make_shared<ValueData>(...) on lines 15 and 18.
We then configure the middle operator - PingMxOp - to receive that data on ports in1 and in2:
C++
Python
- We configure the input ports with the
std::shared_ptr<ValueData>type on lines8and9usingspec.input<std::shared_ptr<ValueData>>(). - The values are received using
op_input.receive()on lines16and17using the port names. The received values are of typestd::shared_ptr<ValueData>as mentioned in the templatedreceive()method.
PingMxOp processes the data, then sends it out on two ports, similarly to what is done by PingTxOp above.
Receiving Any Number of Inputs
In this workflow, PingRxOp has a single input port - receivers - that is connected to two upstream ports from PingMxOp. When an input port needs to connect to multiple upstream ports, we define it with spec.input() and set the size to IOSpec::kAnySize (or IOSpec.ANY_SIZE in Python). This allows the input port to receive data from multiple sources. The inputs are then stored in a vector, following the order they were added with add_flow().
C++
Python
- In the operator’s
setup()method, we define an input portreceivers(line12) withholoscan::IOSpec::kAnySizeto allow any number of upstream ports to connect to it. - The values are retrieved using
op_input.receive<std::vector<std::shared_ptr<ValueData>>>(...). - The type of
value_vectorisstd::vector<std::shared_ptr<ValueData>>(lines16-17).
Please see Retrieving Any Number of Inputs Cpp for more information on how to retrieve any number of inputs in C++.
The rest of the code creates the application, operators, and defines the workflow:
C++
Python
- The operators
tx,mx, andrxare created in the application’scompose()similarly to previous examples. - Since the operators in this example have multiple input/output ports, we need to specify the third, port name pair argument when calling
add_flow():tx/out1is connected tomx/in1, andtx/out2is connected tomx/in2.mx/out1andmx/out2are both connected torx/receivers.
Running the Application
Running the application should give you output similar to the following in your terminal.
Depending on your log level you may see more or fewer messages. The output above was generated using the default value of INFO.
Refer to the Logging section for more details on how to set the log level.