RDSS/gui/tabs/detector_config.py

497 lines
21 KiB
Python

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