マクロフィルタ
先読み: データフロープログラミングの紹介。
ユーザーインターフェースでマクロフィルタを作成する方法については、マクロフィルタの作成を参照してください。
この記事では、マクロフィルタの構築と操作に関するさらに高度な詳細について説明します。
マクロフィルタの構造
プログラム内で異なる4つの役割を果たすマクロフィルタの構造があります:
- ステップ
- バリアントステップ
- タスク
- ワーカー
ステップ
ステップは最も基本的なマクロフィルタ構造です。その主な目的はプログラムをきれいで整理されたものにすることです。
この構造のマクロフィルタは、単純に複数のフィルタのシーケンスを分離し、プログラムの多くの場所で1つのブロックとして使用できるようにします。これは、各マクロフィルタのインスタンスの場所にそのフィルタが展開されたかのように機能します。その結果:
- 含まれるフィルタとレジスタの状態は連続的な呼び出しの間で保持されます。
- ステップマクロフィルタにループジェネレータが挿入されると、このステップは全体としてループジェネレータになります。
- ステップマクロフィルタにループアキュムレータが挿入されると、このステップは全体としてループアキュムレータになります。
バリアントステップ
バリアント マクロフィルタは ステップ に似ていますが、複数の代替実行パスを持つことができます。 各パスは バリアント と呼ばれます。各呼び出しで正確に1つのバリアントが実行されます。選択されるバリアントは、フォーキング 入力または レジスタ(アイコンで表示)の値に依存し、その値はバリアントの ラベル と比較されます。
フォーキングポートとラベルの型は、Bool、Integer、String、または任意の列挙型である可能性があります。 さらに、任意の 条件 または オプション の型を使用することもでき、その場合 "Nil" バリアントが利用可能になります。 これは、プログラムの実行をいくつかの分析が成功したか(適切な値が存在するか)しなかったか(Nil)に基づいて分岐させる際に非常に役立ちます。
すべてのバリアントは単一の外部インターフェースを共有します。入力、出力、およびレジスタも同じです。 連続する反復では異なるバリアントが実行されるかもしれませんが、このマクロフィルタのローカルレジスタを介して内部で情報を交換することができます。外部から見れば、バリアントステップは他のフィルタと同じように見えます。
以下は、バリアントマクロフィルタの例のいくつかです:
- プログラムの一部が複数の代替実装を持つ可能性があり、エンドユーザーによって選択されることを希望する場合。 例えば、オブジェクトを検出する方法が2つあり、異なるトレードオフがあります。ユーザーはHMIのコンボボックスを介してまたは構成ファイルの値を変更して、2つの方法のうちの1つを選択できるかもしれません。
- オブジェクト検出ステップが複数のクラスの検出オブジェクトを生成でき、次のオブジェクト認識ステップは検出されたクラスに依存する場合。
- 検査アルゴリズムが複数の結果(通常: OK および NOK)を持ち、それぞれのケースに対して異なる通信または視覚化フィルタを実行したい場合。
- 有限状態マシン。
バリアントステップ マクロフィルタは、C++ の switch ステートメントに対応しています。
例1
バリアントステップ は、画像取得をカプセル化するサブプログラムを作成するために使用できます。このサブプログラムは、String 型の入力で制御される2つのオプションを持ちます:
- バリアント "Files": あるディレクトリのファイルから画像を読み込む。
- バリアント "Camera": カメラから画像を取得する。
プロジェクトエクスプローラーでは、マクロフィルタアイテムを展開すると、バリアントがアクセス可能になります:
例2
バリアントステップ マクロフィルタの別の応用例は、オプションのデータ処理ステップの作成です。 例えば、プログラムには設定可能な画像平滑化フィルタがあり、ユーザーはHMIのCheckBoxを介してそれをオンまたはオフにできるかもしれません。そのために、次のようなマクロフィルタを作成します:
注意: ここでバリアントステップを使用することは、パフォーマンスの観点からも重要です。たとえ inStdDev を 0 に設定しても、SmoothImage_Gauss フィルタは入力から出力へのデータのコピーが必要です。また、ChooseByPredicate や MergeDefault などのフィルタは完全なコピーを実行します。 一方で、マクロフィルタを使用すると、マクロフィルタの入力からマクロフィルタの出力への内部接続ではデータのコピーは発生せず(代わりにリンクされます)、画像などの重いデータの場合、パフォーマンスの利点が大きいです。
タスク
タスク マクロフィルタは、単なるフィルタの分離されたシーケンス以上のものです。これは完全な計算プロセスを実現する論理的なプログラムユニットです。これはサブプログラムとして使用でき、通常はより高度な状況で必要です。例:
- 配列接続 では簡単に達成できないプログラム内の明示的な入れ子のループが必要な場合。
- メインのプログラムループが開始する前にいくつかの計算を実行する必要がある場合。
実行プロセス
タスク と ステップ の最も重要な違いは、タスク はその自体の単一の呼び出しが終了する前に、そのフィルタシーケンスを多くの反復で実行できることです。 このフィルタシーケンスは、含まれるループ生成フィルタのいずれかがシーケンスの終了を示すまで繰り返し実行されます。ループ生成フィルタがまったくない場合、タスクは正確に1回の反復を実行します。 次に、プログラムの実行がタスクを終了すると(親のマクロフィルタに戻る)、タスクのフィルタとレジスタの状態が破棄されます(これには関連するI/Oデバイスとの接続が含まれます)。
また非常に重要なのは、タスク がプログラムの 反復 の概念を定義していることです。 プログラムの実行中、常に最も入れ子で現在実行されているタスクが1つあります。これを 現在の タスクと呼びます。実行プロセスがこのタスクの終わりに達すると、プログラムの1つの反復が終了したと言います。 ユーザーが 反復 (F6) ボタンをクリックすると、実行は現在のタスクの終わりまで続きます。また 反復ごとのデータプレビューの更新 も同じ概念を指します。
タスク マクロフィルタは、C/C++ の単一の while ループを持つ関数の同等物と見なすことができます。
例: メインループの前の初期計算
非常に一般的なケースは、プログラムのメインループが開始される前にいくつかのフィルタを実行する必要がある場合です。そのような初期計算の典型的な例には、カメラのパラメータを設定する、I/O通信を確立する、外部ファイルからモデルの定義を読み込むなどがあります。 これらの初期操作をすべてマクロフィルタにまとめ、Initialize セクションに配置できます。この種のプログラムは、次の2つの部分から構成される標準的な構造を持つべきです:
プログラムが開始されると、Initialize セクションのすべてのフィルタとマクロフィルタが1回実行され、その後、Acquire および Process セクションのループがプログラムが終了するまで実行されます。
ワーカータスク
ワーカータスク の主な目的は、ユーザーがデータを並列に処理できるようにすることです。また、いくつかの他の新しい機能も可能にします:
- 異なる同期されていないデバイスから来るデータの並列受信と処理
- プログラムのサイクルに依存しない出力デバイスの並列制御(例: ダイオードを500msごとに点灯する)
- プログラムを分割し、リアルタイムで処理する必要がある部分を待機できる部分から分割する
- HMI イベントの問題ない処理
- アルゴリズムのユニットテストのためにプロジェクトに追加のプログラムを持つ
- メインプログラムで後で使用されるデータを事前に計算するためにプロジェクトに追加のプログラムを持つ
利用可能な ワーカータスク の数はライセンスに依存しますが、どのプログラムにも少なくとも1つの "Main" として機能する ワーカータスク が含まれています。
ワーカータスクの作成
特殊な使用のため、ワーカータスク は Ctrl+Space ショートカットで作成したり、直接プログラムエディタで作成したりすることはできません。唯一のオプションは、マクロフィルタの作成記事で示されているように、プロジェクトエクスプローラーで作成することです。
キュー
キューは、プログラム内の異なる ワーカータスク 間の通信と同期に重要な役割を果たします。これらはスレッド間でほとんどのデータおよび 配列 を受け渡すことができます。キュー上の操作はアトミックであり、一度プロセッサが処理を開始すると中断されることはなく、いかなる方法でも影響を受けません。したがって、複数のスレッドが操作を実行しようとした場合、いくつかは待機する必要があります。
異なる配列は互いに同期されていないことに注意してください。複雑なデータを処理したり、同期が必要なデータを受け渡す場合(画像とその情報など)、ユーザータイプを使用することをお勧めします。また、キューをグローバルパラメータの代替として使用しないよう強くお勧めします。
キューの作成
プロジェクトエクスプローラーで新しいキューを作成するには、Create New Queue... アイコンをクリックします。 新しいウィンドウが表示され、新しいキューの名前およびパラメータ(以下のようなもの)を選択できます:
- キューで受け渡されるデータのタイプを指定するアイテムのタイプ
- キューの最大サイズ
- 所属するモジュール
- アクセス - パブリックまたはプライベート
キュー操作
キュー操作を実行し、ワーカータスク でキューを完全に管理するには、当社のソフトウェアで利用可能ないくつかのフィルタを使用できます。具体的には:
- Queue_Pop - キューから値を取り出しますが、それをコピーしません。キューが空の場合は無限に待機します。この操作はデータを読み取るだけでコピーしないため、即座に実行されます。
- Queue_Pop_Timeout - キューから値を取り出しますが、それをコピーしません。キューが空の場合はTimeoutで指定された時間待機します。この操作はデータを読み取るだけでコピーしないため、即座に実行されます。
- Queue_Peek - キューの指定された要素を取得しますが、それを削除しません。キューが空の場合は無限に待機します。
- Queue_Peek_Timeout - キューの指定された要素を取得しますが、それを削除しません。キューが空の場合はTimeoutで指定された時間待機します。
- Queue_Push - 要素をキューに追加します。この操作はデータをコピーするため、他の操作と比較して時間がかかる可能性があります。
- Queue_Size - キューのサイズを返します。
- Queue_Flush - キューをクリアします。データフローをブロックしません。
マクロフィルタのポート
入力
マクロフィルタの入力は、マクロフィルタの実行が開始される前に設定されます。 タスク マクロフィルタの場合、入力は連続した反復で値が変わりません。
出力
マクロフィルタの出力は、フィルタとの接続や グローバルパラメータ との接続、または直接出力に定義された定数値から設定できます。
タスク の場合(多数の反復がある場合)、接続された出力の値は最後に計算された値と等しいです。考慮すべき特殊なケースの1つは、最初の反復で終了するループ生成フィルタがある場合です。この場合、「最後の」値は存在しないため、代わりに出力のデフォルト値が使用されます。このデフォルト値は出力の定義の一部です。 さらに、出力に条件付きの接続がある場合、Nilの値は以前に計算された値を保持します。
レジスタ
レジスタを使用すると、タスク の連続した反復間で情報を受け渡すことができます。 レジスタは ステップ および バリアントステップ でもローカルに定義できますが、その値は親の タスク に属しているかのようにまったく同じ方法で設定されます:
- レジスタの値は、タスク 開始時にデフォルトに初期化されます。
- レジスタの値は各反復の終わりに変更されます。
バリアントマクロフィルタの場合、次の反復のためのレジスタの値は各バリアントごとに別途定義されます。多くの場合、いくつかのレジスタはほとんどのバリアントで値を保持するだけで十分です。これは prev と next ポートの間に長い接続を作成することで行うことができますが、通常、より便利な方法はいくつかのバリアントで next ポートを無効にすることです。
なお、多くの場合、レジスタの代わりに 累積 フィルタ(AccumulateElements、AddIntegers_OfLoop および他の OfLoop)を使用することも可能です。これらのフィルタのそれぞれには、連続した呼び出しの間に情報を格納する内部状態があり、そのための明示的な接続は必要ありません。したがって、これはメソッドとしてはより単純で可読性があり、優先すべきです。ただし、レジスタはより一般的です。
マクロフィルタのレジスタに対するもう1つの代替手段は、prev オペレーターを使用することです。フォーミュラブロック内でのみ使用できます。
レジスタは、C/C++ の変数に対応しています。ただし、Aurora Vision Studioでは、レジスタは関数型およびデータフロープログラミング言語に典型的な シングルアサインメントルール に従います。 これは変数が各反復で最大1回しか割り当てられないというルールです。この方法で作成されたプログラムは理解しやすく、分析しやすくなります。
例: 最大公約数の計算
タスク マクロフィルタとレジスタを使用すると、非常に複雑なアルゴリズムを作成することができます。 たとえば、最大公約数の関数をAurora Vision StudioとC/C++でどのように実装できるかの比較例を以下に示します:
|
int gcd(int inA, int inB) { int a = inA; int b = inB; while (a != 0) { int tmp = a; a = b % a; b = tmp; } return b; } |
フィルタの実行順序
フィルタは常に上から下に実行されると仮定できます。 内部的には、フィルタの実行順序は変更される可能性があり、一部のフィルタはマルチコアプロセッサの利点を活用するために並列で動作することがありますが、これは上から下への実行がまったく同じ結果を生み出す場合のみです。 具体的には、すべてのI/O関数は決して並列化されません。したがって、1つのI/O操作が別のI/O操作よりも上にある場合、常に最初に実行されます。