ラベル python の投稿を表示しています。 すべての投稿を表示
ラベル python の投稿を表示しています。 すべての投稿を表示

2024年7月18日木曜日

tkinterのoptionmenuを使う

tkinterでGUIを作るとき、選択肢から選ばせるようなときにつかうウィジットで、 tkinter.OptionMenuがあります。wxPythonなら、wx.Choiceを使うところです。 普通に選択肢を用意しておいて、使うならシンプルなのですが、 選択肢をユーザーの操作に応じて変化させたいときがあります。 そのような場合のメモを残しておきます。

tkinterでは、ウィジットと結び付けて、その値を保持したり変更を監視したりできるオブジェクトがあります。文字列ならtkinter.StringVar()というものです。ここでは、それを用いてOptionMenuの値を監視します。trace()メソッドを使って、値が設定されたときにon_selected()が呼び出されるようにそておきます。

選択肢を変更するのが、OptionMenuSetChoices()という名前で作った関数です。選ばれたときの値の設定を通して、on_selected()が呼び出されます。OptionMenuの選択状態と、値を保持するオブジェクトが結びついているため、それを変更するとOptionMenuの表示も変更されます。

動作を試すのに、選択肢を100個増やすボタン、最初に移動するボタン、最後に移動するボタンを配置っしておきました。 Windows11、Python 3.8.3の環境で試しています。

import tkinter as tk

def OptionMenuSetChoices(opmenu, var, choices):
    opmenu['menu'].delete(0, "end")
    for i, choice in enumerate(choices):
        opmenu['menu'].add_command(label=choice, command=tk._setit(var, choice))

class APPFRAME(tk.Frame):
    def __init__(self, master, **kwargs):
        super(APPFRAME, self).__init__(master=master, **kwargs)

        self.var = tk.StringVar(self, "default")
        self.var.trace('w', self.on_selected)

        self.choices = [self.var.get()]
        self.opmenu = tk.OptionMenu(self, self.var, *self.choices)
        self.opmenu.pack()
        OptionMenuSetChoices(self.opmenu, self.var, self.choices)

        btn = tk.Button(self, text='Set Choices', command=self.on_set_choices)
        btn.pack()
        btn_first = tk.Button(self, text='Go First', command=self.on_go_first)
        btn_first.pack()
        btn_last = tk.Button(self, text='Go Last', command=self.on_go_last)
        btn_last.pack()

    def on_selected(self, *args):
        print(self.var.get())

    def on_set_choices(self, *args):
        self.choices = ["choice: %02d" % (i+1) for i in range(100)] 
        OptionMenuSetChoices(self.opmenu, self.var, self.choices)
        self.on_go_first()

    def on_go_first(self):
        self.var.set(self.choices[0])
    
    def on_go_last(self):
        self.var.set(self.choices[-1])

def main():
    root = tk.Tk()
    app = APPFRAME(root)
    app.pack()
    root.mainloop()

if __name__ == '__main__':
    main()

2024年7月15日月曜日

Debian12でplaysound(python)を使う

Debian12をDell mini9に入れ、python3.11が使えるので遊んでいます。 Pythonは、とてもよいプログラミング言語環境だと思いますが、 Linuxの場合は、外部モジュールを入れるのにaptを使ったりする必要があるようで、 いろいろ混乱します。

今回使いたいモジュールは、playsoundです。効果音などmp3を再生したいことがときどきあります。MacBookでlubuntu 20.04も使っていて、そちらで書いたコードをmini9に持ってきて動かそうとしたら、playsoundを使えるようにするまでに手間取りましたのでメモしておきます。

Linuxでpythonのモジュールは、aptを使うなりして、例えば、xxxモジュールなら、

sudo apt install python3-xxx
のようにしますが、playsoundは、pipからインストールするようです。

lubuntuのほうでは、pip install playsoundでインストールできました。ただし、このままだとエラーで再生できませんでした。Gstreamerのpythonバインディングが必要とのことで、以下のようにインストールすることで使えるようになりました。

sudo apt install python3-gst-1.0

mini9でのDebian12では、以下のようにして使いました。 まずは、同じようにgstreamerバインディングをインストールします。 さらにpygobjectをインストールするために、 そのビルドに必要なライブラリをインストールします。

