Python3でLeap Motion

# 記事の目的
[Leap Motion](https://www.ultraleap.com/product/leap-motion-controller/)というモーショントラッカーがあります。
これのPython用APIを使って、親指と人差し指の距離をリアルタイムに取得することを試みました。
PythonでLeap Motionを使う方法を紹介した記事は多くありますが、私の環境ではなかなかスムーズにいきませんでした。
ので、自分用メモとして方法を残します。

# 更新履歴
2020年11月16日：
SDKのV2をDLするためのリンクが切れていたので、現在のリンクを貼りました。

# 環境
- Windows 10 64bit
- Python 3.7.5

# まずPython 2.xで
Leap MotionのPython APIは、Python 2.xにしか対応していません。
Python3系列で動かすのはあとにして、まずはPython 2.7で動かしてみます。
Anacondaを使って、Python2.7環境を作ります。

## Leap Motion SDKの準備
次に、[Leap Motion Developerのサイト](https://developer.leapmotion.com/setup)から~~[デスクトップ用のSDK](https://developer.leapmotion.com/setup/desktop)をダウンロードします。
V2とV4の選択肢がありますが、V2を選びます。~~

（2020年11月16日追記）リンクが切れていました。
現在のところ、[SDKのV2はこちら](https://developer.leapmotion.com/legacy-v2/)からダウンロードできるようです。
Windows版の2.3.1をDLしてください。

DLしたzipを解凍すると、中にインストーラがあるので実行しましょう。（←Visualizerいらないなら不要？）
その後、
C:\Program Files\Leap Motion Core Services\Visualizer.exe
を実行すると、Leap Motionの動作確認ができるのでやってみましょう。

![ss1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304493/189d9497-12f5-8861-dc4d-b5f112f0ffed.png)

Visualizerに何も表示されないようでしたら、[この記事](http://www.langhaar.org/wp/?p=1918)を参考にdpinst64.exeを実行してドライバをインストールしてください。

インストーラと一緒に、LeapSDKというフォルダがあります。
さらにその中のlib/Leap.pyを`import`することで、PythonからLeap Motionを扱えるようです。
しかし、例えばLeap.pyと同じディレクトリへ移動して`import`しても、`Import Error: No module named LeapPython`となってしまいます。
[こちらの記事](https://blog.mezquita.jp/archives/4043)を参考に、Leap.pyと
LeapSDK/lib/x64の中身と、\_\_init\_\_.py（空のファイル）をひとつのフォルダ（ここでは、leapmotionという名前にします）内にコピーします。
leapmotionがあるディレクトリに移動して、Pythonから`from leapmotion import Leap`とすると、Leap MotionのAPIが使用できるようになります。

## Leap Motion APIの使用

[このあたりの記事](https://qiita.com/neka-nat@github/items/e2e87309670bfe600b49)や[公式のドキュメント](https://developer-archive.leapmotion.com/documentation/v2/python/devguide/Leap_Overview.html)や`dir()`を駆使し、親指と人差し指の距離を出力します。

下記のようにすると、その瞬間における手の情報を`frame`として取得できるようです。

```python
controller = Leap.Controller()
frame = controller.frame()
```

ちなみに、`controller.frame(1)`とやると、1フレーム前の情報が得られるようです。

`frame`のうち指の情報は、`frame.fingers`に収められています。
各指の情報は、以下のように取り出すようです。

```python
frame.fingers[0]    # <- Thumb（親指
frame.fingers[1]    # <- Index（人差し指
frame.fingers[2]    # <- Middle（中指
frame.fingers[3]    # <- Ring（薬指
frame.fingers[4]    # <- Pinky（小指
```

親指と人差し指の指先の距離を出力するためには、下記のようにします。

```python
pos_thumb = frame.fingers[0].tip_position
pos_index = frame.fingers[1].tip_position
print(pos_thumb.distance_to(pos_index))
```

まとめると、こんな感じです。

```python
from time import sleep

from packages.leapmotion import Leap

controller = Leap.Controller()

THUMB = 0
INDEX = 1
MIDDLE = 2
RING = 3
PINKY = 4

while not controller.is_connected:
    print('Connecting...')
    sleep(.5)
    pass
print('...Connected!')

while True:
    sleep(.01)
    frame = controller.frame()

    pos_thumb = frame.fingers[THUMB].tip_position
    pos_index = frame.fingers[INDEX].tip_position

    print(pos_thumb.distance_to(pos_index))
```

# Python3で動かす
[こちらの記事](https://support.leapmotion.com/hc/en-us/articles/360004362237-Generating-a-Python-3-3-0-Wrapper-with-SWIG-2-0-9)をもとにやっていきます。
記事の設定と違うのは、64bitのWindowsを使っていること、PythonのVer.が3.7であることです。

## Swig
Swigを使って、Python用のプログラムを生成してもらいます。

### インストール
[このサイト](http://rinatz.github.io/swigdoc/install.html)を参考に、WindowsにSwigをインストールします。
記事執筆時点での最新版は4.0.1でしたが、4.0.0以降だとエラーを招くようです（[参考](https://github.com/NanoComp/meep/issues/965)）。
3.0.12でもエラーは（恐らく）出ませんが、記事の通り2.0.9を使います。
swig.exeまでのパスを環境変数PATHに追加するところまでやりましょう。

### スクリプトの手直し
適当なフォルダを作って下記ファイルをコピーします。
- LeapSDK/include/Leap.h
- LeapSDK/include/LeapMath.h
- LeapSDK/include/Leap.i
- LeapSDK/lib/x64/Leap.lib

このフォルダでSwigを実行すればいいのですが、Leap.iがイマイチちゃんとしていないようでエラーになります。
[こちらの記事](https://vaaaaaanquish.hatenablog.com/entry/2018/09/23/222002)を参考に、手動で直します。
Leap.iをテキストエディタで開いてください。
`%pythoncode {%  }}`で囲われている部分がPython用のコードなのですが、インデント（字下げ）に間違いがあります。
`def`の前など、インデント不要なところはインデントなし、そのほかのインデントは空白4つになるように直しましょう。
一応、[手直ししたLeap.i](https://issakuss.github.io/tech/article_13/Leap.i)を置いておきます。

コマンドプロンプトにて、そのフォルダの場所へ`cd`し、下記コマンド。

```
swig -c++ -python -o LeapPython.cpp -interface LeapPython Leap.i
```

エラーメッセージがでたら、それをもとにLeap.iのインデントを再度見直しましょう。
成功すれば、Leap.py、[LeapPython.cpp](https://issakuss.github.io/tech/article_13/LeapPython.cpp)、LeapPython.hが同じフォルダ内に作成されます。

## Visual Studio
Visual Studioを使い、先ほど生成されたLeapPython.cppを、Pythonから動かせるような形でコンパイルします。

### インストール
Python3.7の場合、最新の2019版でなく2017版を用意する必要がある（[参照](https://www.python.jp/install/windows/install_vstools2017.html
)）とのことなので、そちらをDLします。
https://docs.microsoft.com/ja-jp/visualstudio/releasenotes/vs2017-relnotes
![ss2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304493/8350c8c6-19b7-9dd0-a312-f6c953916f51.png)
Download Enterprise 2017を選んで、インストーラをDL＆起動。
![ss4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304493/192a69b5-22ca-4009-899d-31fe9c17da50.png)
どうしてよいかイマイチわからなかったので、ワークロードタブからC++によるデスクトップ開発にチェックを入れてインストール。

### 空のプロジェクト作成
メニューバーから [ファイル]→[新規作成]→[プロジェクト]。
左のメニューから[Visual C++]→[空のプロジェクト]を選択。
名前と場所を適当に決めて [OK]。
![ss9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304493/daca42ef-1dbb-c1e9-dd6e-ad73025057c8.png)
プロジェクトのフォルダに、先ほどSwigを動かしたフォルダの中身をすべて（LeapPython.cppだけでもよい？）コピーしてください。
.filtersなんかがあるファイルと同じディレクトリです。


### いろいろ設定
[表示]→[ソリューションエクスプローラー]。
ソリューションエクスプローラーのプロジェクト名を右クリックして[追加]→[既存の項目]。
先ほどSwig済のフォルダからコピーしたファイルをすべて[追加]します。

次は、再度プロジェクト名を右クリックして [プロパティ]。
[プロパティ]が見当たらない場合は、下のスクショのように、上部のアイコンを使ってフォルダビューからソリューションビューに変更。
![ss5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304493/c96a2c53-9785-d83d-949b-1220e7c2a7c8.png)
構成マネージャーをクリックし、校正をRelease、プラットフォームをx64に変えます。
[全般]メニューを選び、[ターゲット名]をLeapPython、[構成の種類]をダイナミックライブラリ（.dll）に設定。
![ss6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304493/9fac54b6-af14-dd87-827d-1dc4d78e022b.png)
続いて、[C/C++]→[全般]メニューを開き、追加のインクルードディレクトリに、Pythonのincludeフォルダを指定します。
例えば私のAnaconda（Miniconda）環境ですと、
C:\Users\（ユーザー名）\Miniconda3\envs\（環境名）\include
といった感じです。
![ss7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304493/4d115e4e-b9f7-f1bd-a070-99a868ee0ef6.png)
さらに[リンカー]→[入力]メニューへ飛び、追加の依存ファイルのところに Leap.libと、python37.libへのフルパスを追記します。
python37.libは下記の場所などにあるはずです。
C:\Users\（ユーザー名）\Miniconda3\envs\（環境名）\libs\python37.lib
![ss8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304493/fda8d749-ecf3-9016-8395-44233409a511.png)
設定完了です。
プロパティを閉じましょう。
### ビルド
コンパイルを実行します。
プロジェクト名を右クリック→[ビルド]
エラーメッセージがどっと出てきたら、python33.libとLeap.libがちゃんと64bit用のものになっているか確認してください。
ビルドが終了すると、表示されているディレクトリにLeadPython.dllが作られています。
これの名前を変更して、拡張子を.pydにしてください。
一応、[生成されたLeadPython.pyd](https://issakuss.github.io/tech/article_13/LeapPython.pyd)を置いておきます。

## Python3.7で実行
Python2.7で動かした、LeapSDKの中身をあれこれ突っ込んで\_\_init\_\_.pyを作ったフォルダを開いてください。
このうち、Leap.pyとLeapPython.pydを、SwigやVisual Studioで生成されたものに置き換えます。

最後にまた、Leap.pyを手動で直す必要があります。
テキストエディタで開きましょう。
あちこちに、余計な`%`が紛れていますので、消してください。
半角スペース2個＋%を置換して消してやると速いです。
[手直ししたLeap.py](https://issakuss.github.io/tech/article_13/Leap.py)がこちらです。

これで、Python2.7でやったときと同様にLeap MotionのデータをPython3.7で取得できます。

## PsychoPyと組み合わせる（おまけ）
こんなスクリプトを書いて↓

```python:leapmotion.py
from time import sleep

from .leapsdk import Leap


class LeapMotion:
    def __init__(self):
        self.controller = Leap.Controller()
        self.fingers = {'thumb': 0,
                        'index': 1,
                        'middle': 2,
                        'ring': 3,
                        'pinky': 4}

        while not self.controller.is_connected:
            sleep(.5)

    def calc_dist(self, finger_from: str, finger_to: str):
        frame = self.controller.frame()
        pos_from = frame.fingers[self.fingers[finger_from]].tip_position
        pos_to = frame.fingers[self.fingers[finger_to]].tip_position
        return pos_from.distance_to(pos_to)

```

こんな感じで動かして↓

```python
from time import sleep

from packages.device.leapmotion import LeapMotion

from psychopy import visual

leapmotion = LeapMotion()
window = visual.Window(
    colorSpace='rgb255',
    fullscr=False,
    size=(900, 900),
    allowGUI=True,
    wintype='pygame',
    units='pix')
circle = visual.Circle(window, size=1, autoDraw=True)

while True:
    circle.size = leapmotion.calc_dist('thumb', 'index')
    window.flip()
    sleep(.01)
    print(leapmotion.calc_dist('thumb', 'index'))
```

こんな風になりました！↓
![ezgif-7-145ce88082b1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304493/84fed501-104a-9439-ec54-598d4d6671ad.gif)


## Visual Studioの代わりにMinGWで（失敗）
MinGWでインストールしたg++を使ってコンパイルしてみようとしたのですが、うまくいきませんでした。
詰むまで記録を残しておきます。

### インストール
[こちらの記事](https://www.javadrive.jp/cstart/install/index6.html)をもとにMinGW-w64をインストールします。
PATHに追加するところまでやったらOK。
ちなみに私の場合は下記を追加しました。
C:\Program Files\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin

そして、コマンドプロンプトでSwig済フォルダへ`cd`し、

```
g++ -I C:\Users\issakuss\Miniconda3\envs\study09t\include LeapPython.cpp Leap.lib C:\Users\issakuss\Miniconda3\envs\study09t\libs\python37.lib -shared -o LeapPython.pyd 
```

これで、Visual Studioでやったのと同じことができると思ったのですが、エラーがたんまり出てしまい詰みました。
![ss10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304493/7a09b356-c776-4e27-50ae-5fd866890d6f.png)
こっちでできたらVisual Studioインストールしなくて済むので楽だったんですが。