# Copyright 2025, Seiko Epson Corporation
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the “Software”), to deal in
# the Software without restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
# Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.

###
### 道路橋 加速度応答から変位変化を算出する
###   rev. 1.00
### 2024/12/28 - 2025/1/13  Y. Kobayashi
###
### python     3.12.3
### numpy      1.26.4
### pandas     2.2.2
### matplotlib 3.9.0
###

import logging
import os
import pathlib
import tkinter
import tkinter.filedialog
import tkinter.messagebox
from tkinter.scrolledtext import ScrolledText

import matplotlib.pyplot as plt  # type: ignore # typed バージョンなし
import numpy as np
import pandas as pd
from matplotlib import transforms
from PIL import Image

import epson_bdc.constants as CONSTANTS


def output_error(word_dict, key, err):
    """
    エラー出力関数
    """
    message = ""
    # - key が word_dict内にあるか判定する
    if key in word_dict:
        # - key があればその内容を表示
        # - エラー内容の見出しを言語情報で分岐させ日英それぞれで表示
        if word_dict[CONSTANTS.KEY_LANG] == CONSTANTS.LANG_JP:
            message = f"{word_dict[key]}\n\nエラー内容：\n{err}"
        if word_dict[CONSTANTS.KEY_LANG] == CONSTANTS.LANG_EN:
            message = f"{word_dict[key]}\n\nERROR:\n{err}"
    else:
        # - key がなければその旨表示
        # - エラー内容は言語情報で分岐させ日英それぞれで表示
        if word_dict[CONSTANTS.KEY_LANG] == CONSTANTS.LANG_JP:
            message = (
                "システムエラーが発生しました\n"
                + "ソフトウェアを再度インストールしてください\n\n"
                + "エラー内容：\n"
                + "設定ファイルに異常があり必要な情報が読み込めません"
            )
        if word_dict[CONSTANTS.KEY_LANG] == CONSTANTS.LANG_EN:
            message = (
                "A system error has occurred\n"
                + "Please reinstall the software\n\n"
                + "ERROR:\n"
                + "There is an abnormality in the configuration file, and the necessary information cannot be loaded"
            )

    assert message
    tkinter.messagebox.showerror(
        title="",
        message=message,
    )

    # 初期化済みのロガーを使用してerror.logを出力
    logger = logging.getLogger("epson_bdc")
    # - 設定に従いerror.logをスタックトレース付きで出力
    logger.error(err, exc_info=True)