sudo apt install libgirepository1.0-dev
sudo apt install libcairo2-dev

pipを使うために、venvで仮想環境を作り、その中でpipを使ってplaysound、pygobjectをインストールしました。pipとvenvを使うのに、そちらもインストールが必要でした。

sudo apt install python3-pip
sudo apt install python3-venv
mkdir test
python3 -m venv myenv
source myenv/bin/activate
pip install playsound
pip install pygobject
これでpython3を使って、playsoundモジュールを利用することができました。

実際動かしたいコードは、tkinterも使っていましたので、

sudo apt install python3-tk
も行ってtkinterを使えるようにしました。

2024年6月23日日曜日

lubuntu 24.04 LTSでwxPython4.2.1

古いPCにLinuxを入れて使えるようにしています。 Lubuntu 24.04 LTSを古いデスクトップPCに入れて使っています。 リモートデスクトップとpythonくらいしか利用しませんので 古いパソコンでも十分な用途です。 pythonは、python3が利用でます。 さて、GUIにwxPythonをよく利用していたのですが、 そのようなスクリプトを動かすときにwxPythonをインストールする必要があります。 Windowsでは、pipを用いてインストールしていますが、 lubuntuでは、aptを使ってインストールするようです。
apt install python3-wxtools
でインストールできました。

さて、これで使えるなら問題よいのですが、しばしば 仮想環境を利用することが増えています。python3では、 すぐにvenvが利用できるようになっています。 たとえば、

mkdir myproject
cd myproject
python3 -m venv myenv
として、myprojectフォルダの中に、 そのプロジェクト用のmyenvという仮想環境を作って利用したりします。 その仮想環境を利用するには、
cd myproject
source myenv/bin/activate
として、仮想環境のフォルダ内にあるactivateスクリプトを用いて利用できるようにします。 アクティベートするといいます。 ここで pip install numpy とすると仮想環境にnumpyがインストールされるわけです。 ある環境がアクティベートされた状態で起動されるpythonでは、 その環境にインストールされたモジュールが利用できるというわけです。 環境を戻すには、deactivateを実行します。

さて、この仮想環境へはpipを用いてモジュールをインストールします。 pip install wxPythonとしてwxPythonををpipでインストールしようとすると、 ビルドエラーが出て停止してしまいました。 こちら https://wxpython.org/blog/2017-08-17-builds-for-linux-with-pip/index.html のサイトを参考にしてインストールをしました。

"What You Need"の箇所にビルドに必要なライブラリが記載されています。 地道にひとつづつインストールしていきます。 私の環境では、

sudo apt install python-dev
sudo apt install libgtk-3-dev
sudo apt install libgstreamer1.0-dev
sudo apt install libgstreamer-plugins-base1.0-dev
sudo apt install libglu1-mesa-dev mesa-common-dev freeglut3-dev
sudo apt list |grep libwebkit2gtk
sudo apt install libwebkit2gtk-4.1-dev
sudo apt install libjpeg-dev
sudo apt install libpng-dev
sudo apt install libtiff-dev
sudo apt install libsdl-dev
sudo apt install libnotify-dev
sudo apt install libsm-dev
としていきました。すでにインストールされているものもいくつかありましたが、
順番に検索エンジンを利用しながら確認していきました。
途中どれをインストールするかlistを見て確認したりもしています。

あとは、"Build Steps"にあることを順番にしていくだけです。もちろん、 ファイル名はダウンロードされたものに合わせて修正します。 書いてある通りにすると無事にwheelファイルがビルドされ、 wxPythonを利用することができるようになりました。

このようにしてpython3.12でwxPython4.2.1が動きました。 ただし、現在ダウンロードされたファイルをコンパイルしたものは、 pythonを起動してimport wxのあとすぐにquit()してもセグメンテーションフォールトを起こします。 "wxpython 4.2.1 segmentation fault"で検索してみるとすでに報告はあるようです。 とりあえずは利用できるので、修正を待ちます。 ちなみに、仮想環境ではなくaptでインストールした環境ではそのようなエラーは起こっていません。

2020年8月22日土曜日

NimでZipファイルにしたテキストを読む

nimというプログラミング言語を試してみた。 pythonのように書けるけど、静的な型付けのコンパイラなので、 実行速度のパフォーマンスがよいということである。

