#viwer.py
import tkinter as tk
from tkinter import *
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox
from tkinterdnd2 import *
from openpyxl import load_workbook
from openpyxl.drawing.image import Image as XLImage
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg,
NavigationToolbar2Tk
)
from pathlib import Path
import os
from matplotlib.widgets import SpanSelector
from signal_processing import *
from follder_loader import *
#フォントを設定
plt.rcParams["font.family"] = "Meiryo"
#---------------定数---------------
FREQ_DEFAULT = 240
LPF_DEFAULT = 3
AVG_START = 0.5
AVG_END = 22
#-----------------------------------
class WaveformViewer(TkinterDnD.Tk):
#==========================================
#初期化
#===========================================
# 初期化処理
def __init__(self):
super().__init__()
self.title("Waveform Viewer XYZ")
self.geometry("800x600")
self.protocol("WM_DELETE_WINDOW", self.on_close)
self.x = self.y = self.z = None
self.t_full = None
self.save_path = None
self.datasets = []
self._build_controls()
self._build_plot()
self.update_lpf_display()
self.current_button = 1
self.canvas.mpl_connect("button_press_event", self.on_mouse_press)
# self.canvas.get_tk_widget().bind("<Button-3>", lambda e: "break")
self.colors = {
"X": "tab:red",
"Y": "tab:green",
"Z": "tab:blue",
"synthesize": "tab:purple"
}
self.display_idx1 = tk.IntVar(value=0)
self.display_idx2 = tk.IntVar(value=0)
self.time_aligned = True
#確実に終了させる
def on_close(self):
self.quit()
self.destroy()
#==========================================
#GUI入力値取得
#===========================================
def get_freq(self):
return float(self.freq_entry.get())
# def get_lpf_cutoff(self):
# return float(self.lpf_entry.get())
def get_lpf_cutoff(self):
if self.use_auto_lpf.get():
freq = self.get_freq()
lpf = min(max(freq * 0.03, 1.0), 20.0)
else:
lpf = float(self.lpf_entry.get())
return lpf
drop_folders = drop_folders
def update_lpf_display(self):
if self.use_auto_lpf.get():
freq = self.get_freq()
lpf = min(max(freq * 0.03, 1.0), 20.0)
self.lpf_entry.config(state="normal")
self.lpf_entry.delete(0, tk.END)
self.lpf_entry.insert(0, f"{lpf:.2f}")
self.lpf_entry.config(state="disabled")
def toggle_lpf_mode(self):
if self.use_auto_lpf.get():
self.lpf_entry.config(state="disabled")
else:
self.lpf_entry.config(state="normal")
self.update_lpf_display()
self.plot_xyz()
self.update_analysis_plot()
self.update_grip_fft()
#==========================================
#GUI構築
#===========================================
def _build_controls(self):
frame1 = ttk.Frame(self)
frame1.pack(side="top", fill="x", pady=5)
frame2 = ttk.Frame(self)
frame2.pack(side="top", fill="x", pady=5)
ttk.Label(frame1, text="fs [Hz]").pack(side="left")
self.fs_entry = ttk.Entry(frame1, width=8)
self.fs_entry.insert(0, 1000)
self.fs_entry.config(state="disabled")
self.fs_entry.pack(side="left", padx=2)
ttk.Label(frame1, text="Demod Freq [Hz]").pack(side="left")
self.freq_entry = ttk.Entry(frame1, width=8)
self.freq_entry.insert(0, str(FREQ_DEFAULT))
self.freq_entry.pack(side="left", padx=2)
ttk.Label(frame1, text="LPF Cutoff [Hz]").pack(side="left")
self.lpf_entry = ttk.Entry(frame1, width=8)
self.lpf_entry.insert(0, str(LPF_DEFAULT))
self.lpf_entry.pack(side="left", padx=2)
ttk.Label(frame1, text="Data1").pack(side="left")
self.data1_combo = ttk.Combobox(
frame1,
width=15,
state="readonly"
)
self.data1_combo.pack(side="left", padx=2)
self.data1_combo.bind(
"<<ComboboxSelected>>",
lambda e: (
self.plot_xyz(),
self.update_analysis_plot(),
self.update_grip_fft(),
)
)
ttk.Label(frame1, text="Data2").pack(side="left")
self.data2_combo = ttk.Combobox(
frame1,
width=15,
state="readonly"
)
self.data2_combo.pack(side="left", padx=2)
self.data2_combo.bind(
"<<ComboboxSelected>>",
lambda e: (
self.plot_xyz(),
self.update_analysis_plot(),
self.update_grip_fft(),
)
)
#-------表示する軸----------
self.show_x = tk.BooleanVar(value=True)
self.show_y = tk.BooleanVar(value=True)
self.show_z = tk.BooleanVar(value=True)
self.show_synthesize = tk.BooleanVar(value=True)
self.show_unwrap = tk.BooleanVar(value=False)
ttk.Checkbutton(
frame1,
text="X",
variable=self.show_x,
command=lambda: (
self.plot_xyz(),
self.update_analysis_plot(),
self.update_grip_fft()
)
).pack(side="left")
ttk.Checkbutton(
frame1,
text="Y",
variable=self.show_y,
command=lambda: (
self.plot_xyz(),
self.update_analysis_plot(),
self.update_grip_fft()
)
).pack(side="left")
ttk.Checkbutton(
frame1,
text="Z",
variable=self.show_z,
command=lambda: (
self.plot_xyz(),
self.update_analysis_plot(),
self.update_grip_fft()
)
).pack(side="left")
ttk.Checkbutton(
frame1,
text="synthesize",
variable=self.show_synthesize,
command=lambda: (
self.plot_xyz(),
self.update_analysis_plot(),
self.update_grip_fft()
)
).pack(side="left")
ttk.Label(frame1,text=" | ").pack(side="left")
ttk.Checkbutton(
frame1,
text="unwrap",
variable=self.show_unwrap,
command=lambda: (
self.update_analysis_plot(),
)
).pack(side="left")
ttk.Label(frame1,text=" | ").pack(side="left")
self.show_peak_time = tk.BooleanVar(value=False)
ttk.Checkbutton(
frame1,
text="Peak Time",
variable=self.show_peak_time,
command=self.update_analysis_plot
).pack(side="left")
ttk.Label(frame1,text=" | ").pack(side="left")
self.use_auto_lpf = tk.BooleanVar(value=False)
ttk.Checkbutton(
frame1,
text="auto_lpf",
variable=self.use_auto_lpf,
command=self.toggle_lpf_mode
).pack(side="left")
if self.use_auto_lpf.get():
self.lpf_entry.config(state="disabled")
self.freq_entry.bind(
"<Return>",
lambda e: (
self.update_lpf_display(),
self.plot_xyz(),
self.update_analysis_plot()
)
)
self.lpf_entry.bind(
"<Return>",
lambda e: (
self.plot_xyz(),
self.update_analysis_plot()
)
)
ttk.Button(frame1, text="Select Excel File", command=self.select_excel_file).pack(side="left", padx=5)
ttk.Button(frame1, text="Save_file", command=self.save_file).pack(side="left", padx=5)
ttk.Button(frame1, text="Save_wave_pic", command=self.save_picture).pack(side="left", padx=5)
ttk.Button(frame1,text="Save_grip_Pic",command=self.save_grip_pic).pack(side="left", padx=5)
ttk.Label(frame2,text=" Data1 | ").pack(side="left")
ttk.Label(frame2, text="Free Start1").pack(side="left")
self.free_start1_entry = ttk.Entry(frame2, width=6)
self.free_start1_entry.insert(0, "0")
self.free_start1_entry.pack(side="left")
ttk.Label(frame2, text="Free End1").pack(side="left")
self.free_end1_entry = ttk.Entry(frame2, width=6)
self.free_end1_entry.insert(0, "5")
self.free_end1_entry.pack(side="left")
ttk.Label(frame2,text=" | ").pack(side="left")
ttk.Label(frame2, text="Grip Start1").pack(side="left")
self.grip_start1_entry = ttk.Entry(frame2, width=6)
self.grip_start1_entry.insert(0, "5")
self.grip_start1_entry.pack(side="left")
ttk.Label(frame2, text="Grip End1").pack(side="left")
self.grip_end1_entry = ttk.Entry(frame2, width=6)
self.grip_end1_entry.insert(0, "10")
self.grip_end1_entry.pack(side="left")
ttk.Label(frame2, text="", width=5).pack(side="left")
ttk.Label(frame2,text=" Data2 | ").pack(side="left")
ttk.Label(frame2, text="Free Start2").pack(side="left")
self.free_start2_entry = ttk.Entry(frame2, width=6)
self.free_start2_entry.insert(0, "0")
self.free_start2_entry.pack(side="left")
ttk.Label(frame2, text="Free End2").pack(side="left")
self.free_end2_entry = ttk.Entry(frame2, width=6)
self.free_end2_entry.insert(0, "5")
self.free_end2_entry.pack(side="left")
ttk.Label(frame2,text=" | ").pack(side="left")
ttk.Label(frame2, text="Grip Start2").pack(side="left")
self.grip_start2_entry = ttk.Entry(frame2, width=6)
self.grip_start2_entry.insert(0, "5")
self.grip_start2_entry.pack(side="left")
ttk.Label(frame2, text="Grip End2").pack(side="left")
self.grip_end2_entry = ttk.Entry(frame2, width=6)
self.grip_end2_entry.insert(0, "10")
self.grip_end2_entry.pack(side="left")
ttk.Button(frame2,text="Update Grip FFT",command=self.update_grip_fft).pack(side="left", padx=8)
ttk.Label(frame2, text="FFT Ymax").pack(side="left")
self.fft_ymax_entry = ttk.Entry(frame2, width=6)
self.fft_ymax_entry.insert(0, "")
self.fft_ymax_entry.pack(side="left")
ttk.Label(frame2, text="Amp Ymax").pack(side="left")
self.amp_ymax_entry = ttk.Entry(frame2, width=6)
self.amp_ymax_entry.insert(0, "")
self.amp_ymax_entry.pack(side="left")
self.fft_ymax_entry.bind(
"<Return>",
lambda e: (
self.plot_xyz(),
self.update_analysis_plot()
)
)
self.amp_ymax_entry.bind(
"<Return>",
lambda e: (
self.plot_xyz(),
self.update_analysis_plot()
)
)
ttk.Label(frame2, text="FFT Xmax").pack(side="left")
self.fft_xmax_entry = ttk.Entry(frame2, width=6)
self.fft_xmax_entry.insert(0, "")
self.fft_xmax_entry.pack(side="left")
self.fft_xmax_entry.bind(
"<Return>",
lambda e: (
self.plot_xyz(),
self.update_analysis_plot()
)
)
# ---- Drag & Drop ----
self.drop_label = tk.Label(
self,
text="Drag & Drop Folder Here",
relief="groove",
height=3
)
self.drop_label.pack(fill="x", padx=10, pady=5)
self.drop_label.drop_target_register(DND_FILES)
self.drop_label.dnd_bind("<<Drop>>", self.drop_folders)
entries = [
self.free_start1_entry,
self.free_end1_entry,
self.grip_start1_entry,
self.grip_end1_entry,
self.free_start2_entry,
self.free_end2_entry,
self.grip_start2_entry,
self.grip_end2_entry,
]
for e in entries:
e.bind("<Return>", lambda ev: self.update_span_visual())
def get_fft_xmax(self):
txt = self.fft_xmax_entry.get().strip()
try:
return float(txt)
except ValueError:
return None
def get_fft_ymax(self):
txt = self.fft_ymax_entry.get().strip()
try:
return float(txt)
except ValueError:
return None
def get_amp_ymax(self):
txt = self.amp_ymax_entry.get().strip()
try:
return float(txt)
except ValueError:
return None
def _build_plot(self):
# =========================================================
# Notebook
# =========================================================
self.notebook = ttk.Notebook(self)
self.notebook.pack(fill="both", expand=True)
# =========================================================
# Tab1 : Waveform
# =========================================================
self.wave_tab = ttk.Frame(self.notebook)
self.notebook.add(self.wave_tab, text="Waveform")
# =========================================================
# Tab2 : Analysis
# =========================================================
self.analysis_tab = ttk.Frame(self.notebook)
self.notebook.add(self.analysis_tab, text="Analysis")
# =========================================================
# Tab3 : Grip FFT
# =========================================================
self.grip_tab1 = ttk.Frame(self.notebook)
self.grip_tab2 = ttk.Frame(self.notebook)
self.notebook.add(self.grip_tab1, text="Grip Data1")
self.notebook.add(self.grip_tab2, text="Grip Data2")
# =========================================================
# Waveform TAB
# =========================================================
wave_toolbar_frame = ttk.Frame(self.wave_tab)
wave_toolbar_frame.pack(side="top", fill="x")
wave_canvas_frame = ttk.Frame(self.wave_tab)
wave_canvas_frame.pack(side="top", fill="both", expand=True)
self.fig = plt.Figure(figsize=(12, 8))
self.ax1 = self.fig.add_subplot(221)
self.ax2 = self.fig.add_subplot(223)
self.ax3 = self.fig.add_subplot(
222,
sharex=self.ax1,
sharey=self.ax1
)
self.ax4 = self.fig.add_subplot(
224,
sharex=self.ax2,
sharey=self.ax2
)
self.canvas = FigureCanvasTkAgg(
self.fig,
master=wave_canvas_frame
)
self.canvas.draw_idle()
self.canvas.get_tk_widget().pack(
fill="both",
expand=True
)
self.toolbar = NavigationToolbar2Tk(
self.canvas,
wave_toolbar_frame
)
self.toolbar.update()
# =========================================================
# Analysis TAB
# =========================================================
analysis_toolbar_frame = ttk.Frame(self.analysis_tab)
analysis_toolbar_frame.pack(side="top", fill="x")
analysis_canvas_frame = ttk.Frame(self.analysis_tab)
analysis_canvas_frame.pack(side="top", fill="both", expand=True)
self.analysis_fig = plt.Figure(figsize=(12, 8))
self.axa1 = self.analysis_fig.add_subplot(221)
self.axa2 = self.analysis_fig.add_subplot(223)
self.axa3 = self.analysis_fig.add_subplot(222,sharex=self.axa1,sharey=self.axa1)
self.axa4 = self.analysis_fig.add_subplot(224,sharex=self.axa2,sharey=self.axa2)
self.analysis_canvas = FigureCanvasTkAgg(
self.analysis_fig,
master=analysis_canvas_frame
)
self.analysis_canvas.draw_idle()
self.analysis_canvas.get_tk_widget().pack(
fill="both",
expand=True
)
self.analysis_toolbar = NavigationToolbar2Tk(
self.analysis_canvas,
analysis_toolbar_frame
)
self.analysis_toolbar.update()
# =========================================================
# Grip FFT TAB
# =========================================================
grip_tab1_toolbar_frame = ttk.Frame(self.grip_tab1)
grip_tab1_toolbar_frame.pack(side="top", fill="x")
# Data1用
self.label_grip1 = ttk.Label(self.grip_tab1, text="Data1", font=("Meiryo", 14))
self.label_grip1.pack(pady=5)
self.grip_fig1 = plt.Figure(figsize=(10,8))
self.grip_ax1_1 = self.grip_fig1.add_subplot(121)
self.grip_ax1_2 = self.grip_fig1.add_subplot(122,sharey=self.grip_ax1_1)
self.grip_canvas1 = FigureCanvasTkAgg(
self.grip_fig1,
master=self.grip_tab1
)
self.grip_canvas1.get_tk_widget().pack(fill="both", expand=True)
self.grip_toolbar1 = NavigationToolbar2Tk(
self.grip_canvas1,
grip_tab1_toolbar_frame
)
self.grip_toolbar1.update()
grip_tab2_toolbar_frame = ttk.Frame(self.grip_tab2)
grip_tab2_toolbar_frame.pack(side="top", fill="x")
# Data2用
self.label_grip2 = ttk.Label(self.grip_tab2, text="Data2", font=("Meiryo", 14))
self.label_grip2.pack(pady=5)
self.grip_fig2 = plt.Figure(figsize=(10,8))
self.grip_ax2_1 = self.grip_fig2.add_subplot(121)
self.grip_ax2_2 = self.grip_fig2.add_subplot(122,sharey=self.grip_ax2_1)
self.grip_canvas2 = FigureCanvasTkAgg(
self.grip_fig2,
master=self.grip_tab2
)
self.grip_canvas2.get_tk_widget().pack(fill="both", expand=True)
self.grip_toolbar2 = NavigationToolbar2Tk(
self.grip_canvas2,
grip_tab2_toolbar_frame
)
self.grip_toolbar2.update()
def select_excel_file(self):
path = filedialog.asksaveasfilename(
defaultextension=".xlsx",
filetypes=[("Excel files", "*.xlsx")]
)
if path:
self.excel_path = path
# 初回は新規作成
from openpyxl import Workbook
# Excel読み込み
if not os.path.exists(path):
wb = Workbook()
else:
wb = load_workbook(path)
freq = int(self.get_freq())
lpf = int(self.get_lpf_cutoff())
sheet_name = f"{freq}Hz_LPF{lpf}"
if sheet_name not in wb.sheetnames:
ws = wb.create_sheet(sheet_name)
else:
ws = wb[sheet_name]
ws.save(path)
def update_grip_fft(self):
if len(self.datasets) == 0:
return
idx1 = self.data1_combo.current()
idx2 = self.data2_combo.current()
dataset1 = self.datasets[idx1]
dataset2 = self.datasets[idx2]
self.label_grip1.config(text=f"Data1 : {dataset1['folder']}")
self.label_grip2.config(text=f"Data2 : {dataset2['folder']}")
# ---- Data1 ----
self._draw_grip(dataset1, self.grip_ax1_1, self.grip_ax1_2, mode="1")
self.grip_canvas1.draw_idle()
# ---- Data2 ----
self._draw_grip(dataset2, self.grip_ax2_1, self.grip_ax2_2, mode="2")
self.grip_canvas2.draw_idle()
def _draw_grip(self, dataset, ax1, ax2, mode="1"):
ax1.clear()
ax2.clear()
fs = dataset["fs"]
if mode == "1":
free_start = float(self.free_start1_entry.get())
free_end = float(self.free_end1_entry.get())
grip_start = float(self.grip_start1_entry.get())
grip_end = float(self.grip_end1_entry.get())
else:
free_start = float(self.free_start2_entry.get())
free_end = float(self.free_end2_entry.get())
grip_start = float(self.grip_start2_entry.get())
grip_end = float(self.grip_end2_entry.get())
axes = [
("X", dataset["x"]),
("Y", dataset["y"]),
("Z", dataset["z"]),
]
# ---- 各軸 ----
for name, sig in axes:
if name == "X" and not self.show_x.get():
continue
if name == "Y" and not self.show_y.get():
continue
if name == "Z" and not self.show_z.get():
continue
freq, amp = calc_fft(sig, fs, free_start, free_end)
if freq is not None:
peak_f, peak_a = find_fft_peak(freq, amp)
ax1.plot(freq, amp, label=f"{name} (peak {peak_f:.1f} Hz, {peak_a:.4f} G)", color=self.colors[name])
ax1.plot(peak_f,peak_a,"o",mfc="none",mec=self.colors[name],mew=2,ms=8)
freq, amp = calc_fft(sig, fs, grip_start, grip_end)
if freq is not None:
peak_f, peak_a = find_fft_peak(freq, amp)
ax2.plot(freq, amp, label=f"{name} (peak {peak_f:.1f} Hz, {peak_a:.4f} G)", color=self.colors[name])
ax2.plot(peak_f,peak_a,"o",mfc="none",mec=self.colors[name],mew=2,ms=8)
# -------------------
# synthesize(合成)
# -------------------
if self.show_synthesize.get():
# ---- free ----
freq, amp = synthesize_fft(
dataset["x"],
dataset["y"],
dataset["z"],
fs,
free_start,
free_end
)
if freq is not None:
peak_f, peak_a = find_fft_peak(freq, amp)
ax1.plot(
freq, amp,
label=f"syn (peak {peak_f:.1f} Hz, {peak_a:.4f} G)",
color=self.colors["synthesize"]
)
ax1.plot(
peak_f, peak_a,
"o",
mfc="none",
mec=self.colors["synthesize"],
mew=2,
ms=8
)
# ---- grip ----
freq, amp = synthesize_fft(
dataset["x"],
dataset["y"],
dataset["z"],
fs,
grip_start,
grip_end
)
if freq is not None:
peak_f, peak_a = find_fft_peak(freq, amp)
ax2.plot(
freq, amp,
label=f"syn (peak {peak_f:.1f} Hz, {peak_a:.4f} G)",
color=self.colors["synthesize"]
)
ax2.plot(
peak_f, peak_a,
"o",
mfc="none",
mec=self.colors["synthesize"],
mew=2,
ms=8
)
ax1.set_title("非把持")
ax2.set_title("把持")
ax1.set_xlabel("周波数[Hz]")
ax2.set_xlabel("周波数[Hz]")
ax1.set_ylabel("振幅[G]")
ax2.set_ylabel("振幅[G]")
if ax1.has_data():
ax1.legend(loc="best")
if ax2.has_data():
ax2.legend(loc="best")
# ------------------------
# Avg / RMS テキスト表示
# ------------------------
# 合成振幅を作る
freq = self.get_freq()
lpf = self.get_lpf_cutoff()
mag_x, _ = self.get_lockin(dataset, "X")
mag_y, _ = self.get_lockin(dataset, "Y")
mag_z, _ = self.get_lockin(dataset, "Z")
sig = np.sqrt(mag_x**2 + mag_y**2 + mag_z**2)
# 区間ごとに計算
free_stats = self.calc_interval_stats(sig, fs, free_start, free_end)
grip_stats = self.calc_interval_stats(sig, fs, grip_start, grip_end)
if free_stats is not None and grip_stats is not None:
free_mean, free_rms = free_stats
grip_mean, grip_rms = grip_stats
ax1.text(
0.12, 0.98,
f"Free Avg:{free_mean:.3f} RMS:{free_rms:.3f}",
transform=ax1.transAxes,
ha="left",
va="top",
fontsize=10
)
ax2.text(
0.12, 0.98,
f"Grip Avg:{grip_mean:.3f} RMS:{grip_rms:.3f}",
transform=ax2.transAxes,
ha="left",
va="top",
fontsize=10
)
fft_xmax = self.get_fft_xmax()
if fft_xmax is not None:
self.ax1.set_xlim(0, fft_xmax)
self.ax2.set_xlim(0, fft_xmax)
def calc_interval_stats(self, sig, fs, start, end):
s = int(start * fs)
e = int(end * fs)
seg = sig[s:e]
if len(seg) == 0:
return None
mean = np.mean(seg)
rms = np.sqrt(np.mean(seg**2))
return mean,rms
def draw_analysis_stats_text(self, ax, dataset):
fs = dataset["fs"]
# 固定範囲(定数)
start = AVG_START
end = AVG_END
freq = self.get_freq()
lpf = self.get_lpf_cutoff()
mag_x, _ = self.get_lockin(dataset, "X")
mag_y, _ = self.get_lockin(dataset, "Y")
mag_z, _ = self.get_lockin(dataset, "Z")
mag_syn = np.sqrt(mag_x**2 + mag_y**2 + mag_z**2)
# 各軸の統計
stats_x = self.calc_interval_stats(mag_x, fs, start, end)
stats_y = self.calc_interval_stats(mag_y, fs, start, end)
stats_z = self.calc_interval_stats(mag_z, fs, start, end)
stats_s = self.calc_interval_stats(mag_syn, fs, start, end)
if None in (stats_x, stats_y, stats_z, stats_s):
return
mean_x, rms_x = stats_x
mean_y, rms_y = stats_y
mean_z, rms_z = stats_z
mean_s, rms_s = stats_s
# 表示テキスト
text = (
f"[{start:.1f}-{end:.1f}s]\n"
f"X : Avg={mean_x:.3f} RMS={rms_x:.3f}\n"
f"Y : Avg={mean_y:.3f} RMS={rms_y:.3f}\n"
f"Z : Avg={mean_z:.3f} RMS={rms_z:.3f}\n"
f"SYN: Avg={mean_s:.3f} RMS={rms_s:.3f}"
)
ax.text(
-0.02, 1.05,
text,
transform=ax.transAxes,
ha="left",
va="bottom",
fontsize=10,
color="black"
)
#----------データ取り込み------------
def select_save_folder(self):
self.save_folder = filedialog.askdirectory()
def draw_analysis_dataset(self, dataset, ax_top, ax_bottom):
x = dataset["x"]
y = dataset["y"]
z = dataset["z"]
t = dataset["t"]
freq = self.get_freq()
lpf = self.get_lpf_cutoff()
mag_x, phase_x = self.get_lockin(dataset, "X")
mag_y, phase_y = self.get_lockin(dataset, "Y")
mag_z, phase_z = self.get_lockin(dataset, "Z")
if not self.show_unwrap.get():
phase_x = np.degrees(phase_x)
phase_y = np.degrees(phase_y)
phase_z = np.degrees(phase_z)
else:
phase_x = np.degrees(np.unwrap(phase_x))
phase_y = np.degrees(np.unwrap(phase_y))
phase_z = np.degrees(np.unwrap(phase_z))
synthesize = np.sqrt(mag_x**2 +mag_y**2 +mag_z**2)
fs = dataset["fs"]
ignore_sec = 1.0
start_idx = int(ignore_sec * fs)
candidates = [
("X", mag_x, self.colors["X"]),
("Y", mag_y, self.colors["Y"]),
("Z", mag_z, self.colors["Z"]),
("SYN", synthesize, self.colors["synthesize"])
]
best_step_idx = 0
best_diff = 0
step_color = "black"
step_axis = ""
for axis_name, sig, color in candidates:
smooth = pd.Series(sig).rolling(
int(fs * 0.2),
center=True,
min_periods=1
).mean()
diff = np.abs(np.diff(smooth))
local_idx = (
np.argmax(diff[start_idx:])
+ start_idx
)
local_diff = diff[local_idx]
if local_diff > best_diff:
best_diff = local_diff
best_step_idx = local_idx
step_color = color
step_axis = axis_name
step_idx = best_step_idx
step_t = t[step_idx]
# 段差位置を基準に
# -3.5s ~ -0.5s
# +0.5s ~ +3.5s
pre_start = max(
0,
step_idx - int(fs * 3.5)
)
pre_end = max(
0,
step_idx - int(fs * 0.5)
)
post_start = min(
len(synthesize),
step_idx + int(fs * 0.5)
)
post_end = min(
len(synthesize),
step_idx + int(fs * 3.5)
)
before = synthesize[
pre_start:pre_end
]
after = synthesize[
post_start:post_end
]
before_mean = np.mean(before)
after_mean = np.mean(after)
delta = after_mean - before_mean
ratio = (
delta / before_mean * 100
if before_mean != 0
else 0
)
# ------------------------
# Amplitude
# ------------------------
if self.show_x.get():
ax_top.plot(
t,
mag_x,
label="X",
color=self.colors["X"]
)
if self.show_y.get():
ax_top.plot(
t,
mag_y,
label="Y",
color=self.colors["Y"]
)
if self.show_z.get():
ax_top.plot(
t,
mag_z,
label="Z",
color=self.colors["Z"]
)
if self.show_synthesize.get():
ax_top.plot(
t,
synthesize,
label="syn",
color=self.colors["synthesize"]
)
if ax_top.has_data():
ax_top.legend(loc="best")
ax_top.set_title(f"振幅 : {dataset['folder']}", y=1.18)
ax_top.set_xlabel("時間 [s]")
ax_top.set_ylabel("振幅 [G]")
ax_top.axvline(
step_t,
color=step_color,
linestyle="--",
linewidth=1
)
ax_bottom.axvline(
step_t,
color=step_color,
linestyle="--",
linewidth=1
)
ax_top.text(
0.02,
0.92,
(
f"Step={step_t:.2f}s\n"
f"Before={before_mean:.3f}G\n"
f"After={after_mean:.3f}G\n"
f"Δ={delta:.3f}G\n"
f"{ratio:.1f}%"
),
transform=ax_top.transAxes,
va="top"
)
if self.show_peak_time.get():
peak_idx = np.argmax(synthesize)
peak_t = t[peak_idx]
peak_amp = synthesize[peak_idx]
ax_top.plot(
peak_t, peak_amp,
"o",
mfc="none",
mec=self.colors["synthesize"],
mew=2,
ms=8
)
ax_top.annotate(
f"{peak_t:.2f} s",
xy=(peak_t, peak_amp),
xytext=(10, 10),
textcoords="offset points",
arrowprops=dict(arrowstyle="->")
)
self.draw_analysis_stats_text(ax_top, dataset)
# ------------------------
# Phase
# ------------------------
if not self.time_aligned:
ax_bottom.text(
0.02,
0.98,
"Warning: time not aligned",
transform=ax_bottom.transAxes,
va="top",
color="red"
)
if self.show_x.get():
ax_bottom.plot(
t,
phase_x,
label="X",
color=self.colors["X"]
)
if self.show_y.get():
ax_bottom.plot(
t,
phase_y,
label="Y",
color=self.colors["Y"]
)
if self.show_z.get():
ax_bottom.plot(
t,
phase_z,
label="Z",
color=self.colors["Z"]
)
ax_bottom.set_title(
f"位相 : {dataset['folder']}"
)
ax_bottom.set_xlabel("時間 [s]")
ax_bottom.set_ylabel("位相 [deg]")
if ax_bottom.has_data():
ax_bottom.legend(loc="best")
amp_ymax = self.get_amp_ymax()
if amp_ymax is not None:
ax_top.set_ylim(0, amp_ymax)
def plot_xyz(self):
# ---------------------------
# データ数チェック
# ---------------------------
single_mode = len(self.datasets) == 1
if len(self.datasets) == 0:
return
# ---------------------------
# 選択データ取得
# ---------------------------
idx1 = self.data1_combo.current()
idx2 = self.data2_combo.current()
if idx1 < 0:
return
dataset1 = self.datasets[idx1]
if idx2 < 0:
dataset2 = dataset1
else:
dataset2 = self.datasets[idx2]
# ---------------------------
# clear
# ---------------------------
self.ax1.clear()
self.ax2.clear()
self.ax3.clear()
self.ax4.clear()
#-----------表示分岐----------
if single_mode:
self.ax3.set_visible(False)
self.ax4.set_visible(False)
else:
self.ax3.set_visible(True)
self.ax4.set_visible(True)
# ==================================================
# Data1
# ==================================================
raw_x = dataset1["raw_x"]
raw_y = dataset1["raw_y"]
raw_z = dataset1["raw_z"]
x = dataset1["x"]
y = dataset1["y"]
z = dataset1["z"]
t = dataset1["t"]
# ---- 時間波形 ----
if self.show_x.get():
self.ax1.plot(
t,
raw_x,
label="X",
color=self.colors["X"]
)
if self.show_y.get():
self.ax1.plot(
t,
raw_y,
label="Y",
color=self.colors["Y"]
)
if self.show_z.get():
self.ax1.plot(
t,
raw_z,
label="Z",
color=self.colors["Z"]
)
if self.show_synthesize.get():
self.ax1.plot(
t,
np.sqrt(raw_x**2 + raw_y**2 + raw_z**2),
label="syn",
color=self.colors["synthesize"]
)
self.ax1.set_title(f"時間波形 : {dataset1['folder']}", y=1.18)
self.ax1.set_xlabel("時間 [s]")
self.ax1.set_ylabel("加速度 [G]")
if self.ax1.has_data():
self.ax1.legend(
loc="upper left",
bbox_to_anchor=(1.18, 1),
borderaxespad=0
)
# ---- FFT ----
fft_freq_x1, fft_amp_x1 = dataset1["fft_x"]
fft_freq_y1, fft_amp_y1 = dataset1["fft_y"]
fft_freq_z1, fft_amp_z1 = dataset1["fft_z"]
fft_freq_synthesize, fft_amp_synthesize = dataset1["fft_syn"]
peak_freq_x1, peak_amp_x1 = find_fft_peak(
fft_freq_x1,
fft_amp_x1
)
peak_freq_y1, peak_amp_y1 = find_fft_peak(
fft_freq_y1,
fft_amp_y1
)
peak_freq_z1, peak_amp_z1 = find_fft_peak(
fft_freq_z1,
fft_amp_z1
)
peak_freq_syn1, peak_amp_syn1 = find_fft_peak(
fft_freq_synthesize,
fft_amp_synthesize
)
if self.show_x.get():
self.ax2.plot(fft_freq_x1, fft_amp_x1,label=f"X (peak {peak_freq_x1:.1f} Hz, {peak_amp_x1:.4f} G)",color=self.colors["X"])
if self.show_y.get():
self.ax2.plot(fft_freq_y1, fft_amp_y1,label=f"Y (peak {peak_freq_y1:.1f} Hz, {peak_amp_y1:.4f} G)",color=self.colors["Y"])
if self.show_z.get():
self.ax2.plot(fft_freq_z1, fft_amp_z1,label=f"Z (peak {peak_freq_z1:.1f} Hz, {peak_amp_z1:.4f} G)",color=self.colors["Z"])
if self.show_synthesize.get():
self.ax2.plot(fft_freq_synthesize, fft_amp_synthesize,label=f"syn (peak {peak_freq_syn1:.1f} Hz, {peak_amp_syn1:.4f} G)",color=self.colors["synthesize"])
if self.show_x.get():
self.ax2.plot(peak_freq_x1,peak_amp_x1,"o",mfc="none",mec=self.colors["X"],mew=2,ms=8)
if self.show_y.get():
self.ax2.plot(peak_freq_y1,peak_amp_y1,"o",mfc="none",mec=self.colors["Y"],mew=2,ms=8)
if self.show_z.get():
self.ax2.plot(peak_freq_z1,peak_amp_z1,"o",mfc="none",mec=self.colors["Z"],mew=2,ms=8)
if self.show_synthesize.get():
self.ax2.plot(peak_freq_syn1,peak_amp_syn1,"o",mfc="none",mec=self.colors["synthesize"],mew=2,ms=8)
self.ax2.set_title(f"FFT : {dataset1['folder']}")
self.ax2.set_xlabel("周波数 [Hz]")
self.ax2.set_ylabel("振幅 [G]")
if self.ax2.has_data():
self.ax2.legend(loc="best")
fft_ymax = self.get_fft_ymax()
if fft_ymax is not None:
self.ax2.set_ylim(0, fft_ymax)
# ==================================================
# Data2
# ==================================================
raw_x = dataset2["raw_x"]
raw_y = dataset2["raw_y"]
raw_z = dataset2["raw_z"]
x = dataset2["x"]
y = dataset2["y"]
z = dataset2["z"]
t = dataset2["t"]
# ---- 時間波形 ----
if self.show_x.get():
self.ax3.plot(
t,
raw_x,
color=self.colors["X"],
)
if self.show_y.get():
self.ax3.plot(
t,
raw_y,
color=self.colors["Y"],
)
if self.show_z.get():
self.ax3.plot(
t,
raw_z,
color=self.colors["Z"],
)
if self.show_synthesize.get():
self.ax3.plot(
t,
np.sqrt(raw_x**2 + raw_y**2 + raw_z**2),
label="syn",
color=self.colors["synthesize"]
)
self.ax3.set_title(f"時間波形 : {dataset2['folder']}", y=1.18)
self.ax3.set_xlabel("時間 [s]")
self.ax3.set_ylabel("加速度 [G]")
# ---- FFT ----
fft_freq_x2, fft_amp_x2 = dataset2["fft_x"]
fft_freq_y2, fft_amp_y2 = dataset2["fft_y"]
fft_freq_z2, fft_amp_z2 = dataset2["fft_z"]
fft_freq_syn2, fft_amp_syn2 = dataset2["fft_syn"]
peak_freq_x2, peak_amp_x2 = find_fft_peak(
fft_freq_x2,
fft_amp_x2
)
peak_freq_y2, peak_amp_y2 = find_fft_peak(
fft_freq_y2,
fft_amp_y2
)
peak_freq_z2, peak_amp_z2 = find_fft_peak(
fft_freq_z2,
fft_amp_z2
)
peak_freq_syn2, peak_amp_syn2 = find_fft_peak(
fft_freq_syn2,
fft_amp_syn2
)
if self.show_x.get():
self.ax4.plot(fft_freq_x2, fft_amp_x2,label=f"X (peak {peak_freq_x2:.1f} Hz, {peak_amp_x2:.4f} G)",color=self.colors["X"])
if self.show_y.get():
self.ax4.plot(fft_freq_y2, fft_amp_y2,label=f"Y (peak {peak_freq_y2:.1f} Hz, {peak_amp_y2:.4f} G)",color=self.colors["Y"])
if self.show_z.get():
self.ax4.plot(fft_freq_z2, fft_amp_z2,label=f"Z (peak {peak_freq_z2:.1f} Hz, {peak_amp_z2:.4f} G)",color=self.colors["Z"])
if self.show_synthesize.get():
self.ax4.plot(fft_freq_syn2, fft_amp_syn2,label=f"syn (peak {peak_freq_syn2:.1f} Hz, {peak_amp_syn2:.4f} G)",color=self.colors["synthesize"])
#---------ピーク位置--------
if self.show_x.get():
self.ax4.plot(peak_freq_x2,peak_amp_x2,"o",mfc="none",mec=self.colors["X"],mew=2,ms=8)
if self.show_y.get():
self.ax4.plot(peak_freq_y2,peak_amp_y2,"o",mfc="none",mec=self.colors["Y"],mew=2,ms=8)
if self.show_z.get():
self.ax4.plot(peak_freq_z2,peak_amp_z2,"o",mfc="none",mec=self.colors["Z"],mew=2,ms=8)
if self.show_synthesize.get():
self.ax4.plot(peak_freq_syn2,peak_amp_syn2,"o",mfc="none",mec=self.colors["synthesize"],mew=2,ms=8)
self.ax4.set_title(f"FFT : {dataset2['folder']}")
self.ax4.set_xlabel("周波数 [Hz]")
self.ax4.set_ylabel("振幅 [G]")
if self.ax4.has_data():
self.ax4.legend(loc="best")
if fft_ymax is not None:
self.ax4.set_ylim(0, fft_ymax)
self.fig.subplots_adjust(wspace=0.35, hspace=0.3, top=0.85)
self.canvas.draw_idle()
self.update_span_visual() # ←追加
# Data1用
if hasattr(self, "span1"):
self.span1.disconnect_events()
del self.span1
self.span1 = SpanSelector(
self.ax1,
lambda xmin, xmax: self.onselect(xmin, xmax, target="data1"),
'horizontal',
useblit=True,
interactive=False,
button=[1, 3],
props=dict(alpha=0.3, facecolor="green")
)
# Data2用
if hasattr(self, "span2"):
self.span2.disconnect_events()
del self.span2
self.span2 = SpanSelector(
self.ax3,
lambda xmin, xmax: self.onselect(xmin, xmax, target="data2"),
'horizontal',
useblit=True,
interactive=False,
button=[1, 3],
props=dict(alpha=0.3, facecolor="green")
)
fft_xmax = self.get_fft_xmax()
if fft_xmax is not None:
self.ax2.set_xlim(0, fft_xmax)
self.ax4.set_xlim(0, fft_xmax)
def get_lockin(self, dataset, axis):
freq = self.get_freq()
lpf = self.get_lpf_cutoff()
key = (axis, freq, lpf)
if key not in dataset["lockin_cache"]:
sig = dataset[axis.lower()]
dataset["lockin_cache"][key] = quadrature(
sig,
freq,
dataset["fs"],
lpf
)
return dataset["lockin_cache"][key]
def update_span_visual(self):
# ---- Data1 ----
# 既存の塗りつぶしを消す
for p in list(self.ax1.patches):
if hasattr(p, "_from_span_visual"):
p.remove()
fs = float(self.free_start1_entry.get())
fe = float(self.free_end1_entry.get())
gs = float(self.grip_start1_entry.get())
ge = float(self.grip_end1_entry.get())
p1 = self.ax1.axvspan(fs, fe, color="red", alpha=0.1)
p1._from_span_visual = True
p2 = self.ax1.axvspan(gs, ge, color="blue", alpha=0.1)
p2._from_span_visual = True
# ---- Data2 ----
for p in list(self.ax3.patches):
if hasattr(p, "_from_span_visual"):
p.remove()
fs2 = float(self.free_start2_entry.get())
fe2 = float(self.free_end2_entry.get())
gs2 = float(self.grip_start2_entry.get())
ge2 = float(self.grip_end2_entry.get())
p3 = self.ax3.axvspan(fs2, fe2, color="red", alpha=0.1)
p3._from_span_visual = True
p4 = self.ax3.axvspan(gs2, ge2, color="blue", alpha=0.1)
p4._from_span_visual = True
self.canvas.draw_idle()
def onselect(self, xmin, xmax, target="data1"):
is_grip = (self.current_button == 3) # 右クリック
if target == "data1":
if not is_grip:
# Free
self.free_start1_entry.delete(0, tk.END)
self.free_start1_entry.insert(0, f"{xmin:.2f}")
self.free_end1_entry.delete(0, tk.END)
self.free_end1_entry.insert(0, f"{xmax:.2f}")
else:
# Grip
self.grip_start1_entry.delete(0, tk.END)
self.grip_start1_entry.insert(0, f"{xmin:.2f}")
self.grip_end1_entry.delete(0, tk.END)
self.grip_end1_entry.insert(0, f"{xmax:.2f}")
elif target == "data2":
if not is_grip:
self.free_start2_entry.delete(0, tk.END)
self.free_start2_entry.insert(0, f"{xmin:.2f}")
self.free_end2_entry.delete(0, tk.END)
self.free_end2_entry.insert(0, f"{xmax:.2f}")
else:
self.grip_start2_entry.delete(0, tk.END)
self.grip_start2_entry.insert(0, f"{xmin:.2f}")
self.grip_end2_entry.delete(0, tk.END)
self.grip_end2_entry.insert(0, f"{xmax:.2f}")
self.update_grip_fft()
self.update_span_visual()
self.plot_xyz()
# ---------------------- 解析共通 ----------------------
def update_analysis_plot(self):
single_mode = len(self.datasets) == 1
if len(self.datasets) == 0:
return
idx1 = self.data1_combo.current()
idx2 = self.data2_combo.current()
if idx1 < 0:
return
dataset1 = self.datasets[idx1]
if idx2 < 0:
dataset2 = dataset1
else:
dataset2 = self.datasets[idx2]
# clear
self.axa1.clear()
self.axa2.clear()
self.axa3.clear()
self.axa4.clear()
#-----------表示分岐------------
if single_mode:
self.axa3.set_visible(False)
self.axa4.set_visible(False)
else:
self.axa3.set_visible(True)
self.axa4.set_visible(True)
# Data1
self.draw_analysis_dataset(
dataset1,
self.axa1,
self.axa2
)
# Data2
self.draw_analysis_dataset(
dataset2,
self.axa3,
self.axa4
)
self.analysis_fig.subplots_adjust(wspace=0.35,hspace=0.3, top=0.85)
self.analysis_canvas.draw_idle()
self.analysis_ylim = self.axa1.get_ylim()
def on_mouse_press(self, event):
if event.button is not None:
self.current_button = event.button
def save_file(self):
if len(self.datasets) == 0:
return
for dataset in self.datasets:
foldername = dataset["folder"]
download_folder = str(Path.home() / "Downloads")
savepath = os.path.join(
download_folder,
foldername + ".xlsx"
)
x = dataset["x"]
y = dataset["y"]
z = dataset["z"]
raw_x = dataset["raw_x"]
raw_y = dataset["raw_y"]
raw_z = dataset["raw_z"]
t_full = dataset["t"]
fs = dataset["fs"]
# ロックイン
mag_x, phase_x = self.get_lockin(dataset, "X")
mag_y, phase_y = self.get_lockin(dataset, "Y")
mag_z, phase_z = self.get_lockin(dataset, "Z")
phase_x = np.degrees(phase_x)
phase_y = np.degrees(phase_y)
phase_z = np.degrees(phase_z)
synthesize = np.sqrt(mag_x**2 + mag_y**2 + mag_z**2)
mag_x_scalar = 2 * np.sqrt(np.mean(mag_x**2))
mag_y_scalar = 2 * np.sqrt(np.mean(mag_y**2))
mag_z_scalar = 2 * np.sqrt(np.mean(mag_z**2))
# ----------------------------
# FFT
# ----------------------------
freq_x, amp_x = calc_fft(x, fs)
_, amp_y = calc_fft(y, fs)
_, amp_z = calc_fft(z, fs)
# ----------------------------
# DataFrame 作成
# ----------------------------
df_time = pd.DataFrame({
"Time": t_full,
"raw_X": raw_x,
"raw_Y": raw_y,
"raw_Z": raw_z,
"X": x,
"Y": y,
"Z": z,
"Lockin_X": mag_x,
"Lockin_Y": mag_y,
"Lockin_Z": mag_z,
"Amp_synthesize": synthesize
})
df_fft = pd.DataFrame({
"freq": freq_x,
"amp_X": amp_x,
"amp_Y": amp_y,
"amp_Z": amp_z
})
df_summary = pd.DataFrame({
"Axis": ["X", "Y", "Z"],
"Lockin_mag": [mag_x_scalar, mag_y_scalar, mag_z_scalar]
})
# ----------------------------
# Excel保存(複数シート)
# ----------------------------
with pd.ExcelWriter(savepath) as writer:
df_time.to_excel(writer, sheet_name="TimeData", index=False)
df_fft.to_excel(writer, sheet_name="FFT", index=False)
df_summary.to_excel(writer, sheet_name="Summary", index=False)
messagebox.showinfo(
"Save Complete",
f"{len(self.datasets)} files saved"
)
# ---------------------- 画面の保存 ----------------------
def save_picture(self):
import uuid
if len(self.datasets) == 0:
return
if not hasattr(self, "excel_path"):
messagebox.showwarning("Warning", "Excelファイルを選択してください")
return
# Excel読み込み
wb = load_workbook(self.excel_path)
freq = int(self.get_freq())
lpf = int(self.get_lpf_cutoff())
base_name = f"{freq}Hz_LPF{lpf}"
sheet_name = base_name
i = 1
while sheet_name in wb.sheetnames:
sheet_name = f"{base_name}.{i}"
i += 1
ws = wb.create_sheet(sheet_name)
self.excel_row = 1
# --- 元の状態を保存 ---
org_x = self.show_x.get()
org_y = self.show_y.get()
org_z = self.show_z.get()
org_syn = self.show_synthesize.get()
org_idx1 = self.data1_combo.current()
org_idx2 = self.data2_combo.current()
# 既存画像数(位置決め用)
if not hasattr(self, "excel_row"):
self.excel_row = 1
row = self.excel_row
# 一時保存フォルダ
temp_dir = os.path.join(Path.home(), "AppData", "Local", "Temp")
os.makedirs(temp_dir, exist_ok=True)
pairs = []
for i in range(0, len(self.datasets), 2):
idx1 = i
idx2 = i+1 if i+1 < len(self.datasets) else i
pairs.append((idx1, idx2))
count = 0
for axis in ["SYN"]:
self.show_x.set(axis == "X")
self.show_y.set(axis == "Y")
self.show_z.set(axis == "Z")
self.show_synthesize.set(axis == "SYN")
for idx1, idx2 in pairs:
self.data1_combo.current(idx1)
self.data2_combo.current(idx2)
self.plot_xyz()
self.update_analysis_plot()
self.update_idletasks()
# ---------- 一時画像保存 ----------
wave_path = os.path.join(temp_dir, f"wave_{uuid.uuid4().hex}.png")
anl_path = os.path.join(temp_dir, f"analysis_{uuid.uuid4().hex}.png")
self.fig.savefig(wave_path, dpi=200)
self.analysis_fig.savefig(anl_path, dpi=200)
# ---------- Excelに貼る ----------
img1 = XLImage(wave_path)
img1.width = 1200
img1.height = 600
img2 = XLImage(anl_path)
img2.width = 1000
img2.height = 600
# 位置配置
ws.add_image(img1, f"A{row}")
ws.add_image(img2, f"A{row+35}")
row += 70
count += 1
wb.save(self.excel_path)
#元の状態に戻す
self.show_x.set(org_x)
self.show_y.set(org_y)
self.show_z.set(org_z)
self.show_synthesize.set(org_syn)
self.data1_combo.current(org_idx1)
self.data2_combo.current(org_idx2)
# 再描画
self.plot_xyz()
self.update_analysis_plot()
self.update_grip_fft()
messagebox.showinfo("Complete", "Excelに画像追加しました")
def save_grip_pic(self):
save_dir = str(Path.home() / "Downloads")
idx1 = self.data1_combo.current()
idx2 = self.data2_combo.current()
name1 = self.datasets[idx1]["folder"]
name2 = self.datasets[idx2]["folder"]
# 元の状態を保存
org_x = self.show_x.get()
org_y = self.show_y.get()
org_z = self.show_z.get()
org_syn = self.show_synthesize.get()
# 軸ループ
for axis in ["X", "Y", "Z", "SYN"]:
# 一旦全部OFF
self.show_x.set(False)
self.show_y.set(False)
self.show_z.set(False)
self.show_synthesize.set(False)
# 個別ON
if axis == "X":
self.show_x.set(True)
elif axis == "Y":
self.show_y.set(True)
elif axis == "Z":
self.show_z.set(True)
elif axis == "SYN":
self.show_synthesize.set(True)
# 再描画
self.update_grip_fft()
self.update_idletasks()
# 保存 Data1
self.grip_fig1.savefig(
os.path.join(save_dir, f"GripData1_{axis}_{name1}.png"),
dpi=300,
bbox_inches="tight"
)
# 保存 Data2
self.grip_fig2.savefig(
os.path.join(save_dir, f"GripData2_{axis}_{name2}.png"),
dpi=300,
bbox_inches="tight"
)
# 元に戻す
self.show_x.set(org_x)
self.show_y.set(org_y)
self.show_z.set(org_z)
self.show_synthesize.set(org_syn)
self.update_grip_fft()
messagebox.showinfo("Complete", "Grip FFT(軸別)保存完了")
コメント