Ping 自定义操作
在本节中,我们将修改之前的 ping_simple
示例,以在工作流程中添加自定义操作符。我们已经在 hello_world
示例中看到了自定义操作符的定义,但跳过了一些细节。
在本示例中,我们将介绍
创建您自己的自定义操作符类的详细信息。
如何向您的操作符添加输入和输出端口。
如何向您的操作符添加参数。
操作符之间传递的消息的数据类型。
示例源代码和运行说明可以在 GitHub 上的 examples 目录中找到,或者在 NGC 容器和 Debian 包中的 /opt/nvidia/holoscan/examples
下找到,以及它们的可执行文件。
以下是本示例中使用的操作符和工作流程的图表。

图 6 具有新自定义操作符的线性工作流程
与之前的示例相比,我们正在 PingTxOp 和 PingRxOp 操作符之间添加一个新的 PingMxOp 操作符。这个新操作符将一个整数作为输入,将其乘以一个常数因子,然后将新值发送到 PingRxOp。您可以将此自定义操作符视为在将结果发送到下游操作符之前,对输入流进行一些数据处理。
我们的自定义操作符需要 1 个输入端口和 1 个输出端口,可以通过在操作符的 setup()
方法中调用 spec.input()
和 spec.output()
方法来添加。这需要提供端口的数据类型和名称作为参数(对于 C++ API),或者仅提供端口名称(对于 Python API)。我们将在下面的代码片段中看到一个示例。有关更多详细信息,请参阅 指定操作符输入和输出 (C++) 或 指定操作符输入和输出 (Python)。
操作符可以通过在初始化期间自定义其参数来使其更具可重用性。自定义参数可以直接作为参数提供,也可以从应用程序的 YAML 配置文件中访问。我们将在本示例中展示如何使用前者来自定义我们的 PingMxOp 自定义操作符的“multiplier”因子。使用 YAML 配置文件配置操作符将在后续的 示例 中展示。有关更多详细信息,请参阅 配置操作符参数。
下面的代码片段展示了如何定义 PingMxOp 类。
#include <holoscan/holoscan.hpp>
#include <holoscan/operators/ping_tx/ping_tx.hpp>
#include <holoscan/operators/ping_rx/ping_rx.hpp>
namespace holoscan::ops {
class PingMxOp : public Operator {
public:
HOLOSCAN_OPERATOR_FORWARD_ARGS(PingMxOp)
PingMxOp() = default;
void setup(OperatorSpec& spec) override {
spec.input<int>("in");
spec.output<int>("out");
spec.param(multiplier_, "multiplier", "Multiplier", "Multiply the input by this value", 2);
}
void compute(InputContext& op_input, OutputContext& op_output, ExecutionContext&) override {
auto value = op_input.receive<int>("in");
std::cout << "Middle message value: " << value << std::endl;
// Multiply the value by the multiplier parameter
value *= multiplier_;
op_output.emit(value);
};
private:
Parameter<int> multiplier_;
};
} // namespace holoscan::ops
PingMxOp
类继承自Operator
基类(第7
行)。HOLOSCAN_OPERATOR_FORWARD_ARGS
宏(第9
行)是语法糖,用于帮助将操作符的构造函数参数转发到Operator
基类,并且是一种方便的简写,避免了必须手动为您的操作符定义具有必要参数的构造函数。名称为 “in”/“out” 的输入/输出端口分别添加到第
14
行和15
行的操作符规范中。两个端口的端口类型均为int
,如模板参数<int>
所示。我们向操作符规范添加一个 “multiplier” 参数(第
16
行),默认值为 2。此参数与私有数据成员 “multiplier_” 相关联。在
compute()
方法中,我们从操作符的 “in” 端口接收整数数据(第20
行),打印其值,将其值乘以乘法因子,并将新值向下游发送(第27
行)。在第
20
行,请注意操作符之间传递的数据类型为int
。在第
27
行调用op_output.emit(value)
等效于op_output.emit(value, "out")
,因为此操作符只有一个输出端口。如果操作符有多个输出端口,则需要端口名称。
from holoscan.conditions import CountCondition
from holoscan.core import Application, Operator, OperatorSpec
from holoscan.operators import PingRxOp, PingTxOp
class PingMxOp(Operator):
"""Example of an operator modifying data.
This operator has 1 input and 1 output port:
input: "in"
output: "out"
The data from the input is multiplied by the "multiplier" parameter
"""
def setup(self, spec: OperatorSpec):
spec.input("in")
spec.output("out")
spec.param("multiplier", 2)
def compute(self, op_input, op_output, context):
value = op_input.receive("in")
print(f"Middle message value:{value}")
# Multiply the values by the multiplier parameter
value *= self.multiplier
op_output.emit(value, "out")
PingMxOp
类继承自Operator
基类(第5
行)。名称为 “in”/“out” 的输入/输出端口分别添加到第
17
行和18
行的操作符规范中。我们向操作符规范添加一个 “multiplier” 参数,默认值为 2(第
19
行)。在
compute()
方法中,我们从操作符的 “in” 端口接收整数数据(第22
行),打印其值,将其值乘以乘法因子,并将新值向下游发送(第28
行)。
现在自定义操作符已定义,我们创建应用程序、操作符并定义工作流程。
class MyPingApp : public holoscan::Application {
public:
void compose() override {
using namespace holoscan;
// Define the tx, mx, rx operators, allowing tx operator to execute 10 times
auto tx = make_operator<ops::PingTxOp>("tx", make_condition<CountCondition>(10));
auto mx = make_operator<ops::PingMxOp>("mx", Arg("multiplier", 3));
auto rx = make_operator<ops::PingRxOp>("rx");
// Define the workflow: tx -> mx -> rx
add_flow(tx, mx);
add_flow(mx, rx);
}
};
int main(int argc, char** argv) {
auto app = holoscan::make_application<MyPingApp>();
app->run();
return 0;
}
tx、mx 和 rx 操作符在第
6-8
行的compose()
方法中创建。自定义 mx 操作符以与内置操作符完全相同的方式使用
make_operator()
创建(第7
行),并配置了一个 “multiplier” 参数,该参数初始化为 3,这将覆盖参数的默认值 2(在setup()
方法中)。工作流程通过使用
add_flow()
在第11-12
行将 tx 连接到 mx,并将 mx 连接到 rx 来定义。
class MyPingApp(Application):
def compose(self):
# Define the tx, mx, rx operators, allowing the tx operator to execute 10 times
tx = PingTxOp(self, CountCondition(self, 10), name="tx")
mx = PingMxOp(self, name="mx", multiplier=3)
rx = PingRxOp(self, name="rx")
# Define the workflow: tx -> mx -> rx
self.add_flow(tx, mx)
self.add_flow(mx, rx)
def main():
app = MyPingApp()
app.run()
if __name__ == "__main__":
main()
tx、mx 和 rx 操作符在第
4-6
行的compose()
方法中创建。自定义 mx 操作符以与内置操作符完全相同的方式创建(第
5
行),并配置了一个 “multiplier” 参数,该参数初始化为 3,这将覆盖参数的默认值 2(在setup()
方法中)。工作流程通过使用
add_flow()
在第9-10
行将 tx 连接到 mx,并将 mx 连接到 rx 来定义。
对于 C++ API,操作符之间传递的消息是输入和输出处的数据类型的对象,因此上面示例中第 20 行和第 25 行的 value
变量的类型为 int
。对于 Python API,操作符之间传递的消息可以是任意 Python 对象,因此不需要特殊考虑,因为它不受 C++ API 操作符使用的更严格的参数类型的限制。
让我们看一下内置 PingTxOp 类的代码片段,看看这是否有助于更清楚地了解。
#include "holoscan/operators/ping_tx/ping_tx.hpp"
namespace holoscan::ops {
void PingTxOp::setup(OperatorSpec& spec) {
spec.output<int>("out");
}
void PingTxOp::compute(InputContext&, OutputContext& op_output, ExecutionContext&) {
auto value = index_++;
op_output.emit(value, "out");
}
} // namespace holoscan::ops
PingTxOp 的 “out” 端口的类型为
int
(第6
行)。当调用
emit()
时,一个整数被发布到 “out” 端口(第11
行)。下游 PingMxOp 操作符在调用
op_input.receive<int>()
时接收到的消息的类型为int
。
class PingTxOp(Operator):
"""Simple transmitter operator.
This operator has a single output port:
output: "out"
On each tick, it transmits an integer to the "out" port.
"""
def setup(self, spec: OperatorSpec):
spec.output("out")
def compute(self, op_input, op_output, context):
op_output.emit(self.index, "out")
self.index += 1
对于 Python 版本,无需特殊考虑,我们只需调用
emit()
并传递整数对象(第14
行)。
对于高级用例,例如,当编写需要 C++ 本地操作符和 GXF 操作符之间互操作性的 C++ 应用程序时,您将需要使用 holoscan::TensorMap
类型。有关更多详细信息,请参阅 GXF 和本地 C++ 操作符之间的互操作性。如果您正在编写一个需要混合使用 Python 封装的 C++ 操作符和本地 Python 操作符的 Python 应用程序,请参阅 封装的操作符和本地 Python 操作符之间的互操作性。
运行该应用程序应该在您的终端中给出以下输出
Middle message value: 1
Rx message value: 3
Middle message value: 2
Rx message value: 6
Middle message value: 3
Rx message value: 9
Middle message value: 4
Rx message value: 12
Middle message value: 5
Rx message value: 15
Middle message value: 6
Rx message value: 18
Middle message value: 7
Rx message value: 21
Middle message value: 8
Rx message value: 24
Middle message value: 9
Rx message value: 27
Middle message value: 10
Rx message value: 30