大きなカンマ区切りの値(CSV)やタブ区切りの値(TSV)といった テキストデータをzipで圧縮するとだいぶ小さくなるので、 そのようにして保存しておくことも多い。 そのようなテキストファイルを読み込みたいときに、 zipファイルを扱うモジュールが利用できると便利である。

そこで、datafile.txtというテキストファイルをzip圧縮した、 datafile.zipを読み込んで、各行をプリントするだけというスクリプトを書いてみた。

Python(v3.83 windows 64bit)では、以下のようなスクリプトになる。

import io
import zipfile

def readzip():
    filename = "datafile.zip"
    try:
        z = zipfile.ZipFile(filename, 'r')
    except:
        print("Opening zip failed")
        exit(-1)
    try:
        for fname in z.namelist():
            print(fname)
            fp = io.TextIOWrapper(z.open(fname, 'r'))
            line = fp.readline().strip()
            while line:
                print(line)
                line = fp.readline().strip()
            fp.close()
    finally:
        z.close()

if __name__ == '__main__':
    readzip()       
nimでは、zipパッケージにあるzipfilesモジュールを利用する。
import streams
import zip\zipfiles

proc readzip() =
    var filename = "datafile.zip"
    var z: ZipArchive
    var line = ""
    if not z.open(filename):
        echo "Opening zip failed"
        quit(1)
    try:
        for f in walkFiles(z):
            echo f
            var fs = z.getStream(f)
            while fs.readLine(line):
                echo line
            fs.close()
    finally:
        z.close()

when isMainModule:
    readzip()
コンパイルは、readzip.nimというファイルに書いたとすると、
nim c readzip.nim
でできるが、その前にzipパッケージをインストールする必要がある。 Gitがインストールされていれば、
nimble install zip
でインストールできる。 さらに、Windowsでコンパイルすると、gccでコンパイルされるときに エラーで止まってしまった。 こちらにあるように、修正するとコンパイルできた。

ついでなので、Julia(v1.5 windows 64-bit)でも書いてみた。 juliaでは、ZipFile.jlが利用できる。

using Pkg
Pkg.add("ZipFile")
としてインストールできた。
using ZipFile

function readzip()
    filename = "datafile.zip"
    local z

    try
        z = ZipFile.Reader(filename)
    catch
        println("Opening zip failed")
        exit(-1)
    end

    try
        for f in z.files
            println(f.name)

            while !eof(f)
                line = readline(f)
                println(line)
            end
        end
    finally
        close(z)
    end
end

readzip()       
Juliaでは、tryブロックもスコープを作るようで、tryブロック内で作った変数zを、 そのtryブロックから出た後でも利用するために、local z宣言をしている。

どれも似たような感じに書けたが、Juliaは、関数が充実しているからかシンプルに感じる。 whileのループは、eachline(f)を用いたforループで書くと、すっきりする。

2020年7月23日木曜日

Ptyhon3で以前のスクリプトを動かす(epsClip)

ドラッグアンドドロップを使った、 こちらで作成したepsファイルの バウンディングボックスを調整するスクリプトをPython3.8で動かしてみた。 新しい環境にGhostScript9.52 をインストールしたが、GhostView は使えなかったのでこちらのスクリプトを 利用する必要があるからである。 Python3.8になったためというより、wxPython や PIL が変更になったためにそのままでは動かなくなったところがある。 環境はWindows10 64bit、python3.8.3、wxPython4.1.0、PIL(pillow)7.1.2である。

まずは、
import Image
でエラーがでる。
これは、このインポートの仕方は廃止されたためで、以前に調べていた。
from PIL import Image
に修正する。

wxPythonでは、wx.EmptyBitmap、wx.EmptyImage、wx.BitmapFromImageが廃止されたようだ。 wx.EmptyBitmap()およびBitmapFromImage()は、どちらもBitmap()に書き換えればよかった。 wx.EmptyImage()は、wx.Image()で書き換える。

wx.Imageの持っていたtostring()メソッドは廃止されたので、tobytes()メソッドに書き換える。

FileDropTargetのOnDropFiles()ハンドラがbool値を返す必要があるようになったので、return True を加える。

