2020年6月29日月曜日

matplotlibでアニメーション(ライフゲーム)

ライフゲームを表示するスクリプトをmatplotlibのアニメーションで書いたものがこちら。 前のものは、FuncAnimation()へ引き渡す関数をlambda式で作成することで、 フレーム番号以外のパラメータを関数に持たせて受け渡す方法を用いましたが、 Pythonなら、クラスを用いるのもよいです。 クラスにすると、なにかパラメータを保持させていることが明確です。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation

def Rule(alive, neighbours):
    n = sum(neighbours)
    if not alive and n==3:
        return 1 
    if alive and n==2 or n==3:
        return 1 
    if alive and n<=1:
        return 0 
    if alive and n>=4:
        return 0 
    else:
        return alive

def LifeGameUpdate(world, rule):
    w, h = world.shape
    updated_world = np.zeros((w, h))
    for i in range(h):
        for j in range(w):
            im, ip = (i-1) % h, (i+1) % h
            jm, jp = (j-1) % w, (j+1) % w
            neighbours = (
                    world[im, jm], world[im, j], world[im, jp], 
                    world[i, jm], world[i, jp],
                    world[ip, jm], world[ip, j], world[ip, jp])
            updated_world[i, j] = rule(world[i, j], neighbours)
    return updated_world

class LifeGame(object):
    def __init__(self, world, rule):
        self.world = world
        self.rule = rule

    def __call__(self, frame_count):
        plt.cla()
        plt.imshow(self.world)
        self.world = LifeGameUpdate(self.world, self.rule)

if __name__ == '__main__':
    fig = plt.figure()
    world = np.round(np.random.random((100, 100)))
    lifegame = LifeGame(world, Rule)
    anim = matplotlib.animation.FuncAnimation(fig, lifegame, interval=10)
    plt.show()
            

2020年6月28日日曜日

matplotlibでアニメーション

ある画像データをパラメータの変化に合わせてアニメーションをさせながら表示させたい。 そのような場合のメモをしておく。

pcolormesh()を用いて画像データをプロットし、 その下にplot()を用いたパラメータの変化を示す。 さらに、pcolormesh()で表示されている場所をパラメータの変化のグラフ上に線で示す。 pcolormeth()に対するカラーバーの位置も制御したい。 レイアウトの自由度を持たせるために、add_axes()メソッドを用いている。

アニメーションは、

matplotlib.animation.ArtistAnimation
matplotlib.animation.FuncAnimation
のどちらかを用いて行える。

ArtistAnimationでは、各フレームのArtistオブジェクトを保存して、 Animationを作成する。 フレーム数が多くなる場合も考えると、FuncAnimationを利用したい。 ArtistAnimation, FuncAnimationとも、blitキーワードをTrueに設定すると、 指定した場所のみ書き換えるようになるため速く描画できると思われるが、 グラフ(Axes)の外は書き換えが行われなかったりと制約もできてしまう。 pcolormesh()の書き換えでは、軸の枠線がプロットに上書きされて消えてしまうようなことも起こる。 この場合は、blit=Falseを指定することをお勧めする。

MakeData()で、(nx, ny, nz)のshapeをもつデータを作成し、 各zに対する幅nx、高さnyのデータとみて、プロットをしてみる。

FuncAnimation()には、各フレームごとに呼び出される関数を渡す必要がある。 その関数には、フレーム番号を受け取る一つの引数が必要となる。 実際のupdate()関数では、関数内部で用いるfigure、 data、az とフレーム番号を受け取るように書きたい。 そうすると、引数が多くなってしまうので、lambdaを用いて、引数をフレーム番号のみに減らし、 各フレームごとに呼び出される関数としている。

最後のほうにある、

anim.save('anim.gif', writer='pillow')

のコメントを外せば、アニメーションGIFファイルとして保存できる。 writer='pillow'を指定するためにpillow(PIL)をインストールしておく必要がある。

モジュールのバージョン
python : 3.8.3
matplotlib : 3.2.2
numpy : 1.19.0
PIL(pillow): 7.1.2


import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np

def MakeData(nx, ny, nz):
    data = np.ones((nx, ny, nz))
    ax = np.linspace(-3, 3, nx)
    ay = np.linspace(-3, 3, ny)
    az = np.linspace(0, np.pi, nz)
    for k, z in enumerate(az):
        for j, y in enumerate(ay): 
            for i, x in enumerate(ax):
                data[i, j, k] = np.sin(x+z) * np.cos(y+z)
    return data, az

