#!/usr/bin/env python3 """ 探测器设置选项卡 """ import customtkinter as ctk import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import numpy as np class DetectorConfigTab(ctk.CTkFrame): def __init__(self, parent): super().__init__(parent) # 配置网格布局 self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure(0, weight=0) self.grid_rowconfigure(1, weight=1) # 探测器选择器 detector_frame = ctk.CTkFrame(self) detector_frame.grid(row=0, column=0, sticky="nsew", pady=(0, 10)) self.detector_var = ctk.StringVar(value="detector1") ctk.CTkRadioButton( detector_frame, text="探测器1", variable=self.detector_var, value="detector1", command=self.on_detector_change ).pack(side="left", padx=20, pady=10) ctk.CTkRadioButton( detector_frame, text="探测器2", variable=self.detector_var, value="detector2", command=self.on_detector_change ).pack(side="left", padx=20, pady=10) ctk.CTkRadioButton( detector_frame, text="探测器3", variable=self.detector_var, value="detector3", command=self.on_detector_change ).pack(side="left", padx=20, pady=10) # 探测器配置框架 self.config_frame = ctk.CTkFrame(self) self.config_frame.grid(row=1, column=0, sticky="nsew") # 配置探测器1的默认值 self.detector_configs = { "detector1": { "sample_space_size": 5000, "allow_replacement": True, "energy_distribution": { "type": "normal", "mean": 1000.0, "std": 200.0 }, "timestamp_distribution": { "type": "exponential", "scale": 50.0 } }, "detector2": { "sample_space_size": 4000, "allow_replacement": True, "energy_distribution": { "type": "normal", "mean": 800.0, "std": 150.0 }, "timestamp_distribution": { "type": "uniform", "low": 0.0, "high": 200.0 } }, "detector3": { "sample_space_size": 6000, "allow_replacement": True, "energy_distribution": { "type": "gamma", "shape": 2.0, "scale": 500.0 }, "timestamp_distribution": { "type": "normal", "mean": 100.0, "std": 30.0 } } } # 显示当前探测器配置 self.update_config_display() def on_detector_change(self): """探测器切换时的回调""" self.update_config_display() def update_config_display(self): """更新配置显示""" # 清空现有内容 for widget in self.config_frame.winfo_children(): widget.destroy() detector = self.detector_var.get() config = self.detector_configs[detector] # 样本空间设置(横向排列) sample_frame = ctk.CTkFrame(self.config_frame) sample_frame.grid(row=0, column=0, columnspan=2, sticky="w", padx=20, pady=10) ctk.CTkLabel(sample_frame, text="样本空间大小:").pack(side="left", padx=10, pady=5) self.sample_space_var = ctk.IntVar(value=config["sample_space_size"]) ctk.CTkEntry( sample_frame, textvariable=self.sample_space_var, width=100 ).pack(side="left", padx=5, pady=5) self.allow_replacement_var = ctk.BooleanVar(value=config["allow_replacement"]) ctk.CTkCheckBox( sample_frame, text="允许重复抽样", variable=self.allow_replacement_var ).pack(side="left", padx=20, pady=5) # 能量分布 ctk.CTkLabel( self.config_frame, text="能量分布", font=ctk.CTkFont(size=12, weight="bold") ).grid(row=2, column=0, sticky="w", padx=20, pady=10) # 分布类型 ctk.CTkLabel(self.config_frame, text="分布类型:").grid(row=3, column=0, sticky="w", padx=40, pady=5) self.energy_dist_type_var = ctk.StringVar(value=config["energy_distribution"]["type"]) dist_type_frame = ctk.CTkFrame(self.config_frame) dist_type_frame.grid(row=3, column=1, sticky="w", padx=10, pady=5) ctk.CTkOptionMenu( dist_type_frame, values=["normal", "uniform", "exponential", "gamma", "poisson", "lognormal"], variable=self.energy_dist_type_var, command=self.on_energy_dist_type_change ).pack(side="left") # 分布参数(根据类型动态显示) self.energy_params_frame = ctk.CTkFrame(self.config_frame) self.energy_params_frame.grid(row=4, column=0, columnspan=2, sticky="w", padx=40, pady=5) self.update_energy_params() # 时间戳分布 ctk.CTkLabel( self.config_frame, text="时间戳分布", font=ctk.CTkFont(size=12, weight="bold") ).grid(row=6, column=0, sticky="w", padx=20, pady=10) # 分布类型 ctk.CTkLabel(self.config_frame, text="分布类型:").grid(row=7, column=0, sticky="w", padx=40, pady=5) self.timestamp_dist_type_var = ctk.StringVar(value=config["timestamp_distribution"]["type"]) dist_type_frame2 = ctk.CTkFrame(self.config_frame) dist_type_frame2.grid(row=7, column=1, sticky="w", padx=10, pady=5) ctk.CTkOptionMenu( dist_type_frame2, values=["normal", "uniform", "exponential", "gamma", "poisson", "lognormal"], variable=self.timestamp_dist_type_var, command=self.on_timestamp_dist_type_change ).pack(side="left") # 分布参数(根据类型动态显示) self.timestamp_params_frame = ctk.CTkFrame(self.config_frame) self.timestamp_params_frame.grid(row=8, column=0, columnspan=2, sticky="w", padx=40, pady=5) self.update_timestamp_params() def on_energy_dist_type_change(self, value): """能量分布类型变化时的回调""" self.update_energy_params() def on_timestamp_dist_type_change(self, value): """时间戳分布类型变化时的回调""" self.update_timestamp_params() def update_energy_params(self): """更新能量分布参数""" # 清空现有内容 for widget in self.energy_params_frame.winfo_children(): widget.destroy() dist_type = self.energy_dist_type_var.get() config = self.detector_configs[self.detector_var.get()]["energy_distribution"] if dist_type == "normal": # 均值和标准差 ctk.CTkLabel(self.energy_params_frame, text="均值:").pack(side="left", padx=10, pady=5) self.energy_mean_var = ctk.DoubleVar(value=config.get("mean", 1000.0)) ctk.CTkEntry( self.energy_params_frame, textvariable=self.energy_mean_var, width=100 ).pack(side="left", padx=5, pady=5) ctk.CTkLabel(self.energy_params_frame, text="标准差:").pack(side="left", padx=10, pady=5) self.energy_std_var = ctk.DoubleVar(value=config.get("std", 200.0)) ctk.CTkEntry( self.energy_params_frame, textvariable=self.energy_std_var, width=100 ).pack(side="left", padx=5, pady=5) elif dist_type == "uniform": # 最小值和最大值 ctk.CTkLabel(self.energy_params_frame, text="最小值:").pack(side="left", padx=10, pady=5) self.energy_low_var = ctk.DoubleVar(value=config.get("low", 0.0)) ctk.CTkEntry( self.energy_params_frame, textvariable=self.energy_low_var, width=100 ).pack(side="left", padx=5, pady=5) ctk.CTkLabel(self.energy_params_frame, text="最大值:").pack(side="left", padx=10, pady=5) self.energy_high_var = ctk.DoubleVar(value=config.get("high", 2000.0)) ctk.CTkEntry( self.energy_params_frame, textvariable=self.energy_high_var, width=100 ).pack(side="left", padx=5, pady=5) elif dist_type in ["exponential", "gamma"]: # 尺度参数 ctk.CTkLabel(self.energy_params_frame, text="尺度参数:").pack(side="left", padx=10, pady=5) self.energy_scale_var = ctk.DoubleVar(value=config.get("scale", 50.0)) ctk.CTkEntry( self.energy_params_frame, textvariable=self.energy_scale_var, width=100 ).pack(side="left", padx=5, pady=5) if dist_type == "gamma": # 形状参数 ctk.CTkLabel(self.energy_params_frame, text="形状参数:").pack(side="left", padx=10, pady=5) self.energy_shape_var = ctk.DoubleVar(value=config.get("shape", 2.0)) ctk.CTkEntry( self.energy_params_frame, textvariable=self.energy_shape_var, width=100 ).pack(side="left", padx=5, pady=5) elif dist_type == "poisson": # lambda参数 ctk.CTkLabel(self.energy_params_frame, text="lambda:").pack(side="left", padx=10, pady=5) self.energy_lam_var = ctk.DoubleVar(value=config.get("lam", 1000.0)) ctk.CTkEntry( self.energy_params_frame, textvariable=self.energy_lam_var, width=100 ).pack(side="left", padx=5, pady=5) elif dist_type == "lognormal": # 均值和sigma ctk.CTkLabel(self.energy_params_frame, text="均值:").pack(side="left", padx=10, pady=5) self.energy_mean_var = ctk.DoubleVar(value=config.get("mean", 6.0)) ctk.CTkEntry( self.energy_params_frame, textvariable=self.energy_mean_var, width=100 ).pack(side="left", padx=5, pady=5) ctk.CTkLabel(self.energy_params_frame, text="sigma:").pack(side="left", padx=10, pady=5) self.energy_sigma_var = ctk.DoubleVar(value=config.get("sigma", 0.5)) ctk.CTkEntry( self.energy_params_frame, textvariable=self.energy_sigma_var, width=100 ).pack(side="left", padx=5, pady=5) def update_timestamp_params(self): """更新时间戳分布参数""" # 清空现有内容 for widget in self.timestamp_params_frame.winfo_children(): widget.destroy() dist_type = self.timestamp_dist_type_var.get() config = self.detector_configs[self.detector_var.get()]["timestamp_distribution"] if dist_type == "normal": # 均值和标准差 ctk.CTkLabel(self.timestamp_params_frame, text="均值:").pack(side="left", padx=10, pady=5) self.timestamp_mean_var = ctk.DoubleVar(value=config.get("mean", 100.0)) ctk.CTkEntry( self.timestamp_params_frame, textvariable=self.timestamp_mean_var, width=100 ).pack(side="left", padx=5, pady=5) ctk.CTkLabel(self.timestamp_params_frame, text="标准差:").pack(side="left", padx=10, pady=5) self.timestamp_std_var = ctk.DoubleVar(value=config.get("std", 30.0)) ctk.CTkEntry( self.timestamp_params_frame, textvariable=self.timestamp_std_var, width=100 ).pack(side="left", padx=5, pady=5) elif dist_type == "uniform": # 最小值和最大值 ctk.CTkLabel(self.timestamp_params_frame, text="最小值:").pack(side="left", padx=10, pady=5) self.timestamp_low_var = ctk.DoubleVar(value=config.get("low", 0.0)) ctk.CTkEntry( self.timestamp_params_frame, textvariable=self.timestamp_low_var, width=100 ).pack(side="left", padx=5, pady=5) ctk.CTkLabel(self.timestamp_params_frame, text="最大值:").pack(side="left", padx=10, pady=5) self.timestamp_high_var = ctk.DoubleVar(value=config.get("high", 200.0)) ctk.CTkEntry( self.timestamp_params_frame, textvariable=self.timestamp_high_var, width=100 ).pack(side="left", padx=5, pady=5) elif dist_type in ["exponential", "gamma"]: # 尺度参数 ctk.CTkLabel(self.timestamp_params_frame, text="尺度参数:").pack(side="left", padx=10, pady=5) self.timestamp_scale_var = ctk.DoubleVar(value=config.get("scale", 50.0)) ctk.CTkEntry( self.timestamp_params_frame, textvariable=self.timestamp_scale_var, width=100 ).pack(side="left", padx=5, pady=5) if dist_type == "gamma": # 形状参数 ctk.CTkLabel(self.timestamp_params_frame, text="形状参数:").pack(side="left", padx=10, pady=5) self.timestamp_shape_var = ctk.DoubleVar(value=config.get("shape", 2.0)) ctk.CTkEntry( self.timestamp_params_frame, textvariable=self.timestamp_shape_var, width=100 ).pack(side="left", padx=5, pady=5) elif dist_type == "poisson": # lambda参数 ctk.CTkLabel(self.timestamp_params_frame, text="lambda:").pack(side="left", padx=10, pady=5) self.timestamp_lam_var = ctk.DoubleVar(value=config.get("lam", 100.0)) ctk.CTkEntry( self.timestamp_params_frame, textvariable=self.timestamp_lam_var, width=100 ).pack(side="left", padx=5, pady=5) elif dist_type == "lognormal": # 均值和sigma ctk.CTkLabel(self.timestamp_params_frame, text="均值:").pack(side="left", padx=10, pady=5) self.timestamp_mean_var = ctk.DoubleVar(value=config.get("mean", 4.0)) ctk.CTkEntry( self.timestamp_params_frame, textvariable=self.timestamp_mean_var, width=100 ).pack(side="left", padx=5, pady=5) ctk.CTkLabel(self.timestamp_params_frame, text="sigma:").pack(side="left", padx=10, pady=5) self.timestamp_sigma_var = ctk.DoubleVar(value=config.get("sigma", 0.5)) ctk.CTkEntry( self.timestamp_params_frame, textvariable=self.timestamp_sigma_var, width=100 ).pack(side="left", padx=5, pady=5) def preview_energy_dist(self): """预览能量分布曲线""" dist_type = self.energy_dist_type_var.get() # 生成数据 if dist_type == "normal": mean = self.energy_mean_var.get() std = self.energy_std_var.get() data = np.random.normal(mean, std, 10000) elif dist_type == "uniform": low = self.energy_low_var.get() high = self.energy_high_var.get() data = np.random.uniform(low, high, 10000) elif dist_type == "exponential": scale = self.energy_scale_var.get() data = np.random.exponential(scale, 10000) elif dist_type == "gamma": shape = self.energy_shape_var.get() scale = self.energy_scale_var.get() data = np.random.gamma(shape, scale, 10000) elif dist_type == "poisson": lam = self.energy_lam_var.get() data = np.random.poisson(lam, 10000) elif dist_type == "lognormal": mean = self.energy_mean_var.get() sigma = self.energy_sigma_var.get() data = np.random.lognormal(mean, sigma, 10000) elif dist_type == "constant": value = self.energy_constant_var.get() data = np.full(10000, value) else: data = np.random.normal(1000, 200, 10000) # 创建图表 fig, ax = plt.subplots(figsize=(6, 4)) ax.hist(data, bins=50, alpha=0.7, color='blue') ax.set_title(f"{dist_type} 分布预览") ax.set_xlabel("能量 (keV)") ax.set_ylabel("频率") ax.grid(True, alpha=0.3) # 创建临时窗口显示图表 preview_window = ctk.CTkToplevel(self) preview_window.title("分布预览") preview_window.geometry("600x400") canvas = FigureCanvasTkAgg(fig, master=preview_window) canvas.draw() canvas.get_tk_widget().pack(fill="both", expand=True, padx=10, pady=10) def save_config(self): """保存配置""" detector = self.detector_var.get() # 更新样本空间大小和替换设置 self.detector_configs[detector]["sample_space_size"] = self.sample_space_var.get() self.detector_configs[detector]["allow_replacement"] = self.allow_replacement_var.get() # 更新能量分布 dist_type = self.energy_dist_type_var.get() self.detector_configs[detector]["energy_distribution"]["type"] = dist_type if dist_type == "normal": self.detector_configs[detector]["energy_distribution"]["mean"] = self.energy_mean_var.get() self.detector_configs[detector]["energy_distribution"]["std"] = self.energy_std_var.get() elif dist_type == "uniform": self.detector_configs[detector]["energy_distribution"]["low"] = self.energy_low_var.get() self.detector_configs[detector]["energy_distribution"]["high"] = self.energy_high_var.get() elif dist_type == "exponential": self.detector_configs[detector]["energy_distribution"]["scale"] = self.energy_scale_var.get() elif dist_type == "gamma": self.detector_configs[detector]["energy_distribution"]["shape"] = self.energy_shape_var.get() self.detector_configs[detector]["energy_distribution"]["scale"] = self.energy_scale_var.get() elif dist_type == "poisson": self.detector_configs[detector]["energy_distribution"]["lam"] = self.energy_lam_var.get() elif dist_type == "lognormal": self.detector_configs[detector]["energy_distribution"]["mean"] = self.energy_mean_var.get() self.detector_configs[detector]["energy_distribution"]["sigma"] = self.energy_sigma_var.get() # 更新时间戳分布 dist_type = self.timestamp_dist_type_var.get() self.detector_configs[detector]["timestamp_distribution"]["type"] = dist_type if dist_type == "normal": self.detector_configs[detector]["timestamp_distribution"]["mean"] = self.timestamp_mean_var.get() self.detector_configs[detector]["timestamp_distribution"]["std"] = self.timestamp_std_var.get() elif dist_type == "uniform": self.detector_configs[detector]["timestamp_distribution"]["low"] = self.timestamp_low_var.get() self.detector_configs[detector]["timestamp_distribution"]["high"] = self.timestamp_high_var.get() elif dist_type == "exponential": self.detector_configs[detector]["timestamp_distribution"]["scale"] = self.timestamp_scale_var.get() elif dist_type == "gamma": self.detector_configs[detector]["timestamp_distribution"]["shape"] = self.timestamp_shape_var.get() self.detector_configs[detector]["timestamp_distribution"]["scale"] = self.timestamp_scale_var.get() elif dist_type == "poisson": self.detector_configs[detector]["timestamp_distribution"]["lam"] = self.timestamp_lam_var.get() elif dist_type == "lognormal": self.detector_configs[detector]["timestamp_distribution"]["mean"] = self.timestamp_mean_var.get() self.detector_configs[detector]["timestamp_distribution"]["sigma"] = self.timestamp_sigma_var.get() def get_config(self): """获取配置""" # 保存当前配置 self.save_config() # 确保每个探测器配置都包含name键 for detector_name, config in self.detector_configs.items(): config['name'] = detector_name return self.detector_configs def set_config(self, config): """设置配置""" if isinstance(config, dict): for detector, det_config in config.items(): if detector in self.detector_configs: self.detector_configs[detector] = det_config # 更新显示 self.update_config_display()