文字列のコード変換などで、decode()を用いていたが、そのような必要はなくなった。 文字列は、utf-8となったのでバイト列にするencode()しかない。 また、バイト列には文字列にするためのdecode()メソッドがある。

もともとGUIのみのスクリプトでprintしていないので、print()関数にする修正は不要であった。 これらの修正をすると、問題なくpython3.8で動作した。

2020年7月22日水曜日

Python3.8でIDLEを使ってみた

Python3.8を使いだして、インストールされたIDLEを使ってみた。 Python2.7のときも、ときどき利用していたPythonコードをちょっと書いてみるのに便利な環境である。 JupyterやSpiderよりも手軽に軽快に利用できるので、使い始めるのはよいかと思う。

驚いたのはPython3.8でついてくるIDLEでは行番号の表示ができるようになっていたことである。 メニューからnew fileを選んでエディタを開くと、メニューのOptionsにShow Line Numbersがあり、 それを選ぶと行番号が表示される。 行番号表示をデフォルトにしたい場合は、 メニューのOption/Configure IDLEを選んで、GeneralタブにあるShow line numbers in new windows のチェックを入れればよい。

複数行をまとめてインデントをずらしたいときは、 行を選んでCtrl+[キー(インデントを減らす)とCtrl+]キー(インデントを増やす)でできる。

検索は、Ctrl+Fキーで検索用の窓が開くが、単語を選択したあとにCtrl+F3キーを押すとその単語を探してくれる。 でも検索を頻繁にするなら、慣れかもしれないがVimがやりやすい。

範囲をコメントにするにはAlt+3キー、コメントを解除するにはAlt+4キーである。

シェルのほうでも利用できるが、 TABキーを押すと登録されている単語から候補が表示され、Alt+/キーを押すとテキスト内の単語から候補が表示される。 また、Ctrl+0キーで対応するカッコを確認できる。

シェルでは、Alt+pキーとAlt+nキーで履歴を利用できる。画面でカーソルを移動させてEnterキーを押すとそこにあるテキストが入力行へと転写される。ブロックを用いたコードを実行したときなどに、ブロックごと転写されるため、再編集しやすい。ちょっと動作を確認したいときなどに便利である。タートルグラフィクスで遊ぶのにも便利だろう。

この程度操作ができればちょっとしたスクリプトを書くのに便利である。 なんといってもPythonをインストールするとすぐに利用できるのがよい。 Vimがインストールされていない環境でならIDLEをエディタとして使うのもよさそうである。

python3 wxPythonでドラッグアンドドロップ

これまでPython2.7でwxPythonを用いて、ファイルをドラッグアンドドロップで開くスクリプトを 書いて用いていた。Python3.8を使い始めて、改めてドラッグアンドドロップの部分を抜き出して、 動作を確認してみた。 環境は、Windows10 64bit, Python3.8.3, wxPython4.1.0。

tkinterにくらべて、ウィンドウ作成の部分に手間をかけているが、やっていることは以下の通り。

まず、wx.FileDropTargetを継承したクラスを作成して、 ファイルがドロップされた時に実行されるOnDropFiles()を 自分の望むように書き換える。

さらに、wx.PanelのSetDropTarget()メソッドを用いて、 作成したFileDropTargetを継承したクラスのインスタンスを設定するだけである。 実行すると、ウィンドウが開き、その中に配置されたパネルにファイルをドロップすると、 そのパスがコンソールに表示される。

import wx

class MyFileDropTarget(wx.FileDropTarget):
    def __init__(self):
        super(MyFileDropTarget, self).__init__()

    def OnDropFiles(self, x, y, filenames):
        for n in filenames:
            print(x, y, n)
        return True

class DropPanel(wx.Panel):
    def __init__(self, parent, *args, **kwargs):
        super(DropPanel, self).__init__(parent, *args, **kwargs)
        self.droptarget = MyFileDropTarget()
        self.SetDropTarget(self.droptarget)
        
class MyFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(MyFrame, self).__init__(*args, **kwargs)
        
        self.panel = DropPanel(self, size=(300, 300))       
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.panel,  1, wx.EXPAND)
        self.SetSizer(sizer)
        self.Fit()

class MyApp(wx.App):
    def __init__(self):
        wx.App.__init__(self, False)

    def OnInit(self):
        self.frame = MyFrame(None, title=AppName)
        #self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

