NVIDIA Holoscan SDK v2.9.0

Ping 自定义操作

在本节中,我们将修改之前的 ping_simple 示例,以在工作流程中添加自定义操作符。我们已经在 hello_world 示例中看到了自定义操作符的定义,但跳过了一些细节。

在本示例中,我们将介绍

  • 创建您自己的自定义操作符类的详细信息。

  • 如何向您的操作符添加输入和输出端口。

  • 如何向您的操作符添加参数。

  • 操作符之间传递的消息的数据类型。

注意

示例源代码和运行说明可以在 GitHub 上的 examples 目录中找到,或者在 NGC 容器和 Debian 包中的 /opt/nvidia/holoscan/examples 下找到,以及它们的可执行文件。

以下是本示例中使用的操作符和工作流程的图表。

graphviz-71b34fe2fcfd0564f52cb6e8312ac0a0a301b25f.png

图 6 具有新自定义操作符的线性工作流程

与之前的示例相比,我们正在 PingTxOpPingRxOp 操作符之间添加一个新的 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

上一篇 Ping Simple
下一篇 Ping Multi Port
© 版权所有 2022-2024,NVIDIA。 上次更新于 2025 年 1 月 27 日。