python で RTコンポーネントを作る

環境設定

パッケージのインストール

最初に、pythonのためのパッケージのインストールが必要になります。
端末から、次のコマンドを実行してください。

 % sudo aptitude install openrtm-aist-python openrtm-aist-python-example omniidl4-python python-omniorb2-omg

このコマンドで、以下の四つのパッケージをインストールします。

Eclipse に PyDev をインストール

PyDev は、Eclipse で python を扱うプラグインです。
Eclipse のメニューから「ヘルプ」~「ソフトウェア更新」を選びます。
「ソフトウェア更新およびアドオン」ダイアログが開くので、「サイトの追加」ボタンを押してロケーションに「http://pydev.org/updates 」を入力します。
AddSite.png
使用可能なソフトウェアのタブに、この http://pydev.org/updates が追加されるので、これを開いてPyDev の下にある PyDev for Eclipse をチェックして、インストールボタンを押します。
PyDev.png

今回の目標

二つのRTコンポーネント VisionManipulation と MyScheduler を作成して、すでに作成済みの RTコンポーネント VVVRecogTrigger からデータを取得します。

メインとなるのは VisionManipulation です。
スケジューラからモデルのIDを受け取ったら、recogSDL サービスポートを通して VVVRecogTrigger に渡します。
VVVRecogTrigger がモデルIDを認識した結果は、recogResult データポート経由で受けとります。

RTコンポーネントを作成する

以下のページを参照してください。

RTコンポーネントを接続する

二つのRTコンポーネントができたので、まずはローカルで接続してみます。

Eclipse で RT System Editor パースペクティブを開き、ツールバーのONを押して System Diagram を開きます。
ツリーの「localhost:2809」を開くと、その下にさらに「(ホスト名)|host_cxt」というツリーがあると思うので、それも開いておきます。
いくつかRTコンポーネントが表示されるかもしれませんが、どれも□のアイコンで、起動できません。

端末ウインドウを二つ開き、一つでMyScheduler.py, もう一つで VisionManipulation.py を実行します。

すると、「(ホスト名)|host_cxt」ツリーの下に「MyScheduler0|rtc」「VisionManipulation|rtc」という項目が増えます。凸凹の文字を底でくっつけて倒したような形のアイコンになっています。

これを両方 System Diagram にドラッグ&ドロップすると、小さな□のついたブルーの長方形が現れます。これがRTコンポーネントです。
それぞれの下に、MyScheduler0, VisionManipulation0 と名前がついているのを確認してください。
また、小さな□はポートを示しています。ポートにマウスカーソルを合わせると、ポートの名前がわかります。

RTComponents.png

VisionManipulation には4つのサービスポートと1つのデータポートがあるので、右側に4つの□、左側に欠けた□が出ています。
4つの□のひとつが VisionManipulation0.scheduler となっています。
これをMyScheduler0の□にドラッグ&ドロップすると、「ポートプロファイルを入力してください」というダイアログが出るので、そのままOKを押します。
するとポートとポートが線でつながれます。
PortProfile.pngRTCConnected.png

ここでツールバーの緑のプレイボタンを押すと、RTコンポーネントがアクティベートされます。
双方が一瞬緑になったあと、MyScheduler側がエラーで赤くなります。
VisionManipulation を実行している端末のエラーメッセージを確認してください。

 setModelID: 42
 omniORB: Caught an unexpected Python exception during up-call.
 Traceback (most recent call last):
  File "/home/asahi/workspace/VisionManipulation/VisionManipulation_idl_example.py", line 48, in setModelID
    coord = self.recogSDL_service._ptr().recognize_by_ID(ModelID)
 AttributeError: 'NoneType' object has no attribute 'recognize_by_ID'

一行目でsetModelID: 42 が出ているので、MySchedulerからModelIDを受け取れているのがわかります。
その後出ているエラーは、recogSDL_service が未定義のため、recognize_by_ID を呼び出せなかったというエラーです。

これは VisionManipulation0 の recogSDL ポートに何もつないでいないのだから、当然です。

これでひとまず、MySchedulerからVisionManipulationの呼び出しができたということで、いよいよカメラとつないでみることにします。

(作成中)

MyScheduler RTC を作る

MyScheduler は、VisionManipulation にモデルIDを送るだけの、シンプルなRTコンポーネントです。
ここでは、EclipseやRTC Builderのチュートリアルも兼ねて、画面キャプチャつきで詳細に説明します。

PyDev プロジェクトを作成する

新規プロジェクトとして、PyDevの下にあるPyDev Project を選び、「次へ」。
NewProject.png