if __name__ == '__main__':
    AppName = "DnD Test"
    app = MyApp()
    app.MainLoop()  

2020年7月21日火曜日

Python3 tkinterでドラッグアンドドロップ

Pythonのtkinterでファイルをドロップしてそれを開くようなGUIをもったスクリプトを作るのは どうするのか調べてみた。 一つは、ctypes モジュールを用いて、WindowsのAPIを呼び出す方法がある。 もう一つは、TkDNDというTkの拡張ライブラリとそのPythonラッパーであるTkinterDnD2を用いる方法である。 ここでは、後者の方法を試してみた。 環境はWindows10、Python3.8.3、64bit環境である。

まずはこちらにいく。 TkinterDnD2のダウンロードができる。また、TkDND へのダウンロードリンクもある。 行うことは、Tkの拡張ライブラリであるTkDND2.8と、そのPythonラッパーであるTkinterDnD2をインストールすることである。

TkDNDのページへ行く。 64bit環境なら、 Windows Binaries/TkDND2.8の中にある tkdnd2.8-win32-x86_64.tar.gz をダウンロードする。

Python環境がインストールされているフォルダの中にtclというフォルダがある。 それぞれの環境ごとに場所が変わるので、 sys モジュールをインポートして、どこにpythonがインストールされているかを 調べてみるとよいだろう。 そのtklフォルダ中に、tkdnd2.8フォルダをそのまま配置する。

次に、こちらから TkinterDnD2-0.3.zipファイルをダウンロードする。 展開すると、TkinterDnD2フォルダといくつかのdemoファイルおよび 説明のhtmlファイルが得られる。 Pythonがインストールされているフォルダにある、 Lib\site-packagesの中にTkinterDnD2フォルダをそのまま配置すると完了である。

python からdemoを実行してみると動作を確認できる。 簡単なスクリプトは以下のものとなる。 エクスプローラからファイルを投げ込むと、そのファイルパスが表示される。 複数ファイルを投げ入れると、各ファイルパスがスペースで区切られたものとなる。

import sys
import os
import TkinterDnD2 as tkdnd
if sys.version > "3.0":
    import tkinter as tk
else:
    import Tkinter as tk

def drop_enter(event):
    event.widget.focus_force()
    return event.action

def drop_leave(event):
    event.widget._root().focus_force()
    return event.action

def drop_position(event):
    #print(event.x_root, event.y_root)
    return event.action

def drop(event):
    if event.data:
        print(event.data)
    return event.action

app = tkdnd.TkinterDnD.Tk()

app.drop_target_register(tkdnd.DND_FILES)
app.dnd_bind('<<DropEnter>>', drop_enter)
app.dnd_bind('<<DropLeave>>', drop_leave)
app.dnd_bind('<<DropPosition>>', drop_position)
app.dnd_bind('<<Drop>>', drop)

app.mainloop()

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月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年11月18日金曜日

一文字飛ばしにした文字列へ変換する

ある文字列を一文字飛ばしにした文字列へ変換する関数をRubyで書くという話を見かけました。 "abcdefg"を入力したら"aceg"が帰ってくる関数です。 Pythonを使っていると、関数を書くまでもなくs[::2]としたら望みの文字列が得られます。せっかくだからRubyで書いてみました。
pythonでは、
def one_skipped(s):
    return s[::2]

print one_skipped('abcdefg') #=> 'aceg'

と関数にするまでもありません。 Juliaでも、
function one_skipped(s)
    s[1:2:length(s)]
end

print(one_skipped("abcdefg"))

のように文字列から文字列を生成できます。

Rubyでも文字列のメソッドに便利なものがあるのかと見てみましたが、 slice()では、範囲のみの指定のようです。 インデックスを指定して一文字取り出すことはできるようですので、 一つとびのインデックスさえ生成できれば、それをもとに文字列を組み立てられそうです。
def one_skipped(s)
    return (0..s.length/2).map {|i| i*2}.collect {|i| s[i]}.join()
end

Rubyはreturnを省略できるということ。i*2として一つ飛びの数に直すより、 i%2==0の判定を使って取り出したいということで、selectメソッドを使ってみます。
def one_skipped(s)
    (0..s.length).select {|i| i%2==0}.collect {|i| s[i]}.join()
