RDSS/gui/tabs/result_preview.py

438 lines
19 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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")