Pythonゼミ 第10回 MatplotlibにおいてGUI制作の基礎


Last updated: 2023/03/08

GUI (Graphical User Interface): マウスやキーボードなどの入力で操作や指示ができるプログラムです。

pythonにはGUIの製作にいろんなモジュールがあります。うまく利用すれば非常に複雑なソフトが製作できます。 実は、普段私たち使っているソフトの中に、pythonで作られたのはたくさんあります(Dropboxとか)。

データ解析の場合、非常に簡単なGUIでも仕事をかなり効率化できます。Matplotlibを使って、簡単なGUIの製作を紹介します。


例1:画像選定プログラム


たくさんのデータからたくさんの画像を作って、一つ一つ見て分類するのはよくある作業です。 日常生活でもこういう場面があります。例えば、たくさんの写真を撮ったら、貴重な写真、普通の写真、要らない写真等に分類することがあります。 一つ一つ写真を見て、ファイルを各種類のフォルダーに移動するのが結構手間がかかります。どうやってこの作業の効率を少しでも上げますか?


例えば、もしこのようなプログラムがあれば、作業はかなり便利になります。 このプログラムはまず1枚目の写真を表示して、作業者は表示された写真を見て種類を判断して、以下のようにキーボードの操作を行います:
貴重な写真 ⇉ 数字キー1を押す
普通の写真 ⇉ 数字キー2を押す
要らない写真 ⇉ 数字キー3を押す


キーを押すと、プログラムは以下の操作をやってくれる:
キー1 ⇉ 写真をフォルダー1に移動する
キー2 ⇉ 写真をフォルダー2に移動する
キー3 ⇉ 写真を削除する


以上の操作が終わったら、自動的に次の一枚の写真を表示してくれます。作業者はまた判断して、キーボードの操作をやります。 このように繰り返して、全部の写真を分類する。これで作業はかなり速くなります。


プログラムは以下となります。

import os
import shutil #ファイルの移動、コピー等の処理のモジュール
import matplotlib.pyplot as plt
import matplotlib.image as mpimg #matplotlibの画像処理モジュール
import warnings
warnings.filterwarnings('ignore') #warningメッセージを表示させない

#画像ファイル名のリスト化
figDir = 'F:/Course/python/python10-pic/'
fileMat = os.listdir(figDir)
picMat = []
for fileName in fileMat:
    #フォルダーの中に画像以外のファイルもあるかもしれないので、判断したほうが安全
    if len(fileName)>4 and fileName[-4:] in ['.jpg', '.png', 'jpeg']:
        picMat.append(fileName)
picMat.sort()

#フォルダー1、2を作る(どんな名前でもいいですが)
moveDir1 = figDir+'1/'
if os.path.exists(moveDir1)==False:
    os.mkdir(moveDir1)
moveDir2 = figDir+'2/'
if os.path.exists(moveDir2)==False:
    os.mkdir(moveDir2)

curIndex = 0 #現在処理の画像は何番目かを示す変数

f = plt.figure(figsize=(6,6))
ax = f.add_axes([0., 0., 1, 1])
ax.set_xticks([])
ax.set_yticks([])

def key_press_fun(event):
    global curIndex #global変数、後で補足説明する
    strKey = event.key
    fileName = picMat[curIndex]
    if strKey=='1': #押したキーは1の場合、ファイルをフォルダー1に移動
        shutil.move(figDir+fileName, moveDir1+fileName)
    elif strKey=='2':
        shutil.move(figDir+fileName, moveDir2+fileName)
    elif strKey=='3':
        os.remove(figDir+fileName)
    
    #1,2,3のどれかを押したら、次のファイルを表示
    if strKey in ['1', '2', '3']:
        curIndex += 1
        if curIndex<len(picMat):
            img = mpimg.imread(figDir+picMat[curIndex])
            ax.clear()
            ax.set_xticks([])
            ax.set_yticks([])
            ax.imshow(img)
            f.canvas.draw()
        else:
            print('This is the last file.')

#f(行27に定義された)に対して、キーボードの操作が行われれば、関数key_press_fun(行32)を実行する
f.canvas.mpl_connect('key_press_event', key_press_fun)

#このプログラムが実行されたら、まず1枚目の画像を表示する
img = mpimg.imread(figDir+picMat[curIndex])
ax.imshow(img)
plt.show()

以下の画像を使ってプログラムを実行してみてください。
https://tingwu.info/pylab/data/python10-pic.zip

ダウンロード、解凍したら、中に20枚の写真があります。プログラムを利用して、雷の写真をフォルダー1に、 山の写真をフォルダー2に、それ以外の写真を削除するという作業をやってみてください。

