当サイトには広告・プロモーションが含まれています。

Matplotlib | GUIでバックグラウンド処理後に複数のガウシアンフィッティング(2)

この記事で分かること
  • pythonのmatplotlibでGUIでフィッティングをしたい人向けの記事です。
    • (1) はじめにバックグラウンドをフィッティング
    • (2) さらに残差を複数のガウシアンでフィッティング
  • という解析ステップのより実践的なスクリプトを紹介します。

第一回 コード、使用方法、概要紹介

目次

使用方法

前回と重複しますが、復習で使用方法を載せます。

STEP
上段のプロット: ax1

ax1はデータにバックグラウンド(BG)をフィットします。

  • 左クリックでSS1を設定でき、BGのフィット範囲(ROI)を決めます。
  • 右クリックでSS2を設定でき、BGフィットを2つのROIで行えます
  • 残差が青線で表示されます。
STEP
下段のプロット: ax2

ax2はもax1に表示されるプロット残差が表示されます。

  • 左クリックでRS1を描いて初期値を与えたガウシアンを設定できます。
  • キーボードで1から9を押すとガウシアンを追加できます。画像では2,3を押して計3つのガウシアンを設定しています。
    ctrl+1~9で作成した該当のモデルを消去できます。
STEP
フィット実行
  • 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左クリックSpanSelectorfit.select_callback11BGフィットのROI
→ フィット&プロット
self.fit1()
右クリックSpanSelectorfit.select_callback12BGフィットのROI2
→ フィット&プロット
self.fit1()
ax2左クリックRectangleSelectorfit.select_callback21ガウシアン初期値
→ prep_gaussでモデル作成
self.prep_gauss()
→ 初期値をプロット
右クリックSpanSelectorfit.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()でデータを渡しています。

plt.plt()のデータはset_data()ですが、plt.scatter()ではset_offsets()で更新できます。

キーボード操作関係

    # キーボード入力を受ける
    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フィットを行うスクリプトのコードを解説しました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次