はじめに
pyVISAの記事で、「LabVI*Wの代わりになる!」というようなことを書いていました。
ただ、LabV*EWのいいところって、GUI上にグラフを用意して、測定値データをリアルタイムで確認できたりするところにあるんですよね。。。
そこで、Pythonで利用できるTkinter
を使って、測定値のデータをGUI上にリアルタイムで表示するプログラムを考えてみました。
環境
- python3.9.7
- MacOS 14.2.1
※ Windowsでも動くと思います。
計測値データを作成
計測器が周りに無かったので(泣)、計測データっぽいものを捏造作成してみましょう。
線形に増加する値に、乱数を乗せるプログラムを書きました。
今回はこれで作成したデータを一定間隔で読み出し、擬似的に計測器からデータが返ってきた状況を表現してみようと思ます。
import numpy as np
def generate_random_data(x_range, random_range):
np.random.seed(123)
x = np.linspace(*x_range)
random_values = np.random.uniform(*random_range, size=x.shape)
y = x + random_values
return x, y
if __name__=='__main__':
x_range = (0, 30)
random_range = (0, 2)
x, y = generate_random_data(x_range=x_range, random_range=random_range)
# save
np.savetxt('data.csv', np.column_stack((x, y)), delimiter=',')
Tkinter
のGUI上にグラフを表示
Tkinter
の書き方は色々と派閥があるようですが、私はclass
を使った書き方が好きです。
ボタンのコマンドとして、メソッドを用意する、といった設計ができるためです。
GUI上の情報なども、メソッドを用意しておけばインスタンスから取得することもできます。
まずは下のclass
をベースに、グラフを表示する方法を書いていきます。
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import numpy as np
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.master.title('matplotlib graph')
# matplotlib配置用フレーム
frame = tk.Frame(self.master)
# matplotlibの描画領域の作成
fig = Figure()
self.ax = fig.add_subplot(1, 1, 1)
# matplotlibの行場領域とウィジェットの関連付け
self.fig_canvas = FigureCanvasTkAgg(fig, frame)
# matplotlibのツールバーを作成
self.toolbar = NavigationToolbar2Tk(self.fig_canvas, frame)
# matploglibのグラフをフレームに配置
self.fig_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# フレームをウィンドウに配置
frame.pack()
# ボタンの作成
button = tk.Button(self.master, text='Draw Graph', command=self.button_click)
# 配置
button.pack(side=tk.BOTTOM)
def button_click(self):
with open('data.csv', 'r') as f:
data = np.loadtxt(f, delimiter=',')
x = data[:, 0]
y = data[:, 1]
self.ax.plot(x, y)
self.fig_canvas.draw()
if __name__=='__main__':
root = tk.Tk()
app = Application(master=root)
app.mainloop()
プログラムを実行し、Draw Graph
のボタンを押せばグラフが表示されるはずです。
計測器「っぽい」動作をする関数を用意
計測器のように、呼び出すたびに値を返すような関数を作ってみます。
先のモジュールで作成したdata.csv
を読み取り、呼び出すたびに上から順に値を返すような関数を書いてみます。
ここではiterator
を使って実装してみました。
class Iterator(object):
def __init__(self, array):
self._array = array
self._i = 0
def __iter__(self):
return self
def __len__(self):
return self._array.shape[0]
def __next__(self):
if self._i == len(self._array.shape[0]):
raise StopIteration()
x, y = self._array[self._i]
self._i += 1
return x, y
リアルタイムに表示する
Tkinter
でGUIを表示する場合、mainloop()
を使っています。
インスタンスからGUIの表示内容を更新することは不可なので、class
にメソッドを追加していきます。
グラフをリアルタイムに生成するメソッドは、以下のように書きました。
計測器を使うときのイメージは、itr
からデータを呼び出すitr.__next__()
を、計測器からデータを読み込む関数に置き換えれば良いと思います。
def run_gengraph(self):
with open('data.csv', 'r') as f:
data = np.loadtxt(f, delimiter=',')
# iteratorのインスタンス生成
itr = Iterator(data)
# グラフ描画用のlist
x_ = []
y_ = []
for _ in range(len(itr)):
# itrからデータを取得
# 計測データはここで受ければOK
x, y = itr.__next__()
# リストに追加
x_.append(x)
y_.append(y)
# 描画
self.ax.clear() # グラフのリセット
self.ax.plot(x_, y_)
self.fig_canvas.draw()
# GUIの更新
self.master.update()
# 待機時間
time.sleep(1)
ちょっとハマったところは、ただ描画するだけではGUIの更新がされませんでした。
self.master.update()
を追加することで更新され、ブラフにプロットがどんどん追加されるようになりました。
また、self.ax.clear()
を追加することで、プロットごとに線が追加されることなく更新される様になってます。
(いずれもChatGPTに聞くと教えてくれました。ありがたや〜)
最終的にGUIを記述しているclass
は以下の通りです。
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.master.title('matplotlib graph')
# matplotlib配置用フレーム
frame = tk.Frame(self.master)
# matplotlibの描画領域の作成
fig = Figure()
self.ax = fig.add_subplot(1, 1, 1)
# matplotlibの行場領域とウィジェットの関連付け
self.fig_canvas = FigureCanvasTkAgg(fig, frame)
# matplotlibのツールバーを作成
self.toolbar = NavigationToolbar2Tk(self.fig_canvas, frame)
# matploglibのグラフをフレームに配置
self.fig_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# フレームをウィンドウに配置
frame.pack()
# ボタンの作成
button = tk.Button(self.master, text='Draw Graph', command=self.run_gengraph)
# 配置
button.pack(side=tk.BOTTOM)
def run_gengraph(self):
with open('data.csv', 'r') as f:
data = np.loadtxt(f, delimiter=',')
# iteratorのインスタンス生成
itr = Iterator(data)
# グラフ描画用のlist
x_ = []
y_ = []
for _ in range(len(itr)):
# itrからデータを取得
x, y = itr.__next__()
# リストに追加
x_.append(x)
y_.append(y)
# 描画
self.ax.clear() # グラフのリセット
self.ax.plot(x_, y_)
self.fig_canvas.draw()
# GUIの更新
self.master.update()
# 待機時間
time.sleep(1)
最後に
Tkinter
を使ってGUI上にグラフを描画し、更新されるデータをリアルタイムで描画する方法をまとめました。
これでLabVIE*に少しでも近づけたかな?
今後は本家のグラフデザインに少しでも近づけるのと、やはり計測器を繋げたときにどんなプログラムになるのかをまとめたいです。
Github
参考にした記事