end
ずらずらと記述が並びますね。関数に分けてみます。

def range(start, stop, step)
    (start .. stop).select {|i| (i-start)%step==0}
end

def chars_at_indices(s, indices)
    indices.collect {|i| s[i]}
end

def one_skipped(s)
    chars_at_indices(s, range(0, s.length, 2)).join()
end

puts 'abcdefg'
こんな感じでしょうか。関数に分けるほどでもないでしょうか。 せっかく関数に分けたので、SBCLでも書いてみます。
(defun range (start stop step)
  (loop for i from start below stop by step collect i))

(defun chars-at-indices (s indices)
  (loop for i in indices collect (elt s i)))

(defun one-skipped (s)
  (concatenate 'string (chars-at-indices s (range 0 (length s) 2))))

普段使わない言語を、ちょっと調べつつ書いてみる題材としてちょうどよいですね。

Rubyは、Windows上で、
ruby 2.1.2p95 (2014-05-08 revision 45877)
irb 0.9.6(09/06/30)
を用いました。

2016年11月11日金曜日

Gmailの下書きメモと同期するスクリプトを作ってみた

ポメラのdm200いいですね。 Gmailの下書きを利用して同期する機能があるということ。

Windowsでもそんなことやってみようと、pythonで作ってどんな感じかと試し中。 IMAPでgmailに接続して、下書きフォルダにメッセージを送るとともに、「メモ」ラベルをつけてみました。 こうすると、たんに「メモ」にメッセージを送るのとは違い、ブラウザで開いたGmailからも編集可能です。


IMAPでメッセージを削除するときは、「メモ」ラベルを消した後でないと、\Deleteフラグをstoreしても削除されませんでした。 直接「メモ」に送ったメッセージは、\Deleteフラグをstoreするだけで削除されました。 ただ、直接「メモ」に送ると、ブラウザで開いたGmailからは編集不可能でした。 「[Gmail]/下書き」にいれないと再編集できないようですね。

Windowsならファイルの同期には、Dropboxを使うので、あえてGmailに同期する必要はなさそうです。
dm200を手に入れたときには、ターゲットを変更して、便利に使えるのかもしれません。

2016年10月29日土曜日

pythonでSTL形式のデータを作ってみた

gnuplotでは、以下のようなスクリプトで球面を描画したりします。
set parametric # 媒介変数モード
set urange[0:2*pi] # uの範囲を設定
set vrange[0:2*pi] # vの範囲を設定
set ticslevel 0
set hidden3d
set isosample 50
set view equal xyz
splot cos(u/2.0)*cos(v),sin(u/2.0)*cos(v),sin(v) w l
このようなものを3Dモデルとして出力したいということで、 pythonで(u, v)の関数をSTL形式にして出力するスクリプトを書いてみました。 ほかの言語で書き換えて遊ぶのにも、ちょうどよい内容と長さかと思います。
import math

def Sphere(u, v):
    x = math.cos(u/2.0)*math.cos(v)
    y = math.sin(u/2.0)*math.cos(v)
    z = math.sin(v)
    return x, y, z

def VectorSub(v1, v2):
    return [e1-e2 for e1, e2 in zip(v1, v2)] 

def VectorDiv(v, a):
    return [e/a for e in v] 

def VectorCross(v1, v2):
    x1, y1, z1 = v1
    x2, y2, z2 = v2
    return [y1*z2-z1*y2, z1*x2-x1*z2, x1*y2-y1*x2]

def VectorAbs(v):
    return math.sqrt(sum([e**2 for e in v]))

def NormalVector(p1, p2, p3):
    v1 = VectorSub(p2, p1)
    v2 = VectorSub(p3, p2)
    vc = VectorCross(v1, v2)
    a = VectorAbs(vc)
    v = VectorDiv(vc, a) if a!=0.0 else False
    return v

def STLfacet(apolygon, inverse=False):
    if inverse:
        points = apolygon[::-1]
    else:
        points = apolygon[:]

    nv = NormalVector(*points)

    if not nv:
        return ""

    l = "facet normal %g %g %g\n" % tuple(nv)
    l = l + "outer loop\n"
    for p in points:
        l = l + "vertex %g %g %g\n" % tuple(p)
    l = l + "endloop\n"
    l = l + "endfacet\n"
    return l

def PolygonsToSTL(name, polygons, inverse=False):
    l = "solid %s\n" % name
    for p in polygons:
        l = l + STLfacet(p, inverse)
    l = l+"endsolid\n"
    return l

def Polygons_of_Function_UV(func):
    nu = 90 
    nv = 90 
    du = 2.0 * math.pi / nu
    dv = 2.0 * math.pi / nv

    u = [du * i for i in range(nu+1)]
    v = [dv * i for i in range(nv+1)]

    polygons = []
    for i in range(nu):
        for j in range(nv):
            p1 = func(u[i], v[j])
            p2 = func(u[i+1], v[j])
            p3 = func(u[i+1], v[j+1])
            p4 = func(u[i], v[j+1])
            polygons.append((p1, p2, p4))
            polygons.append((p4, p2, p3))
    return polygons

def OutputSTL(name, polygons):
    with open('%s.stl' % name, "w") as fp:
        fp.write(PolygonsToSTL(name, polygons, False))

if __name__ == '__main__':
    OutputSTL("Sphere", Polygons_of_Function_UV(Sphere))


出力されたSTLデータを見るには、GLC Playerが便利ですね。また、Windows10には、3D Builderがついているので、そのままでSTLデータを見ることができます。GLC Playerでスナップショットを取ったものが下の図です。

関数を次のものにしてみます。

def Dounut(u, v):
    x = math.cos(u)*(1+0.5*math.cos(v))
    y = math.sin(u)*(1+0.5*math.cos(v))
    z = 0.5*math.sin(v)
    return x, y, z

ドーナツの形になりました。

def Helical(u, v):
    n = 10.0
    a = 0.6
    b = 0.3
    rm = (lambda v: a*b/math.sqrt(b*b*math.cos(v)**2 + a*a*math.sin(v)**2))
    r = (lambda u, v: 1 + rm(v)*math.cos(v+n*u/2.0))
    x = r(u, v) * math.cos(u)
    y = r(u, v) * math.sin(u)
    z = rm(v) * math.sin(v + n*u/2.0)
    return x, y, z

楕円をぐるぐる回転させていくと、ねじれた形状になりました。

2016年10月20日木曜日

Windowsでファイルをドロップしてpythonスクリプトを起動する

ファイルをドラッグ&ドロップでpythonスクリプトを起動し、処理たいこともあるようで、Googleで検索してみると、二つ方法があるようですね。

一つは、レジストリを編集してPythonスクリプトにドロップハンドルを設定する方法があるようです。

もう一つは、バッチファイルを使う方法です。レジストリをいじったりしないので、こちらの方が好みですし、おすすめです。

testDragAndDrop.py (pythonスクリプトファイル)
import sys
for arg in sys.argv:
    print arg
raw_input()

testDragAndDrop.bat (バッチファイル)
python testDragAndDrop.py %*

の二つのファイルを用意して、testDragAndDrop.batにファイルをドロップするとtestDragAndDrop.pyに引数としてドロップされたファイルのパスが渡されます。非常に簡単ですね。バッチファイルのショートカットを作成して、そちらにドロップすることも可能です。

2016年7月31日日曜日

プチコン3号でたらい回し関数

プチコン3号SmileBasicを試してみたく、new3DSLLを手に入れた。たらい回し関数(竹内関数)を入力して実行してみた。

 DEF TARAI(X,Y,Z)
  IF X<=Y THEN
    RETURN Y
  ELSE
    RETURN TARAI(TARAI(X-1, Y, Z), TARAI(Y-1, Z, X), TARAI(Z-1, X, Y))
  ENDIF
END

PRINT TIME$
PRINT TARAI(12,6,0)
PRINT TIME$

BASICとはいっても、行番号いらないし、ローカル変数あるし、再帰呼び出しも可能なんですね。
引数(12, 6, 0)の場合、28秒で返ってきました。

さて、比較するものがないと困るので、メインで使っているToshiba dynabook SS RX1(Windows Vista 32bit)のpython 2.7.6でたらい回し関数を実行してみました。6秒ほどで返ってきました。

#import time
def tarai(x, y, z):
    if (x <= y):
        return y
    else:
        return tarai(
                    tarai(x-1, y, z),
                    tarai(y-1, z, x),
                    tarai(z-1, x, y))
start = time.time()
print tarai(12, 6, 0)
stop = time.time()
print stop - start

これを、zenfone2(ZE551ML)のqpythonで試すと12秒でした。

2016年3月23日水曜日

Kivy LauncherでAnimated Gif Viewer

Kivy Launcherのアップデートがあったので、せっかくなのでAnimated Gif Viewerを作ってみた。Kivyで作成して、QPythonでの動作を確認したけど、Kivy Launcherでは起動しない。GIFイメージの読み込みにPILを使っているけど、出力されたlogを見るとどうやら、GIFイメージを読むときに AttributeError: tobytes が起こっている。Pillowではtostring()が廃止予定になってtobytes()が使われるらしいとのこと。どうやらKivy Launcherが呼び出しているPILのバージョンが古いためにtobytes()メソッドがないらしい。そういうわけで、以下のようにスクリプトのあたまでtostring()を呼び出すようにtobytes()メソッドを追加してみた。

from PIL import Image as PILImage

def tobytes(pilimg, encoder_name="raw", *args):
    return pilimg.tostring(encoder_name=encoder_name)

PILImage.Image.tobytes = tobytes

無事に起動することができた。こういうところ、Pythonは便利ですね。


Next, Back, Pause, Rotate, Resume, Open, Quitとボタンをつけてみました。
GUIをwxPythonで作り直して、Windows用にもしてみた。



2016年1月15日金曜日

Kivy LauncherでImageViewerの起動に失敗

Zenfone2でKivy Launcherから自作のImageViewerを起動していたけど、新年になって起動しなくなった。どうやらバージョンアップして、動作が怪しくなったみたい。スクリプトを実行してもログも出力せずに、終了してしまった。Google Groupの方でも話題になっている。しかたがないので、プロジェクトのフォルダーをそのままQPythonに移動した。以前試したときは動かなかったけど、今回は問題なく起動した。こちらもバージョンが上がって、Kivyのバージョンが1.9.0になったようだ。

2016/3/21追記
Kivy Launcherのアップデートがあり、自作のImageViewerもKivy Launcherから無事に起動するようになった。


2015年7月8日水曜日

MinGW gcc とctypesモジュールを用いてpythonからc関数を呼び出してみる 2

前に構造体を渡すような関数までやったけど、構造体の配列を渡すようなこともよくありそう。簡単な例で試してみたのでメモしておきます。
呼び出されるc関数は、以下の通り。

#include <stdio.h>

typedef struct {
 char name[20];
 int age;
} NAMEandAGE;

int sum_ages(int sz, NAMEandAGE *na)
{
 int i;
 int sum = 0;
 
 for (i=0; i<sz; i++){
  printf("%s %d\n", na[i].name, na[i].age);
  sum = sum + na[i].age;
 }
 return sum;
}
文字列と整数のフィールドをもつ、構造体NAMEandAGEの配列を渡して、 要素の値を表示し、ageの和を返す関数です。
配列は、pythonスクリプトで作成するため、その長さが分からないので、 整数szで長さを渡しています。

ソースファイルをstructures.cというファイルに保存して、

gcc -Wall -c structures.c

でコンパイルします。structures.oというオブジェクトファイルができますので、

gcc -shared -o structures.so structures.o

として、structures.soという、共有ライブラリファイルを作成します。
これを呼び出す、pythonスクリプトは、以下の通り。

import ctypes

class NAMEandAGE(ctypes.Structure):
    _fields_ = [('name', ctypes.c_char*20),
                ('age', ctypes.c_int)]

if __name__ == '__main__':
    mydll = ctypes.CDLL("structures.so")

    data = [('name1', 1), ('name2', 2)] 
    nameandages = (NAMEandAGE * len(data))()
    for n, d in zip(nameandages, data):
        n.name = d[0]
        n.age = d[1]

    result = mydll.sum_ages(ctypes.c_int(len(data)), ctypes.pointer(nameandages))
    print result

配列は、(構造体のクラス * 要素数)で構造体配列のクラスを作って、それを用いてインスタンスを生成するようです。 その後、リストdataの値で、各要素のフィールドを設定しました。
関数を呼び出すときは、ctypes.pointer()を使って、構造体配列のポインタを渡せばよいようです。