PyDev Project は、
• Project name: MyScheduler
• Project contents: use default
• Project type: Python
• Grammer Version: 2.6
• Interpreter: Default
• Don't configure PYTHONPATH
PyDevProject.png
とします。ほとんどデフォルトのままだと思いますが、Project name と Grammer Version には注意してください。
PyDev パースペクティブを開きますか?と尋ねられますが、すぐに RTC Builder パースペクティブを開くので、いいえで済ませましょう。

PyDev Project を作成すると、workspace ディレクトリに新しく MyScheduler ディレクトリができます。

IDL ファイルを用意する

workspace の下に idl ディレクトリを作り、そこに VisionManipulation.idl をコピーします。

RtcBuilder を使う

Eclipse で、RTC Builder パースペクティブを開きます。
メニューバーの「ウィンドウ(W)」~「パースペクティブを開く」~「その他」を選ぶと、下のような画面が開きます。
OpenPerspective.png
ここでRTC Builder を選びます。RTCBuilderPerspective_0.png
ツールバーのトンカチアイコンをクリックすると、空のRTC Builderが開きます。
EmptyRTCBuilder.png

最初は基本タブが表示されていますが、この画面は RtcBuilderのエディタ画面の下にあるタブで、切り替えることができます。
このタブを切り替えながら、必要な項目を入力していくことになります。

基本タブ

RT-Component Basic Profile 内の項目を以下のように設定します。

RT-Component Basic Profile セクション

  • モジュール名: MyScheduler
  • モジュール概要: Scheduler for test
  • バージョン: 1.0.0
  • ベンダ名: AIST
  • モジュールカテゴリ: VMRG

Output Project セクション

Output Project セクションでは、参照ボタンをクリックして、さきほど作成した MyScheduler プロジェクトを選びます。
Basic.png

アクティビティタブ

利用するアクションコールバック関数をONにします。
今回は onExecute のみを使います。
各コールバック関数の名前がアクションごとに並んでいますが、
「Dataflow型コンポーネントのアクション」のコーナーの左端にある「onExecute」をクリックして、Documentation セクションのアクティビティ名: onExecute となっている右側のスイッチで、ONを選択します。
アクティビティセクションのonExecuteのバックに色がつきます。
Activity.png

サービスポートタブ

ServicePort.png
Add Port ボタンを押すと、新しいポート sv_name が出現します。sv_nameをクリックすると 右側に RT-Component Service Port Profile 画面が開きます。
  • ポート名: Scheduler
  • 表示位置: RIGHT

と設定したら、今度はインターフェースの設定です。
AddPort.png

Add Interfaceボタンを押すと、Scheduer ポートに if_name インターフェースが現れます。if_nameをクリックすると、RT-Component Service Port Interface Profile 画面が開きます。
AddInterface.png

  • インターフェース名: scheduler
  • 方向: Required
  • インスタンス名: scheduler
  • 変数名: scheduler
  • IDLファイル: /home/asahi/workspace/idl/VisionManipulation.idl
  • インターフェース型: SchedulerService
  • IDLパス: /home/asahi/workspace/idl/

ポートとインターフェースの設定は、下のBuildViewに反映します。
BuildView.png

言語・環境タブ

これが最後の設定部分です。
言語セクションで、Python を選択してください。

コード生成

設定がすべて終わったので、基本タブに戻ります。

「コード生成とパッケージ化」セクションのコード生成ボタンをクリックしてください。
「Generate success.」というダイアログが出れば無事完了です。

ここで、「'SchedulerService' is not found in IDL」というエラーが出ることがあります。
そのときは、VisionManipulation.idl を編集して、一行目の #include 文をコメントアウトしてください。

 //#include <rtm/idl/BasicDataType.idl>

もう一度コード生成ボタンを押せば、「Generate success.」というダイアログが出るでしょう。

ここで MyScheduler ディレクトリを見ると、以下のファイルが生成されています。
  • MyScheduler.py: 生成されたRTコンポーネントプログラムの本体
  • MyScheduler.conf: RTコンポーネントの設定ファイルの元
  • idlcompile.bat: IDLファイルをコンパイルするためのコマンド(Windows用)
  • idlcompile.sh: IDLファイルをコンパイルするためのコマンド(Linux用)
  • RTC.xml: エクスポートされた設定

一連の設定は、MyScheduler ディレクトリの下に、RTC.xml というファイルとして保存されます。
RTコンポーネントの設定を変更する必要が出たときは、基本タブの下にある「インポート」ボタンでこのファイルを読み込み直せば、ここまでの設定が再現されます。

MyScheduler.conf は空のファイルなので、以下の内容に書き換えます。

corba.nameservers: localhost:2809
exec_cxt.periodic.rate:2

できたら、rtc.conf にシンボリックリンクを貼ります。
 % ln -s MyScheduler.conf rtc.conf

IDLコンパイル

端末を作成し、workspace/MyScheduler に chdir します。
ここで

 % sh idlcompile.sh