(実行する前にCtrl+F6 -> Execute in an external system terminalに設定する必要がある)

*Macでは上のように設定しても操作の画面が出ない可能性があります。 まず、「mac matplotlib 表示されない」を検索していろいろ試してみて、うまくできなかったら、また相談してください。


補足 - global変数

以下の二例を比較してください

val = 0

def testFun(x):
    val = x+10
    
testFun(5)

print(val)
>>
0

val = 0

def testFun(x):
    global val
    val = x+10
    
testFun(5)

print(val)
>>
15

例2:FALMA波形表示プログラムbasicPlot.py

ソースコードをダウンロード:https://tingwu.info/pylab/data/python10-basicPlot


FALMAの波形データの解析は私たちの多くの研究の基礎です。 以前紹介した波形をプロットするプログラムは波形を表示することができますが、一つ一つの波形をプロットするのは手間がかかります。


たくさんの波形がある場合、もしキーボードやマウスの操作で、次や前の一個のデータの波形を表示する、 波形の任意の部分を一定の時間スケールまで拡大、縮小する、等の操作ができれば、解析は便利になります。 basicPlot.pyはこのようなプログラムです。


使用方法:

行11:自分のパソコンにFALMAの波形データ(bz2ファイル)の保存先です (この保存先に波形データ以外のファイルがあれば、エラーになることがあります)
例えば、第8回で使ったデータの保存先を書く。

dataDir = 'F:/Course/python/python08-data/'

実行したら、コマンドプロンプトと波形を表示するウインドウが出る。最初のファイルAKKL1548422986.bz2が表示されます。


dボタンを押すと、次のファイルを表示される。(最後のファイルが表示されるとき、dボタンを押すとエラーになります)
aボタンを押すと、前のファイルを表示される。(最初のファイルが表示されるとき、aボタンを押すとエラーになります)
*エラーを防ぐこともできますが、今のプログラムには書いてない。


最初に表示される波形は1秒間の波形です。
マウスを波形の上において、1ボタンを押すと、マウスの位置を中心に100msの波形が表示される。
2ボタンを押すと、マウスの位置を中心に10msの波形が表示される。
3ボタンを押すと、マウスの位置を中心に1msの波形が表示される。
4ボタンを押すと、最初の1秒の波形に戻る。
これを使って波形の詳細の確認には便利です。


マウスを波形の上にクリックすると、クリックするところのX軸の値(時間)がコマンドプロンプトに表示される。


たくさんのファイルがある場合、一番目のファイルから見たくなくて、最初に表示するファイルを指定したいとき、行12にファイル名を書く。

dataFile = ''

例えば、最初はKZKL1548422986.bz2を表示したいとき

dataFile = 'KZKL1548422986.bz2'

プログラムを実行すると、このファイルが表示される。(書いたファイル名は行11のフォルダーに存在したい場合は最初のファイルが表示される)


コードの説明


例1と同じように、キーボードの入力があれば、関数key_press_funが実行される


関数key_press_funは自分で定義する。
この中に、strKeyは今押したキー。curXは今マウスの位置のX座標。
dを押すと、関数drawNext()が実行される。ほかa,1,2,3,4を押すと、それぞれの関数が実行される。
他の機能を追加したいとき、この中に新しいキーと対応する関数を書けばできる。


また、マウスをクリックすると、関数click_funが実行される。


click_funはマウスの位置のX座標のprintだけをやっている。


プログラム他の部分は自分で理解してみてください。



問題


1.ファイルAKKL1548422986.bz2を表示して、下図のように81ms付近に拡大して、たくさんのパルスが見えます。

マウスで任意のパルスにクリックすると、コマンドプロンプトにこのパルスのピーク値とピープの時刻が表示されるという機能を作ってください。
この機能を利用して、上図の赤い丸にある10個のパルスのピーク値とピークの時刻を求めてください。
(注意:マウスのクリック位置はちょうどパルスのピークとは限らない)


2.「発展問題」任意スケールの拡大機能

マウスを波形にクリックすると、クリックの位置に緑の縦線が出る。 もう一回クリックすると、緑線と2回目クリックの位置の間の波形が表示される。 表示される波形にもう一回クリックすると、クリックの位置に緑の縦線はまた出る。 さらにクリックすると、緑線と今クリックの位置の間の波形が表示される。 このように波形を任意のスケートで表示することができる。このような機能を作ってください。

実際の操作の様子は以下のビデオを見てください。
http://tingwu.info/pylab/data/python10-demo.mp4

同じようなビデオを作って提出してください。(無料のソフトでできます)