- pythonのmatplotlibでGUIでフィッティングをしたい人向けの記事です。
- (1) はじめにバックグラウンドをフィッティング
- (2) さらに残差を複数のガウシアンでフィッティング
- という解析ステップのより実践的なスクリプトを紹介します。
第一回 コード、使用方法、概要紹介
使用方法
前回と重複しますが、復習で使用方法を載せます。
ax1はデータにバックグラウンド(BG)をフィットします。
- 左クリックでSS1を設定でき、BGのフィット範囲(ROI)を決めます。
- 右クリックでSS2を設定でき、BGフィットを2つのROIで行えます。
- 残差が青線で表示されます。
ax2はもax1に表示されるプロット残差が表示されます。
- 左クリックでRS1を描いて初期値を与えたガウシアンを設定できます。
- キーボードで1から9を押すとガウシアンを追加できます。画像では2,3を押して計3つのガウシアンを設定しています。
ctrl+1~9で作成した該当のモデルを消去できます。
- Enterを押すとフィットを実行します。初期値ではプロット全体をフィットします。
- ax2を右クリックでフィット範囲を決めるSS3を設定できます。SS3を設定後フィットを実行します。
ポイント解説
マウス操作関係
# ax1のフィット範囲1。左クリック
SS1 = SpanSelector(
ax1, fit.select_callback11,
"horizontal", button=[1],
useblit=True, props=dict(alpha=0.2, facecolor="tab:blue"),
interactive=True, drag_from_anywhere=True)
# ax1のフィット範囲2。右クリック
SS2 = SpanSelector(
ax1, fit.select_callback12,
"horizontal", button=[3],
useblit=True, props=dict(alpha=0.2, facecolor="tab:red"),
interactive=True, drag_from_anywhere=True)
# ax2のモデル作成。左クリック
RS1 = RectangleSelector(
ax2, fit.select_callback21, button=[1],
interactive=True, props=dict(alpha=0.2, facecolor="tab:blue"))
# ax2のフィット範囲1。右クリック
SS3 = SpanSelector(
ax2, fit.select_callback22,
"horizontal", button=[3],
useblit=True, props=dict(alpha=0.1, facecolor="tab:grey"),
drag_from_anywhere=True)
マウス操作はax1とax2で異なる挙動になります。まとめると
プロット | マウス | ウィジェット | コールバック関数 | 内容 |
---|---|---|---|---|
ax1 | 左クリック | SpanSelector | fit.select_callback11 | BGフィットのROI → フィット&プロット self.fit1() |
右クリック | SpanSelector | fit.select_callback12 | BGフィットのROI2 → フィット&プロット self.fit1() | |
ax2 | 左クリック | RectangleSelector | fit.select_callback21 | ガウシアン初期値 → prep_gaussでモデル作成 self.prep_gauss() → 初期値をプロット |
右クリック | SpanSelector | fit.select_callback22 | ガウシアンフィットのROI → フィット&プロット self.fit2() |
SpanSelectorはいずれもフィットのROI(x座標: x0,x1)を与えます。
# ax1のフィット範囲1
def select_callback11(self, x0, x1):
# 左クリック
self.list_ROI1[0],self.list_ROI1[1] = x0,x1
self.fit1()
# ax1のフィット範囲2
def select_callback12(self, x0, x1):
# 右クリック
self.list_ROI1[2],self.list_ROI1[3] = x0,x1
self.fit1()
ax1で取得したROIはself.list_ROI1に記録し、フィットを実施するself.fit1()を呼びます。
# ax2のモデルを用意&プロット
def select_callback21(self, eclick, erelease):
x0, y0 = eclick.xdata, eclick.ydata
x1, y1 = erelease.xdata, erelease.ydata
if self.key_input in self.cmps:
gauss,params = self.prep_gauss(self.key_input, x0,x1,y0,y1)
self.dict_model22.update({
self.key_input:{
"ROI":[x0,x1],
"prefix":f"c{self.key_input}_",
"model":gauss,
"params":params}
})
print(list(self.dict_model22.keys()))
model,params = self.dict_model22["1"]["model"], self.dict_model22["1"]["params"]
for key in list(self.dict_model22.keys()):
if key != "1":
model += self.dict_model22[key]["model"]
params.update(self.dict_model22[key]["params"])
self.dict_model21.update({
"model":model,
"params":params
})
# モデルの和をプロット
plot_fit2_ax2.set_data(self.df.x,model.eval(params, x=self.df.x))
# 各モデルをプロット
for key in list(self.dict_model22.keys()):
plot_fit2_c1_ax2[key][0].set_data(self.df.x, self.dict_model22[key]["model"].eval(self.dict_model22[key]["params"], x=self.df.x)) # [0] is necessary
fit.select_callback21のRectangleSelectorは、ガウシアンモデルの初期値を設定するために使います。
受け取ったROIを引数にself.prep_gauss()を呼び、lmfitのモデルとパラメータを受け取ります。
作ったモデルは全モデルと各要素を、それぞれself.dict_model21とself.dict_model22にメモしておきます。
変数名 | 記録する内容 | キー |
---|---|---|
self.dict_model21 | 全モデル | [model],[params],[ROI] |
self.dict_model22 | 各モデル | [num][model], [num][params], [num][ROI], [num][prefix] numの型はstringでモデルの番号。 self.cmps で1~9に限定。 |
メモが終わったら、すぐに初期値での全モデルと各モデルをプロットします。
プロットはすべて予め与えてあったplotのオブジェクトにset_data()でデータを渡しています。
キーボード操作関係
# キーボード入力を受ける
def key_press(self,event):
self.key_input_previous = self.key_input
self.key_input = event.key
print(event.key)
print(list(self.dict_model22.keys())) # fit2に含まれるモデル
# 1~9: モデルiの削除
if event.key in [f'ctrl+{i}' for i in list(self.dict_model22.keys())]:
i = event.key.replace("ctrl+","")
self.dict_model22.pop(i)
plot_fit2_ax2.set_data([],[])
plot_fit2_c1_ax2[i][0].set_data([],[])
plot_residual2_ax2.set_data([],[])
fig.canvas.draw()
fig.canvas.flush_events()
print(f'del {i}')
# Enter: ax2のfit
if(event.key=="enter"):
self.fit2()
# key_inputwを戻した方が便利
self.key_input = self.key_input_previous
fig.canvas.mpl_connect('key_press_event', fit.key_press)
fig.canvas.mpl_connect(‘key_press_event’)でfigとキー入力を繋いで、fit.key_press()を呼びます。
event.keyでキーを抽出し、クラス変数self.key_inputにメモしておきます。
影響のあるキーは下記の3種類です。
- 1~9
-
1~9はself.cmpsに含まれる文字で、self.select_callback21()はこの値を参照してどの番号のモデルかを判断します。
- 以下のキーはこの関数内ですぐに実行します。
- ctrl+1~9
-
self.dict_model22[1~9]にメモした番号のモデルを削除します。プロットも更新します。
なお修飾キーはctrl以外にも色々使えます。該当の公式リンク下記でご参考。 - enter
-
self.fit2()でフィットを実行します。
なおplot上に残っているROIを使いやすくするためにself.key_inputを”enter”から戻してます。
フィッティング関係
def fit1(self):
# フィット範囲を抽出したdf
x0,x1,x2,x3 = self.list_ROI1
df01, df23 = df.query(f'{x0}<x<{x1}'), df.query(f'{x2}<x<{x3}')
df2 = pd.concat([df01, df23])
model1 = PseudoVoigtModel()
params1 = model1.guess(df.y, x=df.x)
result = model1.fit(df2.y, params1, x=df2.x)
def fit2(self):
x0,x1 = self.dict_model21["ROI"]
model, params = self.dict_model21["model"], self.dict_model21["params"]
df2 = self.df.query(f'{x0}<x<{x1}')
result = model.fit(df2["residual1"], params, x=df2.x)
print(result.fit_report())
fit2 = model.eval(result.params, x=self.df.x)
fit2_ROI = model.eval(result.params, x=df2.x)
# フィットと残差をプロット
plot_fit2_ax2.set_data(df2.x,fit2_ROI )
plot_residual2_ax2.set_data(df2.x, df2["residual1"] -fit2_ROI)
# 各要素をプロット
comps = result.eval_components(x = self.df.x)
for key in list(self.dict_model22.keys()):
plot_fit2_c1_ax2[key][0].set_data(self.df.x, comps[f"c{key}_"]) # [0] is necessary
ROI(フィットする領域)のデータの抽出にはpandasのquery()を使っています。
ax1の2つのROIを使うために、2つqueryしてconcatで連結しdf2としています。
結果をプロットするのに、プロット範囲を変えています。
- フィット範囲外までデータを作成 … model.eval()の引数xに元のデータdfのx軸
- フィット範囲内のデータを作成 … model.eval()の引数xにROI内で抽出したデータdf2のx軸
結果を各モデル毎にプロットするために、result.eval_components()を使っています。下記外部記事ご参考。
# ガウシアンモデル作成
def prep_gauss(self,i, x0,x1,y0,y1 ):
xmin,ymin = [min(x0, x1), min(y0, y1)]
xmax,ymax = [max(x0, x1), max(y0, y1)]
print(xmin,xmax,ymin,ymax)
gauss = GaussianModel(prefix=f'c{i}_')
params = gauss.make_params(
center = xmin +(xmax-xmin)/2,
sigma = (xmax -xmin)/4,
amplitude = (ymax -ymin)
)
return gauss,params
ガウシアンモデルを区別するためにGaussianModelの引数にprefixを与えています。作ったモデルとパラメータを返します。
まとめ
matplotlibでGUIフィットを行うスクリプトのコードを解説しました。
コメント