を実行するのですが、現状ではうまく行きません。
 VisionManipulation.idl:19: Error in look-up of 'RTC::TimedDoubleSeq': 'RTC' not found
 VisionManipulation.idl:34: Error in look-up of 'RTC::TimedDoubleSeq': 'RTC' not found
 omniidl: 2 errors.

まず、先ほど VisionManipulation.idl を変更した場合、workspace/MyScheduler に同じファイルがコピーされています。
これをもう一度編集して、一行目のコメントアウトを外してください。

そして、idlcompile.sh を次のように書き換えます。

 omniidl -bpython -I/usr/include VisionManipulation.idl 

-I/usr/include を挿入しています。

編集したファイルをすべて保存したら、あらためて

 % sh idlcompile.sh

を実行してください。
何もメッセージは出ませんが、
• _GlobalIDL_POA
• _GlobalIDL_
• VisionManipulation_idl.py
ができています。

テスト実行

実はこれでもう動きます。端末で python MyScheduler.py を実行してください。

 % python MyScheduler.py 
 comp_args: MyScheduler

プロンプトに戻らなければRTコンポーネントが動いています。
さらに、Eclipse で RT System Editor パースペクティブを開きます。
Name Service ビューの中に、MyScheduler0|rtc という項目があったら、これをSystem Diagram にドラッグ&ドロップしてください。
さらにこれをActivateして、コンポーネントが青から緑になればOKです。

端末へ戻り、Controlキーを押しながらCを押してみます。
実行中のRTコンポーネントが終了すると、System Diagram 上のRTコンポーネントも消えることを確認してください。

実装

Eclipse のパースペクティブを PyDev に切り替え、PyDev Package Explorer で MyScheduler を開きます。
アウトラインビューで、MySchedulerクラスの onExecute メソッドを見つけたら、クリックしてください。

 return RTC.RTC_OK

の前の行に、プログラムの実体を記入します。

ここでやりたいのは、Scheduler ポートの scheduler インターフェースの先に接続したRTコンポーネント(これから作成する VisionManipulation)の、setModelID メソッドを呼ぶことです。

そのためのコードはこうです。

 self._scheduler._ptr().setModelID(42)

self._scheduler が、先にRTC Builder のサービスポートタブで設定した、Schedulerポートのschedulerインターフェースのscheduler変数に当たります。(schedulerが続いてわかりにくいですが察してください)

その _scheduler が持つ、_ptr() というメソッドが、System Diagram画面で接続された先のRTコンポーネントを指しています。
このコンポーネントが持つ setModelID を呼び出しているわけです。

ついでに print 文も追加して、setModelIDを呼ぶと同時にメッセージを表示させましょう。

    def onExecute(self, ec_id):
        self._scheduler._ptr().setModelID(42)
        print "setModelID(42)"
        return RTC.RTC_OK
    

onExecute メソッド全体では以上のようになります。

python なので、再コンパイルの必要はありません。
先ほどと同じように python MyScheduler.py を実行し、エラーが出ないことを確認してください。

完成

これで MyScheduler RTC は完成しました。
引き続き、VisionManipulation RTC を作る に移ります。

VisionManipulation RTC を作る

いよいよ本体の VisionManipulation RTC の作成に入ります。

MyScheduler RTC からの setModelID の呼び出しを受けたら、VVVRecogTrigger RTC にこのコマンドを飛ばし、結果を recogResult データポートで受け取って表示するRTコンポーネントです。

概念としてはこういう形になります。

RTC Builder

設定する項目だけ列挙します。設定の仕方の詳細は MyScheduler RTC を作る を参照してください。

Eclipse プロジェクト名: VisionManipulation

基本タブ

  • モジュール名: VisionManipulation
  • モジュール概要: Vision Manipulation
  • バージョン: 1.0.0
  • ベンダ名: AIST
  • モジュールカテゴリ: VMRG
  • Output Project: VisionManipulation

アクティビティタブ

  • onExecute: ON

サービスポートタブ

Scheduler ポート

MyScheduler の Schedulerポートとほぼ同じですが、コマンドを呼ばれる側なので方向が Provided になります。

  • 表示位置 LEFT
  • scheduler インターフェース
    • 方向: Provided
    • インスタンス名: scheduler
    • 変数名: scheduler
    • IDLファイル: /home/asahi/workspace/idl/VisionManipulation.idl
    • インターフェース型: SchedulerService
    • IDLパス: /home/asahi/workspace/idl/

recogSDL ポート

VVVRecogTrigger RTC を接続するポートです。
  • 表示位置: RIGHT
  • recogSDL インターフェース
    • 方向: Required
    • インスタンス名: recogSDL
    • 変数名: recogSDL
    • IDLファイル: /home/asahi/workspace/idl/VisionManipulation.idl
    • インターフェース型: RecognitionServiceSDL
    • IDLパス: /home/asahi/workspace/idl/

