Fletを使った簡易計測のひな型


Fletを使った簡易な計測GUIのひな型（シミュレーション）を作ったので、覚えとして記載します。

#### 仕様
- ある値を徐々に変えて、その応答を測定する
（今回の例では、分光器を制御して試料に当てる光のエネルギーを変えていき、その時に試料から放出される電子を測定する。光電子収量（Photonelectron Yield Spectroscopy: PYS）と呼ばれる測定）
- リアルタイム（100ms程度）でグラフを更新する
- 途中で測定をキャンセルできるようにする


#### 開発環境
- windows 11 pro
- miniconda
(minicondaで最低限のファイルと仮想環境を作り、それ以外はすべてpipでインストール)
- Python=3.10
- Flet　（GUIとして）
- 測定によって、pyserial, pyvisa, nidaqmxなどをインストール


#### コード

```Python
import time
import datetime
from pathlib import Path

import matplotlib
matplotlib.use('Agg') 
matplotlib.use("svg")
import matplotlib.pyplot as plt

import numpy as np
import pandas as pd
import flet as ft
from flet.matplotlib_chart import MatplotlibChart


# -----
def ramp_func(start,end,step):
    #　測定点を作る配列
    s_d = len(str(step).split('.')[1]) # 小数点以下の桁数を調べる
    ramp_ = np.round(np.arange(start,end+step,step),s_d) # 数の丸めを行う。
    ramp_= ramp_[np.where(ramp_ <= end)]
    # print(ramp_)
    return ramp_
# -----


# global変数（途中で測定を止めるために利用する）
flag= False　# False:続行、True:停止とする


def main(page: ft.Page):
    
    out_file_name = ft.Ref[ft.Text]()
    fig, ax = plt.subplots()
    ui_rows = []
    
    # callback
    def goto_clicked(e):
        ct_ene.value = float(move_ene.value)
        time.sleep(0.5)
        page.update()
        
    def current_clicked(e):
        ct_ene.value = float(move_ene.value)
        page.update()
        
    def start_clicked(e):
        measure_plot(fig, ax )
     
    def abort_clicked(e):
        # For loopを途中で止めるため
        global flag
        flag = True
    
    # GUI 設定        
    title_txt = ft.Text("PYS Measurement System",size=20)
    start_ene = ft.TextField(label="Start[eV]", value="4",width=100)
    end_ene = ft.TextField(label="End[eV]", value="6.2",width=100)
    step = ft.TextField(label="Step[eV]", value="0.05",width=100)
    move_ene = ft.TextField(label="Move[eV]", value="4",width=100)
    ct_ene =  ft.TextField(label="Current[eV]", value="0",width=100)
    out_file =  ft.Text(ref=out_file_name)
    
    goto_btn = ft.ElevatedButton("Goto", on_click=goto_clicked)
    current_btn = ft.ElevatedButton("Current", on_click=current_clicked)
    start_btn = ft.ElevatedButton("Start", on_click=start_clicked)
    abort_btn = ft.ElevatedButton("Abort", on_click=abort_clicked)
    

    ui_rows.append(ft.Row(controls=[title_txt]))
    ui_rows.append(ft.Row(controls=[current_btn,ct_ene, goto_btn, move_ene]))
    ui_rows.append(ft.Row(controls=[start_btn, abort_btn]))
    ui_rows.append(ft.Row(controls=[start_ene, end_ene, step]))
    ui_rows.append(ft.Row(controls=[out_file]))
    
    fig_draw = MatplotlibChart(fig,expand=True)
                  
    ui_controls = ft.Column(controls=ui_rows)
    page.add(ui_controls, fig_draw) 
    
    # 測定 Realtimeでグラフを更新する    
    def measure_plot(fig, ax):
        global flag
        flag = False
        
        # スキャンするXの範囲
        start_f = float(start_ene.value)
        end_f = float(end_ene.value)
        step_f = float(step.value)

        xs = []
        ys = []
        
        for x in ramp_func(start_f,end_f,step_f):
            ax.cla()
            
            # ---測定する命令を書く
            # 例えばエネルギーを設定する。
            # 測定器から帰ってくる値をとる
            x1 = x 
            y1 = x1**4 # デモとして4乗の値を返す
            
            # ---
            
            xs.append(x1)
            ys.append(y1)
            
            ax.plot(xs, ys,'ro-')
            ax.set_xlabel('Energy[eV]')
            ax.set_ylabel('Yield')
            ax.grid()
            
            fig_draw.update()

            if flag:
                break
            
            time.sleep(0.5)
        
        # data Save　測定が終わると自動でデータをSaveする
        save_path_name = 'results'
        Path(f'./{save_path_name}').mkdir(exist_ok=True)
        now = datetime.datetime.now()
        ext_file_name = f'./{save_path_name}/{out_file.value}_{now.strftime("%Y%m%d_%H%M%S")}'
        ext_csv = ext_file_name + '.csv' 
        ext_png = ext_file_name + '.png' 
        
        df_ = pd.DataFrame({"x":xs,"y":ys})
        df_.to_csv(ext_csv,index=False)
        fig.savefig(ext_png)
        # plt.close(fig)
        

if __name__ == '__main__':
    ft.app(target=main)   
    # ft.app(target=main, view=ft.WEB_BROWSER)
```

#### 測定画面と測定結果（シミュレーション）

![GUI001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/1119d40c-64a3-5607-c72d-27003026810c.png)


測定をスタートする

![GUI002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/e466ff8e-871c-5fb5-8ae4-62d7fc834fa1.png)