def update(fig, data, az, iz):
    fig.clear()
    # create axes
    ax0 = fig.add_axes([0.1, 0.4, 0.7, 0.5])
    cbar_ax0 = fig.add_axes([0.82, 0.4, 0.05, 0.5])
    ax1 = fig.add_axes([0.1, 0.1, 0.7, 0.2])
    
    # plot data
    im0 = ax0.pcolormesh(data[:, :, iz].T)
    fig.colorbar(im0, cax=cbar_ax0)
    ax1.plot(az, '-', color='#0000FF')
    ax1.plot([iz, iz], [0, np.pi], '-', color='#FF0000')

    # put text labels
    label = ax1.text(0.1, 0.8, '%.5g (rad)' % az[iz], 
            transform=ax1.transAxes)
    ax0.set_title('Frame %d' % iz)


nx, ny, nz = 30, 60, 100
data, az = MakeData(nx, ny, nz)

fig = plt.figure(figsize=(5, 6))

anim = FuncAnimation(fig, lambda iz: update(fig, data, az, iz), nz, 
        interval=100, blit=False, repeat=False)

#anim.save('anim.gif', writer='pillow')

plt.show() 

2019年7月6日土曜日

USBを使って制御できるリレーモジュールをpythonで動かしてみる

Amazonで検索すると、QuimatリレーモジュールQY15なるものが見つかったので購入してみました。 4個のリレーSRD-05VDC-SL-CをUSBからの出力でON/OFFできるようです。 FT245RLというUSB-パラレル変換のチップが搭載されているようです

カスタマーレビューを見ると、 FTDI社で提供しているライブラリを使い、 BitBangモードにしてバイトデータを書き出せばよいと いうことで、Pythonからも簡単に制御できそうです。

梱包はいたって簡単で、本体とUSBケーブルが1本ついてきました。 それ以外はなにもありません。 USBポートにQY15を接続してみます。 すると、パチッとリレーの動作音がして、LEDが光ります。 デバイスマネージャを見ると、ポートの個所にUSB Serial Portとして認識されていました。

次はソフトウェアです。
Windows10 64bit
Python 2.7 64bit
で試します。