class EULADialog(tkinter.Frame):
    """
    EULAダイアログ用クラス
    """

    def __init__(self, master, word_dict, bundle_dir):
        super().__init__(master)

        self.agreed = False

        self.master.geometry("1000x800")

        try:
            # epson_bdcディレクトリを起点にファイル取得を行う
            self.master.iconphoto(
                True,
                tkinter.PhotoImage(file=os.path.join(bundle_dir, "img", "icon.png")),
            )
        except tkinter.TclError as e:
            logger = logging.getLogger("epson_bdc")
            logger.error(e, exc_info=True)

        # ダイアログで使用する文言を word_dict より取得
        title = word_dict[CONSTANTS.KEY_EULA_TITLE]
        font = word_dict[CONSTANTS.KEY_EULA_FONT]

        self.master.title(title)

        lbl = tkinter.Label(
            self.master,
            text=title,
            font=(font, 18, "bold"),
        )
        text = ScrolledText(self.master, font=(font, 15))

        # 言語設定に応じたEULAの文言を取得
        # - EULAのファイルは言語ごとに EULA_{言語設定情報}.txt という名前になっている
        with open(
            os.path.join(
                bundle_dir, "doc", f"EULA_{word_dict[CONSTANTS.KEY_LANG]}.txt"
            ),
            encoding="utf-8",
        ) as f:
            eula_txt = f.read()

        text.insert(1.0, eula_txt)
        text.configure(state="disabled")
        text.config(spacing2=5)

        lbl2 = tkinter.Label(
            self.master,
            text=word_dict[CONSTANTS.KEY_CONFIRM_AGREEMENT],
            font=(font, 12),
        )
        button1 = tkinter.Button(
            self.master,
            text=word_dict[CONSTANTS.KEY_AGREE],
            font=(font, 12, "bold"),
            command=self.agree,
        )
        button2 = tkinter.Button(
            self.master,
            text=word_dict[CONSTANTS.KEY_DISAGREE],
            font=(font, 12, "bold"),
            command=self.not_agree,
        )

        lbl.pack(pady=5)
        text.pack(fill=tkinter.BOTH, expand=True, padx=20, pady=20)
        lbl2.pack(pady=5)

        button1.pack(
            side=tkinter.LEFT,
            anchor=tkinter.E,
            ipadx=10,
            ipady=10,
            padx=10,
            pady=10,
            expand=True,
        )
        button2.pack(
            side=tkinter.RIGHT,
            anchor=tkinter.W,
            ipadx=10,
            ipady=10,
            padx=10,
            pady=10,
            expand=True,
        )

        self.master.resizable(False, False)

        self.master.protocol("WM_DELETE_WINDOW", self.not_agree)

        self.master.bind("<Visibility>", self.centering_main_window)

    # - 同意した場合の処理を定義
    def agree(self):
        self.master.destroy()
        self.agreed = True

    # - 同意しなかった場合の処理を定義
    def not_agree(self):
        self.master.destroy()

    # - ダイアログが画面の中央に表示されるための関数を定義
    def centering_main_window(self, event):
        screen_width = self.master.winfo_screenwidth()
        screen_height = self.master.winfo_screenheight()
        window_width = self.master.winfo_width()
        window_height = self.master.winfo_height()
        x = screen_width / 2 - window_width / 2
        y = screen_height / 2 - window_height / 2
        self.master.geometry("+%d+%d" % (x, y))


class ColumnNameNotFoundError(Exception):
    """
    列名が見つからない場合のエラー
    """

    pass


class ColumnNameFoundMultipleError(Exception):
    """
    列名が複数存在する場合のエラー
    """

    pass


def get_valid_column(df, valid_col_names, word_dict):
    """
    列名チェック関数
    """
    exists_col_names = [v for v in valid_col_names if v in df]

    if len(exists_col_names) == 1:
        return df[exists_col_names[0]]
    elif len(exists_col_names) > 1:
        # - 列名が不正でもプログラム的にエラーは発生しないのでエラーを自作しraise
        raise ColumnNameFoundMultipleError(
            f'{word_dict[CONSTANTS.KEY_MULTIPLE_COL]}\n"{valid_col_names[0]}"'
        )
    else:
        # - 列名が不正でもプログラム的にエラーは発生しないのでエラーを自作しraise
        raise ColumnNameNotFoundError(
            f'{word_dict[CONSTANTS.KEY_NOT_FOUND_COL]}\n"{valid_col_names[0]}"'
        )


