"""
XRFデータフィッティングおよび解析アプリケーション。
このモジュールは、X線蛍光分析(XRF)データを読み込み、プロットし、
フィッティングやピーク探索などの解析機能を提供するGUIアプリケーションです。
データ入力にはプラグイン機構を採用しており、様々な形式のファイルに対応可能です。
:doc:`XRF_fit_usage`
"""
import os
import sys
import re
import numpy as np
from numpy import exp, log, log10, sqrt, sin, cos, tan
from importlib import import_module
from matplotlib import pyplot as plt
import tkinter as tk
from tklib.tkapplication import tkApplication
from tklib.tkutils import getarg, getintarg, getfloatarg, pint, pfloat, split_file_path, replace_path, print_line
from tklib.tkfile import tkFile
from tklib.tkparams import tkParams
from tklib.tkvariousdata import tkVariousData
from tklib.tkfilter import tkFilter
from tklib.tksci.tksci import Gaussian, Lorentzian, GaussLorentz
from tklib.tksci.tkmatrix import make_matrix1, make_matrix2, make_matrix3
from tklib.tkgraphic.tkplotevent import tkPlotEvent, RangeSelector
from tklib.tkgraphic.tkplot_pyplot import select_plt, tkPlot_pyplot
from tklib.tkgraphic.tkplot_tkinter import tkPlot_tkinter
from tklib.tkgui.tksimple_gui import tkWidgets, CustomDialog_with_config
#from tklib.tkgui.tksimple_gui import get_window_from_plt, add_widgets, update_variables, show_varibles, CustomDialog_with_config
ProgramName = "XRF fit"
#===================================
# Global variables
#===================================
markers = ['o', 's', '+', 'x', 'D', 'v', '^', '<', '>', '8', 'h', 'H']
colors = ['black', 'red', 'blue', 'darkgreen', 'darkorange', 'hotpink', 'lightgreen', 'cyan', 'yellow', 'magenta', 'chocolate',
'navy', 'slategray', 'olive' ]
figsize = (8, 6)
fontsize = 12
legend_fontsize = 8
#=============================
# Treat argments
#=============================
[ドキュメント]
def usage(app = None, cparams = None):
"""
アプリケーションの利用方法を表示します。
`app` オブジェクトに設定されている利用方法文字列をコンソールに出力します。
:param app: tkApplicationのインスタンス。
:type app: tkApplication
:param cparams: 設定パラメータを含むtkParamsのインスタンス。
:type cparams: tkParams
:returns: なし
"""
cparams = app.get_params()
for s in app.usage_str.split('\n'):
cmd = 'print({})'.format(s.rstrip())
eval(cmd)
[ドキュメント]
def initialize(app = None):
"""
アプリケーションの初期設定と引数定義を行います。
`tkParams` オブジェクトを生成し、デバッグレベル、プリントレベル、
デフォルトの入力ファイル名、およびコマンドライン引数を定義します。
引数には、モード、入力プラグインディレクトリ、入力プラグイン名、入力ファイルパスが含まれます。
:param app: tkApplicationのインスタンス。
:type app: tkApplication
:returns: 初期化された設定オブジェクト。
:rtype: tkParams
"""
argv, narg = app.get_argv()
app.cfg = tkParams()
cfg = app.cfg
cfg.debug = 0
cfg.print_level = 0
infile = "KI240409-1-IGZO.txt"
app.add_argument(opt = "--mode", type = "str", desc = 'mode=[plot_all|peak_search]', defval = 'plot_all', optional = True)
def_plugin_dir = os.path.join(os.environ["tkprog_X_path"], 'plugin', "filter")
app.add_argument(opt = "--input_plugin_dir", type = "str", var_name = 'input_plugin_dir', opt_str = "--input_plugin_dir=dir",
desc = 'input_plugin_dir', defval = def_plugin_dir, optional = False)
app.add_argument(opt = "--input_plugin_name", type = "str", var_name = 'input_plugin_name', opt_str = "--input_plugin_name=file_body",
desc = 'input_plugin_name', defval = 'xlsx2xlsx',
optional = False)
app.add_argument(opt = "--infile", type = "str", var_name = 'infile', opt_str = "--infile=path",
desc = 'input file path', defval = infile, optional = True)
return cfg
[ドキュメント]
def update_vars(app = None, cparams = None):
"""
コマンドライン引数をパースし、設定変数を更新します。
`app.read_args` を呼び出し、引数のチェックとデフォルト値の適用を行います。
引数解析中にエラーが発生した場合は、エラーメッセージを表示してアプリケーションを終了します。
:param app: tkApplicationのインスタンス。
:type app: tkApplication
:param cparams: 更新対象の設定パラメータを含むtkParamsのインスタンス。
:type cparams: tkParams
:returns: なし
"""
args_opt, args_idx, args_vars = app.read_args(vars = cparams, check_allowed_args = True, apply_default = True)
if args_opt is None:
error_no = args_idx
error_message = args_vars
app.terminate("\n\n"
+ "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+ f"! {error_message}\n"
+ "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
usage = app.usage)
[ドキュメント]
def read_file(path, app, cparams, modules, module_names):
"""
指定されたパスのファイルを読み込むための適切なモジュールを特定し、データを読み込みます。
提供されたモジュールリストを巡回し、各モジュールがファイルのタイプを
チェックして、適合するモジュールを見つけます。
その後、そのモジュールを使用してデータを読み込みます。
:param path: 読み込むファイルのパス。
:type path: str
:param app: tkApplicationのインスタンス。
:type app: tkApplication
:param cparams: 設定パラメータを含むtkParamsのインスタンス。
:type cparams: tkParams
:param modules: 読み込みモジュールのリスト。
:type modules: list
:param module_names: 読み込みモジュール名のリスト。
:type module_names: list
:returns: 読み込みに使用されたモジュール名、モジュールオブジェクト、および読み込まれたデータ。
適切なモジュールが見つからない場合、(None, None, None) を返します。
:rtype: tuple[str | None, object | None, dict | None]
"""
module = None
module_name = ""
for i in range(len(modules)):
name = module_names[i]
m = modules[i]
file_type = m.check_file_type(path, app = app, cparams = cparams)
print(f"try [{name}] for [{path}]: file_type={file_type}")
if file_type is not None and 'Error' not in file_type:
module = m
module_name = name
break
if module is None:
return None, None, None
inf = module.read_data(path, app = app, cparams = cparams)
return module_name, module, inf
[ドキュメント]
def plot(app, cparams, tkplt, fig, axes, inf_in):
"""
入力データ (`inf_in`) をMatplotlibとTkinterの組み合わせでプロットします。
`inf_in` からメタデータ、ラベル、データリストを取り出し、
各スペクトルをサブプロットに描画します。
軸ラベル、凡例なども設定し、グラフを視覚化します。
:param app: tkApplicationのインスタンス。
:type app: tkApplication
:param cparams: 設定パラメータを含むtkParamsのインスタンス。
:type cparams: tkParams
:param tkplt: tkPlot_pyplotまたはtkPlot_tkinterのインスタンス。プロット操作を提供します。
:type tkplt: tkPlot_pyplot | tkPlot_tkinter
:param fig: MatplotlibのFigureオブジェクト。
:type fig: matplotlib.figure.Figure
:param axes: MatplotlibのAxesオブジェクト、またはAxesオブジェクトの配列。
:type axes: matplotlib.axes.Axes | np.ndarray
:param inf_in: 読み込まれた入力ファイルの情報。辞書形式で、"meta", "labels", "data_list" などを含む。
:type inf_in: dict
:returns: なし
"""
inf_list = inf_in["meta"]
# sample = inf_in.get("sample_name", "")
labels = inf_in["labels"]
data_list = inf_in["data_list"]
nspectrum = len(data_list)
ndata = len(data_list[0])
# axesが単一のAxesオブジェクトの場合、リストに変換してループ処理に対応させる
if nspectrum == 1 and not isinstance(axes, (list, np.ndarray)):
axes = [axes]
for isp in range(nspectrum):
ax = axes[isp]
subinf = inf_list[isp]
x_list, y_list = data_list[isp]
xmin = x_list[0]
xmax = x_list[-1]
print(f" {subinf['range']}: {subinf['name']} 2Q=[{xmin}, {xmax}]: wl={subinf['_WL']} A Xtal={subinf['_Xtal']}")
# ax.set_title(f"{cparams.infile}")
ax.tick_params(labelsize = fontsize)
ax.plot(x_list, y_list, label = subinf['name'])
ax.set_xlabel(r'2$\theta$', fontsize = fontsize)
if isp == 0:
ax.set_ylabel('Intensity', fontsize = fontsize)
legend = ax.legend(fontsize = legend_fontsize)
legend.set_draggable(True)
tkplt.tight_layout()
# tkinter.mainloop()
[ドキュメント]
def plot_all(app, cparams):
"""
全てのスペクトルデータをロードし、Tkinter GUIと連携してプロットを表示します。
入力モジュールをロードし、`matplotlib` と `tkinter` を使用してグラフウィンドウを作成します。
ツールバーボタン(図の保存、再描画)、GUIウィジェット(エントリー、ボタン、リストボックスなど)
を追加し、データをプロットします。ユーザー操作に応じたコールバック関数も定義します。
:param app: tkApplicationのインスタンス。
:type app: tkApplication
:param cparams: 設定パラメータを含むtkParamsのインスタンス。
:type cparams: tkParams
:returns: なし
"""
print()
print(f"input_plugin: {cparams.input_plugin_dir}/{cparams.input_plugin_name}")
print(f"input file : {cparams.infile}")
inf_in = load_input_modules(app, cparams)
data_list = inf_in["data_list"]
nspectrum = len(data_list)
print()
print("plot")
tkplt, root, tkpyplot = select_plt(use_tkinter = 1, plt = plt, parent = None, title = "XRF fit")
fig, axes = tkplt.subplots(1, nspectrum, figsize = figsize, dpi = 100, tight_layout = False)
plot_event = tkPlotEvent(plt)
# plot_event.add_stop_button()
# plot_event.prepare_annotation()
# plot_event.prepare_popup_menu(fig, parent = root)
tkplt.create_window(fig)
tkplt.add_toolbar()
wg = tkWidgets(parent = tkplt.parent, plt = plt)
notebook = wg.add_tab()
page1_frame = wg.add_page(notebook, title = "main")
page2_frame = wg.add_page(notebook, title = "configure")
top_frame = tk.Frame(page1_frame) #tkplt.parent)
top_frame.pack(side = tk.TOP, expand = True, fill = "both")
left_frame = tk.Frame(top_frame)
left_frame.pack(side = tk.LEFT, anchor = "n", expand = True, fill = "x")
canvas_frame = tk.Frame(top_frame)
canvas = tkplt.add_canvas(fig, parent = canvas_frame)
canvas_frame.pack(side = tk.LEFT, expand = True, fill = "both")
def save_figure(fig, outfile = "figure.png"):
"""
現在の図をファイルに保存します。
:param fig: 保存するMatplotlibのFigureオブジェクト。
:type fig: matplotlib.figure.Figure
:param outfile: 保存するファイル名。デフォルトは"figure.png"。
:type outfile: str
:returns: なし
"""
print()
print(f"Save figure to [{outfile}]")
fig.savefig(outfile)
def redraw():
"""
グラフを再描画します。
Matplotlibの図とTkinterキャンバスを更新して、グラフを再描画します。
:returns: なし
"""
# self.ax.clear() # 既存のグラフをクリア
tkplt.draw()
canvas.draw()
tkplt.add_toolbar_button(text = "Save figure", command = lambda: save_figure(fig))
tkplt.add_toolbar_button(text = "Redraw", command = redraw)
wg.vars.update(Vg_min = 0, Vg_max = 10, Vd_min = 0, Vd_max = 20)
def on_click():
"""
GUIウィジェットのクリックイベントハンドラです。
ウィジェットの値を更新し、デバッグ目的でコンソールに出力します。
:returns: なし
"""
# show_varibles(config)
wg.update_variables()
print("clicked", wg.vars.Vg_min, wg.vars.Vg_max, wg.vars.Vd_min, wg.vars.Vd_max)
# button1 = tk.Button(left_frame, text = "test")#, command = command)
widgets = [
# [{"type": "widget", "widget": button1}],
[{"type": "label", "label": "Vg:", "width": None},
{"type": "entry","varname": "Vg_min", "vartype": "float", "width": 10, "expand": True, "fill": "x"},
{"type": "label", "label": "-", "width": None},
{"type": "entry","varname": "Vg_max", "vartype": "float", "width": 10, "expand": True, "fill": "x"},
{"type": "label", "label": " ", "width": None},
],
[{"type": "label", "label": "Vd:", "width": None},
{"type": "entry","varname": "Vd_min", "vartype": "float", "width": 10, "expand": True, "fill": "x"},
{"type": "label", "label": "-", "width": None},
{"type": "entry","varname": "Vd_max", "vartype": "float", "width": 10, "expand": True, "fill": "x"},
{"type": "label", "label": " ", "width": None},
],
[{"type": "button", "name": "smoothing", "text": "smoothing", "anchor": "w", "command": on_click}],
[{"type": "button", "name": "peak_search", "text": "peak search", "anchor": "w", "command": on_click}],
[{"type": "listbox", "name": "test", "text": "data", "anchor": "w",
"options": ["all", "b"], "sel": 0, "width": 20, "height": 5, "command": on_click}],
[{"type": "combobox", "name": "test", "text": "data", "anchor": "w",
"options": ["all", "b"], "sel": 0, "width": 20, "height": 5, "command": on_click}],
[{"type": "checkbox", "varname": "cb", "text": "data", "anchor": "w", "value": True}],
[{"type": "radiobutton", "varname": "rb", "anchor": "w",
"options": ["all", "b"], "sel": 0}],
[{"type": "label", "varname": "input_path", "label": "input"}],
[{"type": "path", "varname": "input_path", "label": "input", "value": "***",
"width": 30,
"title": "Choose input file", "command": None}],
]
widgets_frame, config_left_pain = wg.add_widgets(parent = left_frame, widgets = widgets, side = "top", expand = True, fill = "x")
plot(app, cparams, tkplt, fig, axes, inf_in)
tkplt.pause(0.1)
input("Press ENTER to terminate>>")
[ドキュメント]
def main():
"""
アプリケーションのエントリポイントです。
`tkApplication` を初期化し、設定をロードします。
コマンドライン引数から入力ファイルパスを特定し、ログファイルを設定します。
設定されたモード(例: `plot_all`)に基づいて適切な関数を呼び出します。
無効なモードが指定された場合、エラーメッセージを表示して終了します。
:returns: なし
"""
app = tkApplication()
cfg = initialize(app)
#--infile引数をチェックし、読み込みファイル名を最初に確定する
cfg.infile = app.check_arg('--infile', defval = None, vartype = 'str')
#cfg.infileからログファイル名を作り、console出力をredirectする
logfile = app.replace_path(None, template = ["{dirname}", "{filebody}-out.txt"])
logfile = app.replace_path(cfg.infile)
# logfile = app.replace_path(cparams.infile, template = ["{dirname}", "{filebody}-out.txt"])
app.redirect(heading = f"Open logfile [{logfile}]", targets = ["stdout", logfile], mode = 'w')
app.print_title(f"# {ProgramName}")
#起動時引数で与えられたパラメータをcfgに設定
app.update_vars(cfg,apply_default = True)
# cfg.modeで与えられた関数が定義されていれば、呼び出す
if cfg.mode in globals():
globals()[cfg.mode](app, cfg)
else:
app.terminate("Error in main: Invalide mode [{}]".format(cfg.mode), usage = app.usage, pause = True)
# app.terminate(pause = True)#usage = usage)
if __name__ == "__main__":
main()