RDSS/gui/tabs/result_preview.py

438 lines
19 KiB
Python
Raw Permalink Normal View History

2026-03-13 14:16:00 +08:00
#!/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")