ここ(https://www.ftdichip.com/index.html)のDriversから、 D2XXのドライバーをダウンロードします。 新しいドライバがあるようですが、D2XXは別のページみたいです。 "for D2XX drivers please click here" に従うと、D2XX Driversのダウンロードページに行けます。 試す環境はWindows用64bitなので、それに合わせて64bit版をダウンロードします。 ダウンロードされたCDM v2.12.28 WHQL Certified.zipを展開すると、 中には、ftd2xx.hというヘッダファイル、さらに64bit,32bit版のライブラリが入っていました。 使うのは、64bit環境なのでamd64のフォルダにある、ftd2xx64.dllです。

Pythonは、ctypesモジュールを使ってDLLの関数を呼び出せます。 関数の呼び出し方は、ヘッダファイルftd2xx.hを見ると分かります。 ftd2xx64.dllと同じフォルダーで、Pythonのスクリプトを書いていきます。

まず、ctypesとtimeモジュールをインポートします。 timeモジュールは、動作テストさせるときの待ち時間を作るのに使います。

import ctypes
import time

ctypesモジュールで、DLLファイルをロードします。

dll = ctypes.CDLL('ftd2xx64.dll')

これでDLLを使う準備ができました。

次に、DLLで提供されているFT_Open()関数を使って、制御するためのハンドルを得ます。 ハンドルはポインタですので、ハンドルを格納するポインタ型の領域を確保します。

handle = ctypes.c_void_p()

それでは、FT_Open()関数をコールします。

res=dll.FT_Open(0, ctypes.pointer(handle))

第1引数は、デバイス番号で0を渡します。 第2引数は、結果として得られるハンドルを格納する場所(ポインタを格納する場所へのポインタ)を渡します。 返り値resが0なら、OKです。そうでなければどこか問題があります。

次にボーレート(通信速度)を設定します。

res=dll.FT_SetBaudRate(handle, 921600)

として、921600bpsを設定します。

次に、BitBangモードに設定します。

res=dll.FT_SetBitMode(handle, 0xFF, 1)

第2引数は、バイトデータに含まれる各ビットの入力出力方向を指定するマスクで、すべて出力(1)に設定しています。 第3引数に1を設定することで、BitBangモードを指定します。

あとは、FT_Write()関数でデータを書き出せば、値に応じてリレーがON/OFFするはずです。 各リレーは、1から4ビットに対応していて、各ビットの重みは1、2、4、8になりますので、 3番目のリレーをONにしたければ、4を書き出し、 1番目と3番目のリレーをONにしたければ、1+4=5を書き出せばよいわけです。 そのまえに、FT_Write()関数を使って1バイトデータを書き出す手続きを関数にしておきましょう。

def write_byte(dll, hndl, byte):
    buff = ctypes.c_byte(byte)
    written = ctypes.c_ulong(0)
    return dll.FT_Write(hndl, ctypes.pointer(buff), 1, ctypes.pointer(written))

dll、hndlおよび書き出す1バイトデータを受け取ります。 1バイトデータを格納した、長さ1のバイト型の領域buffを確保します。 書き出されたバイト数を格納するための、領域writtenを確保します。 これら確保した場所へのポインタを、FT_Write()関数に渡して呼び出します。 FT_Write()関数の3番目の引数は、バッファの大きさですので、1を渡します。 この関数を用いて、1番目と3番目のリレーをONにするデータを書き出します。

res = write_byte(dll, handle, 5)

すこし時間を空けて、リレーをOFFにするデータを書き出します。

time.sleep(2)
res = write_byte(dll, handle, 0)

最後に、FT_Close()関数でハンドルを閉じます。

res = dll.FT_Close(handle)

ここまでをまとめて載せます。

import ctypes
import time

def write_byte(dll, hndl, byte):
    data = ctypes.c_byte(byte)
    written = ctypes.c_ulong(0)
    return dll.FT_Write(hndl, 
            ctypes.pointer(data), 1, ctypes.pointer(written)) 

dllfile='ftd2xx64.dll'
dll = ctypes.CDLL(dllfile)
handle = ctypes.c_void_p()
res = dll.FT_Open(0, ctypes.pointer(handle))
res = dll.FT_SetBaudRate(handle, 921600) # 921600 bps 
res = dll.FT_SetBitMode(handle, 0xFF, 1) # All output and bit bang mode 
res = write_byte(dll, handle, 5) 
time.sleep(2)
res = write_byte(dll, handle, 0)
res = dll.FT_Close(handle)         

ftd2xx.dllを扱うPythonのモジュールはあるようですが、 このリレーモジュールを動かす程度なら、自分で書いてしまうほうが楽かもしれません。

2017年4月15日土曜日

Dell Latitude10の最近の調子

もうすぐWindows10のCreaters Updateがくるかもしれないと思いつつ、 Latitude10はWindows10 Version 1607で使用中です。

 先日、1607用の更新プログラムKB15217が2回ほど失敗し、 3回目にしてようやくインストールされました。 ところが、再起動されないで止まってしまうということに。

電源ケーブルはつないだ状態で使っていましたが、電源ボタンを押しても、反応がありません。

電源ケーブルをはずして、電源ボタンを押すとやっと反応して、ボタン脇のLEDが一瞬点灯します。しかし、起動しません。

バッテリーを外して、少し放置してから再度バッテリーを取り付け試みましたが、LEDが一瞬点灯するだけなのは変わらず。

とうとうハードウェア故障かと覚悟しましたが、突然壊れるものなのかと、あきらめきれず。 電源ケーブルをつないで、一日放置して再度試したところなぜか無事に起動され、 KB15217インストールの再起動後のプロセスが実行され無事に成功しました。

 またしばらく使用して、電源を切り、翌日電源ボタンをいれたところ同じような症状で起動しません。 しかし、今回は電源ケーブルをはずし、数回電源ボタンを押し、LEDが一瞬点灯するのを繰り返し、 また10秒ほどまって再び押したところ起動しました。

電源ボタンの接触の問題なのかと思いますが、LEDは的確に反応しますので、別の原因でしょう。 シャットダウンして、LEDランプが消灯しますが、じつは内部ではなにかがずっと動いているのでしょうか。 ということで、考えられるのはWindowsの高速スタートアップの設定です。 コントロールパネルから電源ボタンの動作設定を見てみると、チェックがはいっています。 はずしてつかっていたつもりでしたが、現状はチェックが入った状態。

とりあえず、高速スタートアップを無効にして、シャットダウンしてみました。 LEDが消灯してから、電源ボタンを押してみると、無事に起動します。 二回ほど試したところで、これを書いていますが、無事に起動しています。

 これが原因か、まだはっきりとはしませんが、覚書として残しておきます。

追加
残念ながら、夜にまた起動できない状態に。LEDランプが一瞬点灯するけど、起動しない。
電源ケーブルをはずして、しばらく経過すると起動に成功しました。
シャットダウンしたあと、電源ケーブルを差しておいたのですが、そういえばアップデートをかけて、電源ケーブルを差して翌朝起動しないということが多い。
電源ケーブルをはずして、しばらく経過したのち電源が入ります。
なにかバッテリー関係の保護が働いているのでしょうか。
今度は、シャットダウンして電源ケーブル接続せずに放置して、どうなるか様子を見ます。





2017年4月1日土曜日

pythonでimport Imageしたらエラーが出た

先日のこと、python 2.7にて、
import Image
Image.open('figure.png')
としたところ、
"cannot identify image file"
と、これまで使えていたスクリプトにもかかわらずエラーが生じました。 'figure.png'が破損でもしたのかと思ったのですが、 ビューアで開くと画像ファイルに問題はありませんでした。

そういえば、最近、reportLabをインストールしたところで、 PIL(Python Imaging Library)としてPillow 4.0.0がインストールされました。 そこで、site-packagesにあるPIL/Image.pyを覗いてみたところ、問題は、 イメージの種類に対応するPluginが正常に登録されていないことでした。

Image.open()を呼び出すと、そこでpreinit()が呼ばれ、 BmpImagePluginやPngImagePluginといった画像の種類に対応したモジュールがimportされます。 それぞれのPluginモジュールは、 Imageモジュール内のregister_open()等の関数を用いて、 Imageモジュールの*トップレベルに作成されたリストや辞書*にID等を登録していきます。

構造としては、ImageモジュールでPluginモジュールをimportして、 Pluginモジュールで、Image.register_open()を呼び出しているわけです。 さて、問題の箇所は、 Imageモジュールの*トップレベルに作成されたリストや辞書*は、 Imageモジュールから見るとglobal変数なのですが、 Pluginモジュールから見るとImageモジュールの変数であり、別物ということです。

以下、確認のためシンプルなスクリプトを4つ用意しました。

#-- module_main.py --
GLOBAL_V =[]
def register(x):
    #global GLOBAL_V
    GLOBAL_V.append(x)

def importer():
    import plugin_100
    import plugin_200

if __name__ == '__main__':
    importer()
    print GLOBAL_V
#-- plugin_100.py --
import module_main
module_main.register(100)
#-- plugin_200.py --
import module_main
module_main.register(200)
#-- using_module.py --
import module_main
module_main.importer()
print module_main.GLOBAL_V

module_mainにあるregister()を呼び出して、 module_mainにあるGLOBAL_Vに値を追加していくものです。

module_main.pyを直接実行してみます。

>python module_main.py
[]

module_main.pyを実行しても、GLOBAL_Vの値は変更されていません。 register()の中にあるglobal GLOBAL_Vがコメントアウトされてているからではありません。 コメントアウトを外して確認できるように、記述しました。

module_mainをimportして利用するusing_module.pyを実行してみます。

>python using_module.py
[100, 200]

と、設定されました。 このGLOBAL_Vは、グローバル変数を意図して作成されたものではなく、 モジュール変数を意図して作成されたもののようです。 (だからregister()内に、global GLOBAL_Vがないのですね)

ということで、Import Imageとすると、Image内で参照されているグローバル変数と、 PluginからImage.open_register()の呼び出しで変更される変数が別物(Imageのモジュール変数) になってしまうということでした。

from PIL import Image
Image.open('figure.png')
とすることで、問題は解決しました。 import Imageでエラーが出なかったため、 はまってしまったのでリマインダとして残しておきます。 (エラーがでなかったのは、以前のPILのPIL.pthが残っていたためです。) PILのimport Imageというimportの方法は、 廃止されるようですね。 問題が分かって検索してみると、 同じようなことが。

2016年12月20日火曜日

Pharoからgnuplotでsin(x)

UnifiedFFIを使ってC言語の関数を呼べたので、 Pharoからgnuplotを呼び出してみた。

呼び出す関数は、以前書いたgnuplotへのpipeを開いて コマンドを書き込む以下の関数。

/* gpdo.c */
#include <stdio.h>

FILE *gpstart(void);
void gpstop(FILE *p);
void gpdo(FILE *p, char *s);

FILE *gpstart(void) {
    /* start gnuplot process */
    return popen("gnuplot.exe --persist", "w");
}

void gpstop(FILE *p) {
    /* stop gnuplot process */
    gpdo(p, "quit");
    pclose(p);
}

void gpdo(FILE *p, char *s) {
    /* send command string to gnuplot process */
    fprintf(p, s);
    fprintf(p, "\n");
    fflush(p);
}

Windowsのgcc(MinGW)で共有ライブラリにします。

gcc -shared -o gpdo.so gpdo.c
出力されたgpdo.soをPharo.exeがあるフォルダに配置しました。

と同様に、Pharoを起動してFFILibraryを継承したライブラリファイルを示す クラスGnuplotDoLibを作成しました。そのクラスに、win32ModuleNameメソッド を追加し、共有ライブラリのファイル名を返すようにします。

win32ModuleName
    ^ 'gpdo.so'

次は、関数呼び出し用のクラスを作ります。Objectクラスを継承してGnuplotDoクラスを 作りました。メソッドは関数を呼び出すだけなので、クラス側のメソッドにします。 これで、呼び出すときにわざわざインスタンスを生成する必要がなくなります。 三つの関数についてそれぞれメソッドを追加しました。 FILE構造体へのポインタ(FILE *)については、 今回はPharo側で解釈する必要がない(メンバーにアクセスしたりしない)ため、(void *)とします。

gpOpen
    ^ self ffiCall: #(void * gpstart ()) module: GnuplotDoLib.

gpClose: aHandle
    self ffiCall: #( void gpstop (void * aHandle)) module: GnuplotDoLib

gpHndl: aHandle gpCmd: aString
    self ffiCall: #( void gpdo (void * aHandle, String aString) ) module: GnuplotDoLib

これで準備完了です。Playgroundを開いて動かしてみます。

hndl := GnuplotDo gpOpen.
GnuplotDo gpHndl: hndl gpCmd: 'set xlabel "x"'.
GnuplotDo gpHndl: hndl gpCmd: 'set ylabel "y"'.
GnuplotDo gpHndl: hndl gpCmd: 'plot sin(x)'.
GnuplotDo gpClose: hndl.

とりあえず、これでPharoからgnuplotを起動してラベル設定してsin(x)を プロットする手続きが行えました。 エラーが起こるようなコマンドをgnuplotに渡すとgnuplotがクラッシュ してしまいますが、よしとします。

2016年12月16日金曜日

PharoのUnifiedFFIを使ってみた

Pharo5.0からCの共有ライブラリを呼び出す方法が簡単になったということで、 「Pharo UnifiedFFI」で検索してみたところ、確かにわかりやすいかも。 Windowsでも呼び出せるか、試してみた。システムブラウザで見てみると、UnifiedFFIというパッケージがすでにありました。

呼び出すCの関数は、単純で簡単なdoublenumber.c。

/* doublenumber.c */
#include 
int doublenumber(int n)
{
    return n * 2;
}

WindowsのMinGW gccで共有ライブラリとしてコンパイル/リンクしmylib.soを作成しました。

gcc -shared -o mylib.so doublenumber.c

作成されたmylib.soをPharo.exeと同じフォルダに配置しました。

ここから、Pharoを起動して呼び出すためのクラスを追加します。

まず、FFILibraryを継承したクラスMyTestLibを作成して、3つのメソッドを追加します。

macModuleName
unixModudleName
win32ModuleName

それぞれのメソッドは、各プラットフォームにおけるライブラリのファイル名を返すようにします。 ですからメソッドwin32ModuleNameは、作成した共有ファイル名を返すようにしました。
win32ModuleName
    ^ 'mylib.so'

次に、Objectを継承して関数呼び出しを行うためのクラスMyTestLibCallを作成しました。 このクラスに、doubleNumber:メソッドを追加しました。 このメソッドで外部関数を呼び出します。

doubleNumber: anInteger
    ^ self ffiCall: #( int doublenumber (int anInteger)) module: MyTestLib 

ffiCall:キーワードには、Cの関数の型、関数名、引数を指示する配列を指定します。
module:キーワードには、先に作成したクラスMyTestLibを指定します。

実際に、MyTestLibCallクラスのインスタンスを生成して、メソッドを呼び出してみます。

MyTestLibCall new doubleNumber: 5

これをPrint Itすると10が表示され、無事に呼び出しされているようです。