438 lines
19 KiB
Python
438 lines
19 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
结果预览选项卡
|
||
"""
|
||
|
||
import customtkinter as ctk
|
||
import matplotlib.pyplot as plt
|
||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
||
import numpy as np
|
||
from pathlib import Path
|
||
|
||
class ResultPreviewTab(ctk.CTkFrame):
|
||
def __init__(self, parent):
|
||
super().__init__(parent)
|
||
|
||
self.grid_columnconfigure(0, weight=1)
|
||
self.grid_rowconfigure(0, weight=1)
|
||
|
||
viz_frame = ctk.CTkFrame(self, fg_color="#2b2b2b")
|
||
viz_frame.grid(row=0, column=0, sticky="nsew")
|
||
viz_frame.grid_columnconfigure(0, weight=1)
|
||
viz_frame.grid_rowconfigure(0, weight=0)
|
||
viz_frame.grid_rowconfigure(1, weight=1)
|
||
|
||
ctk.CTkLabel(
|
||
viz_frame,
|
||
text="可视化预览",
|
||
font=ctk.CTkFont(size=14, weight="bold"),
|
||
text_color="white"
|
||
).grid(row=0, column=0, sticky="w", padx=10, pady=10)
|
||
|
||
self.chart_frame = ctk.CTkFrame(viz_frame, fg_color="#2b2b2b")
|
||
self.chart_frame.grid(row=1, column=0, sticky="nsew", padx=5, pady=5)
|
||
self.chart_frame.grid_columnconfigure(0, weight=1)
|
||
self.chart_frame.grid_rowconfigure(0, weight=1)
|
||
|
||
self.fig = None
|
||
self.canvas = None
|
||
|
||
self.update_chart([])
|
||
|
||
def update_chart(self, events, detector_configs=None):
|
||
"""更新图表"""
|
||
# 清空图表框架
|
||
for widget in self.chart_frame.winfo_children():
|
||
widget.destroy()
|
||
|
||
if not events:
|
||
ctk.CTkLabel(
|
||
self.chart_frame,
|
||
text="暂无数据",
|
||
font=ctk.CTkFont(size=16),
|
||
text_color="white"
|
||
).pack(expand=True)
|
||
return
|
||
|
||
timestamps = {0: [], 1: [], 2: []}
|
||
energies = {0: [], 1: [], 2: []}
|
||
|
||
from core.encoder import BitFieldEncoder
|
||
encoder = BitFieldEncoder()
|
||
|
||
from core.simulator import SampleSpaceGenerator
|
||
sample_generator = SampleSpaceGenerator()
|
||
|
||
# 使用传入的探测器配置,如果没有则使用默认配置
|
||
if detector_configs is None:
|
||
detector_configs = {
|
||
"detector1": {
|
||
"energy_distribution": {"type": "normal", "mean": 1000.0, "std": 200.0},
|
||
"timestamp_distribution": {"type": "exponential", "scale": 50.0}
|
||
},
|
||
"detector2": {
|
||
"energy_distribution": {"type": "normal", "mean": 800.0, "std": 150.0},
|
||
"timestamp_distribution": {"type": "uniform", "low": 0.0, "high": 200.0}
|
||
},
|
||
"detector3": {
|
||
"energy_distribution": {"type": "gamma", "shape": 2.0, "scale": 500.0},
|
||
"timestamp_distribution": {"type": "normal", "mean": 100.0, "std": 30.0}
|
||
}
|
||
}
|
||
|
||
for event in events:
|
||
for signal in event:
|
||
if signal != 0:
|
||
decoded = encoder.decode(signal)
|
||
# 为每个探测器单独收集时间戳和能量
|
||
for i in range(3):
|
||
if decoded['detector_fired'][i]: # 如果该探测器点火
|
||
timestamps[i].append(decoded['timestamp'])
|
||
energies[i].append(decoded['energies'][i])
|
||
|
||
# 增大字体大小以匹配左侧面板(再大两号)
|
||
label_fontsize = 10
|
||
title_fontsize = 10
|
||
tick_fontsize = 10
|
||
legend_fontsize = 10
|
||
|
||
# 使用固定大小,确保每次更新图表大小一致
|
||
self.fig = plt.figure(figsize=(16, 12), dpi=100)
|
||
self.fig.patch.set_facecolor('#2b2b2b')
|
||
|
||
colors = ['#ff6b6b', '#4ecdc4', '#45b7d1']
|
||
labels = ['Detector 1', 'Detector 2', 'Detector 3']
|
||
markers = ['o', 's', '^'] # 不同的标记:圆圈、方块、三角形
|
||
|
||
# 减小图表之间的间隔,增大图表大小
|
||
plt.subplots_adjust(hspace=0.4, wspace=0.25, left=0.06, right=0.98, top=0.98, bottom=0.06)
|
||
|
||
# 第1-3张图:探测器1-3的信号时间分布
|
||
for i in range(3):
|
||
ax = self.fig.add_subplot(3, 3, i+1)
|
||
if timestamps[i]:
|
||
ax.hist(timestamps[i], bins=20, alpha=0.7, color=colors[i])
|
||
ax.set_xlabel('Timestamp (us)', fontsize=label_fontsize)
|
||
ax.set_ylabel('Count', fontsize=label_fontsize)
|
||
ax.set_title(f'{labels[i]} - Signal Time Distribution', fontsize=title_fontsize, fontweight='bold')
|
||
ax.grid(True, alpha=0.3)
|
||
ax.set_facecolor('#1e1e1e')
|
||
ax.tick_params(colors='white', labelsize=tick_fontsize)
|
||
ax.xaxis.label.set_color('white')
|
||
ax.yaxis.label.set_color('white')
|
||
ax.title.set_color('white')
|
||
for spine in ax.spines.values():
|
||
spine.set_color('#444444')
|
||
|
||
# 第4-6张图:探测器1-3的信号能量分布
|
||
for i in range(3):
|
||
ax = self.fig.add_subplot(3, 3, i+4)
|
||
if energies[i]:
|
||
ax.hist(energies[i], bins=20, alpha=0.7, color=colors[i])
|
||
ax.set_xlabel('Energy (keV)', fontsize=label_fontsize)
|
||
ax.set_ylabel('Count', fontsize=label_fontsize)
|
||
ax.set_title(f'{labels[i]} - Signal Energy Distribution', fontsize=title_fontsize, fontweight='bold')
|
||
ax.grid(True, alpha=0.3)
|
||
ax.set_facecolor('#1e1e1e')
|
||
ax.tick_params(colors='white', labelsize=tick_fontsize)
|
||
ax.xaxis.label.set_color('white')
|
||
ax.yaxis.label.set_color('white')
|
||
ax.title.set_color('white')
|
||
for spine in ax.spines.values():
|
||
spine.set_color('#444444')
|
||
|
||
# 第7张图:3个探测器信号在时间-能量平面上的散点图(占据整行)
|
||
ax = self.fig.add_subplot(3, 1, 3)
|
||
for i in range(3):
|
||
if timestamps[i] and energies[i]:
|
||
ax.scatter(timestamps[i], energies[i],
|
||
c=colors[i], marker=markers[i],
|
||
s=30, alpha=0.6, label=labels[i])
|
||
ax.set_xlabel('Timestamp (us)', fontsize=label_fontsize)
|
||
ax.set_ylabel('Energy (keV)', fontsize=label_fontsize)
|
||
ax.set_title('Detector Signals in Time-Energy Space', fontsize=title_fontsize, fontweight='bold')
|
||
ax.legend(fontsize=legend_fontsize)
|
||
ax.grid(True, alpha=0.3)
|
||
ax.set_facecolor('#1e1e1e')
|
||
ax.tick_params(colors='white', labelsize=tick_fontsize)
|
||
ax.xaxis.label.set_color('white')
|
||
ax.yaxis.label.set_color('white')
|
||
ax.title.set_color('white')
|
||
for spine in ax.spines.values():
|
||
spine.set_color('#444444')
|
||
|
||
# # 第7-9张图:探测器1-3的样本空间分布(能量+时间,使用双Y轴)
|
||
# for i in range(3):
|
||
# ax = self.fig.add_subplot(3, 3, i+7)
|
||
# detector_key = f"detector{i+1}"
|
||
|
||
# # 使用generate_sample_space方法生成样本空间(一次生成能量和时间戳)
|
||
# detector_config = detector_configs[detector_key].copy()
|
||
# detector_config['name'] = detector_key
|
||
# sample_space = sample_generator.generate_sample_space(detector_config)
|
||
# energy_data = sample_space[:, 0] # 第一列是能量
|
||
# timestamp_data = sample_space[:, 1] # 第二列是时间戳
|
||
|
||
# # 创建双Y轴
|
||
# ax2 = ax.twinx()
|
||
|
||
# # 绘制能量分布(左侧Y轴,使用探测器颜色)
|
||
# ax.hist(energy_data, bins=50, alpha=0.6, color=colors[i], label='Energy (keV)')
|
||
# ax.set_xlabel('Energy (keV)', fontsize=label_fontsize)
|
||
# ax.set_ylabel('Energy Count', fontsize=label_fontsize, color=colors[i])
|
||
# ax.tick_params(axis='y', labelcolor=colors[i], colors='white', labelsize=tick_fontsize)
|
||
|
||
# # 绘制时间分布(右侧Y轴,白色)
|
||
# ax2.hist(timestamp_data, bins=50, alpha=0.6, color='white', label='Time (us)')
|
||
# ax2.set_ylabel('Time Count', fontsize=label_fontsize, color='white')
|
||
# ax2.tick_params(axis='y', labelcolor='white', colors='white', labelsize=tick_fontsize)
|
||
|
||
# ax.set_title(f'{labels[i]} - Sample Space Distribution', fontsize=title_fontsize, fontweight='bold')
|
||
# ax.grid(True, alpha=0.3)
|
||
# ax.set_facecolor('#1e1e1e')
|
||
# ax.tick_params(axis='x', colors='white', labelsize=tick_fontsize)
|
||
# ax.xaxis.label.set_color('white')
|
||
# ax.title.set_color('white')
|
||
# for spine in ax.spines.values():
|
||
# spine.set_color('#444444')
|
||
# for spine in ax2.spines.values():
|
||
# spine.set_color('#444444')
|
||
|
||
self.canvas = FigureCanvasTkAgg(self.fig, master=self.chart_frame)
|
||
self.canvas.draw()
|
||
self.canvas.get_tk_widget().pack(fill="both", expand=True)
|
||
|
||
toolbar = NavigationToolbar2Tk(self.canvas, self.chart_frame, pack_toolbar=False)
|
||
toolbar.update()
|
||
toolbar.pack(side="top", fill="x")
|
||
|
||
def update_chart_with_time_series(self, time_series, detector_configs=None):
|
||
"""使用时间序列数据更新图表"""
|
||
# 清空图表框架
|
||
for widget in self.chart_frame.winfo_children():
|
||
widget.destroy()
|
||
|
||
if not time_series:
|
||
ctk.CTkLabel(
|
||
self.chart_frame,
|
||
text="暂无时间序列数据",
|
||
font=ctk.CTkFont(size=16),
|
||
text_color="white"
|
||
).pack(expand=True)
|
||
return
|
||
|
||
# 获取理论分布类型
|
||
distribution_types = {0: "unknown", 1: "unknown", 2: "unknown"}
|
||
if detector_configs:
|
||
for i, det_name in enumerate(["detector1", "detector2", "detector3"]):
|
||
if det_name in detector_configs:
|
||
dist_config = detector_configs[det_name].get("timestamp_distribution", {})
|
||
dist_type = dist_config.get("type", "unknown")
|
||
distribution_types[i] = dist_type
|
||
|
||
# 准备时间序列数据
|
||
timestamps = {0: [], 1: [], 2: []}
|
||
energies = {0: [], 1: [], 2: []}
|
||
|
||
detector_keys = ["detector1", "detector2", "detector3"]
|
||
for det_idx, det_name in enumerate(detector_keys):
|
||
signals = time_series.get(det_name, [])
|
||
for signal in signals:
|
||
if len(signal) == 2:
|
||
timestamp, energy = signal
|
||
timestamps[det_idx].append(timestamp)
|
||
energies[det_idx].append(energy)
|
||
|
||
# 增大字体大小
|
||
label_fontsize = 10
|
||
title_fontsize = 10
|
||
tick_fontsize = 10
|
||
|
||
# 使用固定大小
|
||
self.fig = plt.figure(figsize=(16, 12), dpi=100)
|
||
self.fig.patch.set_facecolor('#2b2b2b')
|
||
|
||
colors = ['#e74c3c', '#2ecc71', '#3498db']
|
||
labels = ['Detector 1', 'Detector 2', 'Detector 3']
|
||
|
||
# 获取能量分布类型
|
||
energy_dist_types = {0: "unknown", 1: "unknown", 2: "unknown"}
|
||
if detector_configs:
|
||
for i, det_name in enumerate(["detector1", "detector2", "detector3"]):
|
||
if det_name in detector_configs:
|
||
dist_config = detector_configs[det_name].get("energy_distribution", {})
|
||
dist_type = dist_config.get("type", "unknown")
|
||
energy_dist_types[i] = dist_type
|
||
|
||
plt.subplots_adjust(hspace=0.5, wspace=0.4, left=0.06, right=0.94, top=0.96, bottom=0.06)
|
||
|
||
# 第1-3张图:各探测器的时间分布直方图
|
||
for i in range(3):
|
||
ax = self.fig.add_subplot(3, 3, i+1)
|
||
if timestamps[i]:
|
||
ax.hist(timestamps[i], bins=30, alpha=0.7, color=colors[i])
|
||
ax.set_xlabel('Time (us)', fontsize=label_fontsize)
|
||
ax.set_ylabel('Count', fontsize=label_fontsize)
|
||
total_count = len(timestamps[i])
|
||
ax.set_title(f'{labels[i]} - Time Distribution\n({distribution_types[i]}, n={total_count})',
|
||
fontsize=title_fontsize, fontweight='bold')
|
||
ax.grid(True, alpha=0.3)
|
||
ax.set_facecolor('#1e1e1e')
|
||
ax.tick_params(colors='white', labelsize=tick_fontsize)
|
||
ax.xaxis.label.set_color('white')
|
||
ax.yaxis.label.set_color('white')
|
||
ax.title.set_color('white')
|
||
for spine in ax.spines.values():
|
||
spine.set_color('#444444')
|
||
|
||
# 第4-6张图:各探测器的能量分布直方图(mV)
|
||
for i in range(3):
|
||
ax = self.fig.add_subplot(3, 3, i+4)
|
||
if energies[i]:
|
||
ax.hist(energies[i], bins=30, alpha=0.7, color=colors[i])
|
||
ax.set_xlabel('Energy (mV)', fontsize=label_fontsize)
|
||
ax.set_ylabel('Count', fontsize=label_fontsize)
|
||
total_count = len(energies[i])
|
||
avg_energy = sum(energies[i]) / total_count if total_count > 0 else 0
|
||
ax.set_title(f'{labels[i]} - Energy Distribution\n({energy_dist_types[i]}, n={total_count}, avg={avg_energy:.0f})',
|
||
fontsize=title_fontsize, fontweight='bold')
|
||
ax.grid(True, alpha=0.3)
|
||
ax.set_facecolor('#1e1e1e')
|
||
ax.tick_params(colors='white', labelsize=tick_fontsize)
|
||
ax.xaxis.label.set_color('white')
|
||
ax.yaxis.label.set_color('white')
|
||
ax.title.set_color('white')
|
||
for spine in ax.spines.values():
|
||
spine.set_color('#444444')
|
||
|
||
# 第7-9张图:各探测器的时间-能量散点图
|
||
for i in range(3):
|
||
ax = self.fig.add_subplot(3, 3, i+7)
|
||
if timestamps[i] and energies[i]:
|
||
ax.scatter(timestamps[i], energies[i], alpha=0.5, c=colors[i], s=10)
|
||
ax.set_xlabel('Time (us)', fontsize=label_fontsize)
|
||
ax.set_ylabel('Energy (mV)', fontsize=label_fontsize)
|
||
ax.set_title(f'{labels[i]} - Time vs Energy',
|
||
fontsize=title_fontsize, fontweight='bold')
|
||
ax.grid(True, alpha=0.3)
|
||
ax.set_facecolor('#1e1e1e')
|
||
ax.tick_params(colors='white', labelsize=tick_fontsize)
|
||
ax.xaxis.label.set_color('white')
|
||
ax.yaxis.label.set_color('white')
|
||
ax.title.set_color('white')
|
||
for spine in ax.spines.values():
|
||
spine.set_color('#444444')
|
||
|
||
self.canvas = FigureCanvasTkAgg(self.fig, master=self.chart_frame)
|
||
self.canvas.draw()
|
||
self.canvas.get_tk_widget().pack(fill="both", expand=True)
|
||
|
||
toolbar = NavigationToolbar2Tk(self.canvas, self.chart_frame, pack_toolbar=False)
|
||
toolbar.update()
|
||
toolbar.pack(side="top", fill="x")
|
||
|
||
def verify_files(self, base_filename):
|
||
"""验证输出文件"""
|
||
files = []
|
||
|
||
# 检查二进制文件
|
||
bin_file = f"{base_filename}.bin"
|
||
if Path(bin_file).exists():
|
||
size = Path(bin_file).stat().st_size
|
||
files.append({"name": bin_file, "size": size, "exists": True})
|
||
else:
|
||
files.append({"name": bin_file, "size": 0, "exists": False})
|
||
|
||
# 检查文本文件
|
||
txt_file = f"{base_filename}_v4.txt"
|
||
if Path(txt_file).exists():
|
||
size = Path(txt_file).stat().st_size
|
||
files.append({"name": txt_file, "size": size, "exists": True})
|
||
else:
|
||
files.append({"name": txt_file, "size": 0, "exists": False})
|
||
|
||
# 检查调试文本文件
|
||
debug_file = f"{base_filename}_debug.txt"
|
||
if Path(debug_file).exists():
|
||
size = Path(debug_file).stat().st_size
|
||
files.append({"name": debug_file, "size": size, "exists": True})
|
||
else:
|
||
files.append({"name": debug_file, "size": 0, "exists": False})
|
||
|
||
return files
|
||
|
||
def update_chart_with_sample_space(self, sample_spaces, detector_configs):
|
||
"""使用预生成的样本空间更新图表(仅显示样本空间分布)"""
|
||
# 清空图表框架
|
||
for widget in self.chart_frame.winfo_children():
|
||
widget.destroy()
|
||
|
||
if not sample_spaces:
|
||
ctk.CTkLabel(
|
||
self.chart_frame,
|
||
text="暂无样本空间数据",
|
||
font=ctk.CTkFont(size=16),
|
||
text_color="white"
|
||
).pack(expand=True)
|
||
return
|
||
|
||
# 增大字体大小
|
||
label_fontsize = 10
|
||
title_fontsize = 10
|
||
tick_fontsize = 10
|
||
|
||
# 使用固定大小
|
||
self.fig = plt.figure(figsize=(16, 12), dpi=100)
|
||
self.fig.patch.set_facecolor('#2b2b2b')
|
||
|
||
colors = ['#e74c3c', '#2ecc71', '#3498db']
|
||
labels = ['Detector 1', 'Detector 2', 'Detector 3']
|
||
|
||
plt.subplots_adjust(hspace=0.4, wspace=0.4, left=0.06, right=0.94, top=0.98, bottom=0.06)
|
||
|
||
# 显示3个探测器的样本空间分布(使用双Y轴)
|
||
for i in range(3):
|
||
ax = self.fig.add_subplot(3, 1, i+1)
|
||
detector_key = f"detector{i+1}"
|
||
|
||
if detector_key not in sample_spaces:
|
||
continue
|
||
|
||
sample_space = sample_spaces[detector_key]
|
||
energy_data = sample_space[:, 0] # 第一列是能量
|
||
timestamp_data = sample_space[:, 1] # 第二列是时间戳
|
||
|
||
# 创建双Y轴
|
||
ax2 = ax.twinx()
|
||
|
||
# 绘制能量分布(左侧Y轴,使用探测器颜色)
|
||
ax.hist(energy_data, bins=50, alpha=0.6, color=colors[i], label='Energy (keV)')
|
||
ax.set_xlabel('Energy (keV)', fontsize=label_fontsize)
|
||
ax.set_ylabel('Energy Count', fontsize=label_fontsize, color=colors[i])
|
||
ax.tick_params(axis='y', labelcolor=colors[i], colors='white', labelsize=tick_fontsize)
|
||
|
||
# 绘制时间分布(右侧Y轴,白色)
|
||
ax2.hist(timestamp_data, bins=50, alpha=0.6, color='white', label='Time (us)')
|
||
ax2.set_ylabel('Time Count', fontsize=label_fontsize, color='white')
|
||
ax2.tick_params(axis='y', labelcolor='white', colors='white', labelsize=tick_fontsize)
|
||
|
||
ax.set_title(f'{labels[i]} - Sample Space Distribution (n={len(energy_data)})',
|
||
fontsize=title_fontsize, fontweight='bold')
|
||
ax.grid(True, alpha=0.3)
|
||
ax.set_facecolor('#1e1e1e')
|
||
ax.tick_params(axis='x', colors='white', labelsize=tick_fontsize)
|
||
ax.xaxis.label.set_color('white')
|
||
ax.title.set_color('white')
|
||
for spine in ax.spines.values():
|
||
spine.set_color('#444444')
|
||
for spine in ax2.spines.values():
|
||
spine.set_color('#444444')
|
||
|
||
self.canvas = FigureCanvasTkAgg(self.fig, master=self.chart_frame)
|
||
self.canvas.draw()
|
||
self.canvas.get_tk_widget().pack(fill="both", expand=True)
|
||
|
||
toolbar = NavigationToolbar2Tk(self.canvas, self.chart_frame, pack_toolbar=False)
|
||
toolbar.update()
|
||
toolbar.pack(side="top", fill="x")
|