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