def calculate_bridge_deflection(exe_dir, bundle_dir, word_dict):  # noqa: C901
    """
    橋梁変位推定関数
    """
    try:
        # EULAに一度同意したら次回起動時にEULAダイアログを表示させない
        # - EULA同意フラグ用のファイルを定義
        agreed_file_path = os.path.join(bundle_dir, "agreed.txt")
        # - EULA同意フラグ用のファイルがなければダイアログを表示する
        if not os.path.exists(agreed_file_path):
            root_eula = tkinter.Tk()
            app = EULADialog(root_eula, word_dict, bundle_dir)
            app.mainloop()

            # - 同意した場合EULA同意フラグ用のファイルを作成し処理を続行する
            # - 同意しなかった場合プログラムを終了する
            if app.agreed:
                pathlib.Path(agreed_file_path).touch()
            else:
                return

        ########################################## ファイル選択ダイアログの表示  データファイルの選択
        root = tkinter.Tk()
        root.withdraw()
        try:
            root.iconphoto(
                True,
                tkinter.PhotoImage(file=os.path.join(bundle_dir, "img", "icon.png")),
            )
        except tkinter.TclError as e:
            logger = logging.getLogger("epson_bdc")
            logger.error(e, exc_info=True)
        fTyp = [("", "*.csv")]

        # ソフトウェア名、word_dict を引数として渡す
        tkinter.messagebox.showinfo(
            f"{CONSTANTS.SOFTWARE_NAME} {CONSTANTS.VERSION}",
            word_dict[CONSTANTS.KEY_SELECT_FILE],
        )

        # ファイル選択ダイアログ
        filei = tkinter.filedialog.askopenfilename(filetypes=fTyp, initialdir=exe_dir)

        # ファイル選択ダイアログでキャンセルした場合は処理を終了する(キャンセルした場合空文字が返る)
        if filei == "":
            return

        # 保存ダイアログの初期表示フォルダに入力ファイルのあるフォルダを指定
        plt.rcParams["savefig.directory"] = os.path.dirname(filei)

        ########################################## データをセットする
        df = pd.read_csv(filei)

    except Exception as e:
        output_error(word_dict, CONSTANTS.KEY_ERROR_IN_INPUT_FILE, e)
        return

    # データの取得処理
    try:
        X = get_valid_column(df, ["Time(s)", "Time (s)"], word_dict)
        Y = get_valid_column(df, ["Accel(G)", "Accel (G)"], word_dict)

        # Ref列が存在するかどうかをフラグで管理
        ref_exists = True
        try:
            Z = get_valid_column(df, ["Ref(mm)", "Ref (mm)"], word_dict)
        except ColumnNameNotFoundError:
            # - Ref列が見つからない場合のエラーをキャッチしてRef列存在フラグをFalseに設定
            ref_exists = False
            Z = pd.Series()  # Pylance警告対策

        x = X.to_numpy()
        y = Y.to_numpy()
        z = Z.to_numpy()

    except Exception as e:
        output_error(word_dict, CONSTANTS.KEY_ERROR_IN_GET_DATA, e)
        return

    # グラフに表示するデータの計算処理
    try:
        # 時間データの1次元化
        time_x = np.ravel(x)
        # データサイズ
        d_lengs = len(time_x)
        xx = np.ravel(np.zeros(d_lengs))

        # 時間データについて初期値でオフセットを除く
        for i in range(1, d_lengs + 1):
            xx[i - 1] = time_x[i - 1] - time_x[0]

        # データ時間分解能
        tr = xx[1] - xx[0]

        ########################################### 加速度を積分して変位を計算する
        # 9.80665*1000*0.01*0.01
        at = y * 98.0665 * tr
        # reshape to 1 row and 1001 column
        a = at.reshape(1, d_lengs)
        # 累積和
        v = a.cumsum(axis=1)
        # 累積和  1 row and 1001 column
        u = v.cumsum(axis=1)

        # vd = np.ravel(v)

        ########################################### 積分変位のLPF処理   積分変位の移動平均処理
        # 変位の1次元化
        ud = np.ravel(u)

        # 41 中央移動平均区間
        n1: int = 20
        # 1次元配列
        m = np.ones(n1 * 2 + 1) / (n1 * 2 + 1)
        # 1次元配列で計算
        mut = np.convolve(ud, m, mode="same")
        # 33 中央移動平均区間
        n2: int = 16
        m = np.ones(n2 * 2 + 1) / (n2 * 2 + 1)
        mut = np.convolve(mut, m, mode="same")
        # 27 中央移動平均区間
        n3: int = 13
        m = np.ones(n3 * 2 + 1) / (n3 * 2 + 1)
        mut = np.convolve(mut, m, mode="same")
        # 23 中央移動平均区間
        n4: int = 11
        m = np.ones(n4 * 2 + 1) / (n4 * 2 + 1)
        mut = np.convolve(mut, m, mode="same")

        # 移動平均で歪む範囲
        nn: int = n1 + n2 + n3 + n4

        ########################################### 近似区間を切り出しセット
        # 近似区間長の片側の指定
        n: int = 20
        # 41 近似区間長
        k: int = n * 2 + 1
        # ポインタ
        p: int = nn + n

        # 2次係数の1次元配列の作成 np.empty(n)の方が高速ただし0とは限らない
        ca = np.ravel(np.zeros(d_lengs))

        ########################################### 近似区間をシフトし2次近似係数を算出する
        # ポインタによる近似区間の指定
        for i in range(p, d_lengs - p):
            # 時間データの1次元配列から切出す
            rd = xx[i - n : i + n + 1]
            # LPF処理変位データの1次元配列から切出す
            ru = mut[i - n : i + n + 1]

            st = np.sum(rd)
            rd2 = rd * rd
            st2 = np.sum(rd2)
            rd3 = rd2 * rd
            st3 = np.sum(rd3)
            rd4 = rd3 * rd
            st4 = np.sum(rd4)
            su = np.sum(ru)
            sut = np.sum(ru * rd)
            sut2 = np.sum(ru * rd2)

            c_a = (
                -1
                * (
                    (sut2 / st2 - su / k) / (st3 / st2 - st / k)
                    - (sut / st - su / k) / (st2 / st - st / k)
                )
                / (
                    (st4 / st2 - st2 / k) / (st3 / st2 - st / k)
                    - (st3 / st - st2 / k) / (st2 / st - st / k)
                )
            )
            # 時系列2次係数の配列
            ca[i] = c_a

        # 初期区間の挿入
        ca1 = ca[p]
        for i in range(1, p + 1):
            ca[i - 1] = ca1

        # 後区間の挿入
        ca2 = ca[1001 - p - 1]
        for i in range(d_lengs - p, d_lengs):
            ca[i] = ca2

        # オフセットした2次係数の経時変化
        cao = ca - ca[0]

        ############################################ 進入出を検知する
        # 2次係数波形を2値化する閾値➝ピーク範囲の抽出
        caoth = max(cao) / 3

        # 2値化
        cao2 = np.ravel(np.zeros(d_lengs))
        for i in range(1, d_lengs + 1):
            if cao[i - 1] > caoth:
                cao2[i - 1] = 1
            else:
                cao2[i - 1] = 0

        cao2d: np.ndarray = np.ravel(np.zeros(d_lengs))
        for i in range(1, d_lengs):
            if cao2[i] - cao2[i - 1] == 1:
                cao2d[i] = 1

        cao2s: np.ndarray = np.cumsum(cao2d)
        # 抽出した区間の数
        cao2s_n: np.float64 = max(cao2s)

        # 進入ポイント
        caoi = np.ravel(np.zeros(d_lengs))
        for i in range(1, d_lengs + 1):
            if cao2s[i - 1] == 1:
                caoi[i - 1] = cao[i - 1]
            else:
                caoi[i - 1] = 0

        caoi_n = max(caoi)
        caoip = 0
        for i in range(1, d_lengs):
            if caoi[i] == caoi_n:
                caoip = i

        # 進出ポイント
        caoo = np.ravel(np.zeros(d_lengs))
        for i in range(1, d_lengs):
            if cao2s[i - 1] == cao2s_n:
                caoo[i - 1] = cao[i - 1]
            else:
                caoo[i - 1] = 0

        caoo_n = max(caoo)
        caoop = 0
        for i in range(1, d_lengs):
            if caoo[i] == caoo_n:
                caoop = i

        ##################################### リファレンス変位と積分変位振幅を合わせるため進入出時を微調整する。
        # 進入時点
        tin = caoip
        # 進出時点
        tout = caoop

        print(
            "Time of entering = ",
            time_x[tin],
            "s / Time of leaving = ",
            time_x[tout],
            "s",
        )

        # 進入時点を前方に移動する
        caoip = caoip - 20
        # 進出時点を後方に移動する
        caoop = caoop + 60

        ##################################### 進入出区間の2次多項式近似
        # 区間長の指定  進入区間の切出し区間
        dd = 100

        # 時間　前区間の切出し
        xi = xx[caoip - dd : caoip]
        # 変位　前区間の切出し
        udi = ud[caoip - dd : caoip]

        # 時間　後区間の切出し
        xo = xx[caoop : caoop + dd]
        # 変位　後区間の切出し
        udo = ud[caoop : caoop + dd]

        xio = np.ravel(np.zeros(200))
        udio = np.ravel(np.zeros(200))

        # 前と後の切出し区間の配列を結合
        for i in range(1, dd + 1):
            xio[i - 1] = xi[i - 1]
            udio[i - 1] = udi[i - 1]
            xio[i - 1 + dd] = xo[i - 1]
            udio[i - 1 + dd] = udo[i - 1]

        ############################################## 切出し区間を2次多項式近似し係数を求める
        k = dd + dd
        st = np.sum(xio)
        rd2 = xio * xio
        st2 = np.sum(rd2)
        rd3 = rd2 * xio
        st3 = np.sum(rd3)
        rd4 = rd3 * xio
        st4 = np.sum(rd4)
        su = np.sum(udio)
        sut = np.sum(udio * xio)
        sut2 = np.sum(udio * rd2)

        ############################################# 2次係数c_a 1次係数C_b 0次係数C_C
        c_a = (
            (sut2 / st2 - su / k) / (st3 / st2 - st / k)
            - (sut / st - su / k) / (st2 / st - st / k)
        ) / (
            (st4 / st2 - st2 / k) / (st3 / st2 - st / k)
            - (st3 / st - st2 / k) / (st2 / st - st / k)
        )
        c_b = (sut / st - su / k - (st3 / st - st2 / k) * c_a) / (st2 / st - st / k)
        c_c = (su - st * c_b - st2 * c_a) / k

        ############################################# 積分変位から2次多項式近似を除いて、推定変位を算出
        u_etm = np.ravel(np.zeros(d_lengs))
        for i in range(1, d_lengs + 1):
            u_etm[i - 1] = (
                ud[i - 1] - c_a * xx[i - 1] * xx[i - 1] - c_b * xx[i - 1] - c_c
            )

        ############################################# 推定変位を進入時刻変位で0変位にオフセット
        # u_etm = u_etm - u_etm[caoip-50]

        ############################################# 推定変位の進入出時刻前後区間の波形調整
        u_etm2 = np.ravel(np.zeros(d_lengs))

        for i in range(1, d_lengs + 1):
            if i - 1 < caoip - dd:
                u_etm2[i - 1] = u_etm[caoip - dd]
            else:
                u_etm2[i - 1] = u_etm[i - 1]

        for i in range(caoip - dd, d_lengs + 1):
            if i - 1 > caoop + dd:
                u_etm2[i - 1] = u_etm[caoop + dd]
            else:
                u_etm2[i - 1] = u_etm[i - 1]

        # ref変位の1次元化
        if ref_exists:
            zd = np.ravel(z)
        else:
            zd = np.ravel(0)  # Pylance 警告対策

        # caoth_ = np.ones(d_lengs) * caoth

        # 加速度の1次元化
        yy = np.ravel(y)

    except Exception as e:
        output_error(word_dict, CONSTANTS.KEY_ERROR_IN_CALC_DATA, e)
        return

    ######################################### グラフ表示

    def draw_1(xx, u_etm2):
        # 推定変位グラフ書式指定
        plt.title(c="black", label="Displacement")

        plt.ylabel("Displacement (mm)")
        plt.xlabel("Time (s)")

        # - 推定変位グラフの凡例用にラベルを加える
        plt.plot(xx, u_etm2, label="Calculated displacement", color="#1f77b4")
        # - 凡例をグラフの右下に枠なしで表示するよう設定
        plt.legend(loc="lower right", frameon=False)

    def draw_2(xx, zd):
        # ref変位グラフ書式指定
        plt.title(c="black", label="Displacement")

        plt.ylabel("Displacement (mm)")
        plt.xlabel("Time (s)")

        # - ref変位グラフの凡例用にラベルを加える
        plt.plot(xx, zd, label="Reference", color="#ff7f0e")
        # - 凡例をグラフの右下に枠なしで表示するよう設定
        plt.legend(loc="lower right", frameon=False)

    def draw_3(xx, cao):
        # 2次係数グラフ書式指定
        plt.title(c="black", label="2nd coefficient")
        plt.ylabel("coeff.  mm/s^2")
        plt.xlabel("time  s")
        plt.plot(xx, cao)

    def draw_4(xx, caoth_):
        # 閾値グラフ書式指定
        plt.title(c="black", label="2nd coefficient")
        plt.ylabel("coeff.  mm/s^2")
        plt.xlabel("time  s")
        plt.plot(xx, caoth_)

    def draw_5(xx, caoth_):
        plt.plot(xx[caoip], caoth_[caoip], marker="o")

    def draw_6(xx, caoth_):
        plt.plot(xx[caoop], caoth_[caoop], marker="o")

    def draw_7(xx, u_etm2):
        plt.plot(xx[caoip], u_etm2[caoip], marker="o", color="#2ca02c")

    def draw_8(xx, u_etm2):
        plt.plot(xx[caoop], u_etm2[caoop], marker="o", color="#d62728")

    def draw_9(xx, yy):
        plt.title(c="black", label="Accelerometer")

        plt.ylabel("Accelerometer (G)")
        plt.xlabel("Time (s)")

        plt.plot(
            xx,
            yy,
        )

    def draw_10(xx, ud):
        # 積分変位
        plt.title(c="black", label="Displacement")
        plt.ylabel("Displacement  mm")
        plt.xlabel("time  s")
        plt.plot(
            xx,
            ud,
        )

    try:
        root.quit()
        root.destroy()
        # グラフ表示範囲の大きさ
        plt.figure(f"{CONSTANTS.SOFTWARE_NAME} {CONSTANTS.VERSION}", figsize=(15, 8))

        # グラフウィンドウのアイコン設定
        fig_manager = plt.get_current_fig_manager()
        assert fig_manager  # Pylance 警告対策

        try:
            fig_manager.window.tk.call(
                "wm",
                "iconphoto",
                fig_manager.window._w,
                tkinter.PhotoImage(file=os.path.join(bundle_dir, "img", "icon.png")),
            )
        except tkinter.TclError as e:
            logger = logging.getLogger("epson_bdc")
            logger.error(e, exc_info=True)

        # 保存ダイアログで初期設定されているファイル名に「入力ファイル名.png」を指定
        # - 関数代入で mypy 警告が出るが、動作確認済みのため警告抑止する
        fig_manager.canvas.get_default_filename = (  # type: ignore
            lambda: f"{os.path.splitext(os.path.basename(filei))[0]}.png"
        )

        plt.subplots_adjust(wspace=0.2, hspace=0.4)

        # グラフウィンドウの下部に入出時刻を表示
        # - 現在のグラフの座標を取得
        fig = plt.gcf()
        tran = plt.gca().transData

        # - 表示する文字列群の設定 値は(文字, 色)のタプルで持たせる
        texts = [
            ("● ", "green"),
            (f"Time of entering = {time_x[tin]}s  ", "black"),
            ("● ", "red"),
            (f"Time of leaving = {time_x[tout]}s", "black"),
        ]

        # - グラフと文字列の幅から右下寄せの位置を決めるために、表示する文字列を描画した時の幅を算出
        temp = plt.text(0, 0, "".join(t[0] for t in texts))
        temp.draw(fig.canvas.get_renderer())
        ex = temp.get_window_extent()
        text_width = tran.inverted().transform_bbox(ex).width
        temp.remove()

        # - 文字列の表示を開始するx座標, 文字列の表示位置をどれだけ下にずらすかのy座標を設定
        x_text_start = 1.1 - text_width
        y_shift = -0.12

        # - 文字列を色付きで描画するための関数を定義
        def draw_color_text(figure, x, y, text, color, transform):
            draw_text = plt.text(x, y, text, color=color, transform=transform)
            draw_text.draw(figure.canvas.get_renderer())
            ex = draw_text.get_window_extent()
            # テキストの幅のドット数をポイント数に変換
            width = ex.width / figure.dpi * 72

            return transforms.offset_copy(
                draw_text._transform, x=width, units="points", fig=figure
            )

        # - 文字列をそれぞれの色で描画 tranは次の文字列を描画する起点のx座標の情報を持つ
        for t in texts:
            tran = draw_color_text(fig, x_text_start, y_shift, t[0], t[1], tran)

        # - 現在のグラフの軸情報を非表示(文字列だけを表示したいため)
        plt.gca().axis("off")

        # ロゴファイルを読み込みグラフウィンドウの右上に表示する
        img = Image.open(os.path.join(bundle_dir, "img", "epson_logo.png"))
        # - 画像の表示位置, 大きさ調整([左端のx座標, 下端のy座標, 幅, 高さ]の順)
        logo = fig.add_axes((0.87, 0.875, 0.125, 0.125))
        logo.imshow(img)
        # - 画像表示領域の軸情報を非表示(画像だけを表示したいため)
        logo.axis("off")

        # 表示するグラフの種類を絞る
        # - 右上, 左下のグラフを削除
        # - 左上, 右下のグラフを横いっぱいに表示

        # # グラフ描画位置の指定
        # plt.subplot(2, 2, 3)
        # # 2次係数
        # draw_3(xx, cao)
        # # 閾値
        # draw_4(xx, caoth_)
        # draw_5(xx, caoth_)
        # draw_6(xx, caoth_)
        # # グラフ描画位置の指定
        # plt.subplot(2, 2, 4)
        # # 推定変位
        # draw_1(xx, u_etm2)
        # # ref変位
        # draw_2(xx, zd)
        # # 進入時点
        # draw_7(xx, u_etm2)
        # # 進出時点
        # draw_8(xx, u_etm2)
        # plt.subplot(2, 2, 1)
        # draw_9(xx, yy)
        # plt.subplot(2, 2, 2)
        # draw_10(xx, ud)

        # グラフ描画位置の指定
        plt.subplot(2, 1, 2)
        # 推定変位
        draw_1(xx, u_etm2)

        # ref変位
        if ref_exists:
            draw_2(xx, zd)

        # 進入時点
        draw_7(xx, u_etm2)
        # 進出時点
        draw_8(xx, u_etm2)
        plt.subplot(2, 1, 1)
        draw_9(xx, yy)

        plt.show()

    except Exception as e:
        output_error(word_dict, CONSTANTS.KEY_ERROR_IN_DISP_GRAPH, e)
        return

    # 出力ファイルの書き出し処理
    try:
        ############################################### 計算結果の推定変位をファイルに出力

        # reshape to 1001 row and 1 column
        uout = u_etm2.reshape(d_lengs, 1)

        index2 = ["Displ.(mm)"]
        outd = pd.DataFrame(uout, columns=index2)
        index1 = ["Time(s)"]
        outt = pd.DataFrame(x, columns=index1)

        out_df = pd.concat([outt, outd], axis=1)
        # 出力ファイル名
        fileo = filei.replace(".csv", "_Displ.csv")

        # csvファイルに出力
        out_df.to_csv(fileo, index=False)

    except Exception as e:
        output_error(word_dict, CONSTANTS.KEY_ERROR_IN_OUTPUT_FILE, e)
        return