以下のポートは今回は実装しませんが、今後の拡張のためにあらかじめ定義しておきます。

recogPort ポート

  • 表示位置: RIGHT
  • recogPort インターフェース
    • 方向: Required
    • インスタンス名: recogPort
    • 変数名: recogPort
    • IDLファイル: /home/asahi/workspace/idl/VisionManipulation.idl
    • インターフェース型: RecognitionService
    • IDLパス: /home/asahi/workspace/idl/

PlanStartPort ポート

  • 表示位置: BOTTOM
  • PlanStart インターフェース
    • 方向: Required
    • インスタンス名:
    • 変数名: plan_start
    • IDLファイル: /home/asahi/workspace/idl/VisionManipulation.idl
    • インターフェース型: GraspPlanStart
    • IDLパス: /home/asahi/workspace/idl/

言語・環境タブ

  • 言語: Python

データポートタブ

ここでrecogResult ポートを定義します。
前回は設定しなかったタブなので、ここだけ詳しく解説します。
DataPort.png
DataPort プロファイルのセクションが、「*ポート名(InPort)」と「「*ポート名(OutPort)」」の二つのリストに分かれています。
このうちのInPort側のAddボタンを押すと、ポート名リストに「dp_name」が加わり、Detail セクションにポート情報が入ります。
ポート名リストのdp_nameをクリックすると名前の変更ができるので、ポート名 recogResult を設定します。

すると、ここで設定したポート名がDetailセクションのポート名として反映します。
さらに、Detail セクションの項目を以下のように設定します。

  • データ型: RTC::TimedDoubleSeq
  • 変数名: recogResultIn
  • 表示位置: RIGHT

このとき、データ型が選択できないことがあります。
そのときは、RtcBuilder にIDLファイルのパスを設定します。
メニューバーからウィンドウ~設定を選び、左のツリーからRtcBuilderを選択します。
すると「データ型: IDL File Directories」というリストが現れます。
右の「新規」ボタンを押して、場所に「/usr/include/rtm/idl」を追加します。
IDLFileDirectories.png
そうすると、IDLファイルからデータ型を読み込んで、たくさんの候補が選択できるようになります。

これで、倍精度浮動小数点数の配列が流れてくる入力データポート、recogResult が定義できました。

コード生成

BuildViewはこのような形になっています。
VisionManipulationBuildView.png

コード生成を行ってください。
「'SchedulerService' is not found in IDL」が出るようなら、IDLのinclude文をコメントアウトします。

端末からの設定

VisionManipulation.conf の内容を以下のように修正して、rtc.conf にシンボリックリンクを貼ります。

corba.nameservers: localhost:2809
exec_cxt.periodic.rate: 10

次に、idlcompile.sh を実行します。
エラーが出る場合は、スクリプトのコマンドラインに -I/usr/include を挿入して、やり直してください。

実装

MyScheduler RTC が setModelID を呼び出すと、VisionManipulation では SchedulerService_i::setModelID が呼び出されます。
そこで、VisionManipulation_idl_example.py の該当部分を以下のように修正します。

    # void setModelID(in long ModelID)
    def setModelID(self, ModelID):
        print "setModelID: %s" % ModelID
        
        coord = self.recogSDL_service._ptr().recognize_by_ID(ModelID)
        print "recognize_by_ID: %s" % coord

端末にModelIDを表示し、recogSDL_service を通して recognize_by_ID メソッドを呼んでいます。

その結果はデータポート recogResultIn で受けることになります。
ところで、self.recogSDL_service はまだ定義していません。
これは、呼び出し元からセットしてもらうことにします。

SchedulerService_i クラスに新しいメソッドを追加します。

    def setRecogSDLService(self, recogSDL_service):
        self.recogSDL_service = recogSDL_service
        

次に、呼び出し元のVisionManipulation.py を開きます。
初期化の時に、SchedulerService_i のインスタンスに、recogSDL_service をセットしてやります。
onInitialize メソッドの最後(return RTC.RTC_OK の前の行)に一行追加してください。
        self._scheduler.setRecogSDLService(self._recogSDL)

そうして、onExecute メソッドでデータポートを監視します。
コードは以下のようになります。

    def onExecute(self, ec_id):
        if self._recogResultIn.isNew():
            print "resultIn:"
            indata = self._recogResultIn.read()
              val = indata.data
              print val
            print "\n"
                   
        time.sleep(0.01)

        return RTC.RTC_OK

今回は、流れてきたデータをそのまま端末に表示しているだけですが、いずれこれを整形して PlanStartPort に渡すなどの処理を追加する必要があるでしょう。