GUIでファイル選択をして最小二乗

09-leastsq-GUI.py

import sys
import os
from tkinter import *
from tkinter import ttk
from tkinter import filedialog, messagebox
from pprint import pprint
import csv
from math import sqrt
import numpy as np
from scipy import optimize
from matplotlib import pyplot as plt


"""
GUIでファイル選択をして最小二乗
"""



#=============================
# 大域変数の定義
#=============================
# フィッティングパラメータ初期値。線形最小二乗の場合は適当
ai0 = [0, 0, 0]
xrange = [0, 10]
# グラフのフォントサイズ
fontsize = 24

window_size = "500x200"
ini_dir = os.path.abspath(os.path.dirname(__file__))
file_type = [('CSV file', '*.csv')]


#=============================
# csvファイルの読み込み
#=============================
def read_csv(path):
    i = 0
    x = []
    y = []
    with open(path, "r") as f:
        reader = csv.reader(f)

        for row in reader:
            if i == 0:
                header = row
            else:
                xi = float(row[0])
                x.append(xi)
                y.append(float(row[1]))
            i += 1

    print("")
    print("CSV data:")
    print(" header:", header)
    print(" x:", x)
    print(" y:", y)
    return header, x, y


def path_button_click(pathvar, x0var, x1var, obj):
    selpath = filedialog.askopenfilename(filetypes = file_type, initialdir = ini_dir)
    pathvar.set(selpath)
    header, x, y = read_csv(selpath)
    x0var.set(min(x))
    x1var.set(max(x))
    obj['path'] = selpath
    obj['header'] = header
    obj['x'] = x
    obj['y'] = y
    obj['savepath'].set(os.path.splitext(selpath)[0] + '-fit.csv')

#=============================
# 最小化する関数の定義
#=============================
def ycal(ai, x):
    return ai[0] + ai[1] * x + ai[2] * x * x

def residual(ai, x, y):
    res = []
    for i in range(len(x)):
        res.append(y[i] - ycal(ai, x[i]))
    return res


#=============================
# scipy.optimize()による最小化
#=============================
def lsq_button_click(x, y, x0, x1, obj):
    print("")
    print("polynomial fit by scipy.optimize() start:")

    xf = []
    yf = []
    for i in range(len(x)):
        if x0 <= x[i] <= x1:
            xf.append(x[i])
            yf.append(y[i])

# leastsqの戻り値は、最適化したパラメータのリストと、最適化の結果
    ai, cov_x, inf, mesg, ier = optimize.leastsq(residual, ai0,
            args= (xf, yf), full_output = True)
    print(" lsq result: ai=", ai)
    res = sqrt(sum(inf['fvec']*inf['fvec']) / len(x))
    print(" residual=", res)
    print(" y = {} + {} * x + {} * x^2".format(ai[0], ai[1], ai[2]))


#=============================
# グラフの表示
#=============================
#表示データの作成
    ncal = 100
    xmin = min(x)
    xmax = max(x)
    xstep = (xmax - xmin) / (ncal - 1)
    xc = []
    yc = []
    for i in range(ncal):
        xi = xmin + i * xstep
        yi = ycal(ai, xi)
        xc.append(xi)
        yc.append(yi)

#グラフの作成、表示
    plt.clf()
    plt.plot(x, y, label = 'raw data', marker = 'o', linestyle = 'None')
    plt.plot(xc, yc, label = 'fitted', linestyle = 'dashed')
    plt.title(obj['path'], fontsize = fontsize)
    plt.xlabel(obj['header'][1], fontsize = fontsize)
    plt.ylabel(obj['header'][0], fontsize = fontsize)
    plt.legend(fontsize = fontsize)
    plt.tick_params(labelsize = fontsize)
    plt.tight_layout()

    plt.pause(0.001)
# plt.show()


def main():
    obj = {}

    root = Tk()
    root.title('Second-order polynomial lsq')
# root.resizable(False, False)
    root.geometry(window_size)
    root.minsize(200, 200)

    path = StringVar()
    savepath = StringVar()
    x0 = DoubleVar(value = xrange[0])
    x1 = DoubleVar(value = xrange[1])
    obj['savepath'] = savepath

# Menu
    menu_bar = Menu(root)
    menu_file = Menu(menu_bar, tearoff = 0)
    menu_file.add_command(label='Open', accelerator='Ctrl+O',
                    command = lambda: path_button_click(path, x0, x1, obj))
    menu_file.add_command(label='exit', accelerator='Alt+E',
                    command = lambda: exit())
    menu_bar.add_cascade(label = 'File', menu = menu_file)
    root.config(menu = menu_bar)
    root.grid()

# Root frame
    root_frame = ttk.Frame(root, padding=10)
    root_frame.pack(side = 'top')

# Path frame
    path_frame = ttk.Frame(root_frame)
    path_label = ttk.Label(path_frame, text = 'Path:', padding = (5,2))
    path_label.pack(side = 'left')
    path_entry = ttk.Entry(
        path_frame,
        textvariable = path,
        width = 50
        )
    path_entry.pack(side = 'left', expand = True)

    path_button = ttk.Button(path_frame, text = 'path',
                    command = lambda: path_button_click(path, x0, x1, obj))
    path_button.pack(side = 'left')
    path_frame.pack(side = 'top', anchor = 'w')

# Save path frame
    savepath_frame = ttk.Frame(root_frame)
    savepath_label = ttk.Label(savepath_frame, text = 'Save path:', padding = (5,2))
    savepath_label.pack(side = 'left')
    savepath_entry = ttk.Entry(
        savepath_frame,
        textvariable = savepath,
        width = 50
        )
    savepath_entry.pack(side = 'left', expand = True)

    savepath_button = ttk.Button(savepath_frame, text = 'save',
                    command = lambda: savepath_button_click(path, obj))
    savepath_button.pack(side = 'left')
    savepath_frame.pack(side = 'top', anchor = 'w')

# Range frame
    range_frame = ttk.Frame(root_frame)
    range_label = ttk.Label(range_frame, text = 'x range:', padding = (5,2))
    range_label.grid(row = 1, column = 0, sticky = 'w')
    x0_entry = ttk.Entry(
        range_frame,
        textvariable = x0,
        width = 10)
    x0_entry.grid(row = 1, column = 1)

    range_label2 = ttk.Label(range_frame, text = '-', padding = (5,2))
    range_label2.grid(row = 1, column = 2, sticky = E)
    x1_entry = ttk.Entry(
        range_frame,
        textvariable = x1,
        width = 10)
    x1_entry.grid(row = 1, column = 3)
    range_frame.pack(side = 'top', anchor = 'w')

# Button frame
    button_frame = ttk.Frame(root_frame)
    lsq_button = ttk.Button(button_frame, text = 'lsq',
        command = lambda: lsq_button_click(obj['x'], obj['y'], x0.get(), x1.get(), obj))
    lsq_button.pack(side = 'left')
    exit_button = ttk.Button(button_frame, text = 'exit', command = lambda: exit())
    exit_button.pack(side = 'left')
    button_frame.pack(side = 'top', anchor = 'w')

    root.mainloop()


if __name__ == '__main__':
    main()