Files
WQ_GUI/src/gui/panels/step6_5_panel.py
2026-05-07 16:49:24 +08:00

289 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Step6_5 面板 - 非经验统计回归建模
"""
import os
from pathlib import Path
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout, QGridLayout,
QHBoxLayout, QLabel, QCheckBox, QSpinBox, QPushButton,
QFileDialog, QMessageBox,
)
from src.gui.components.custom_widgets import FileSelectWidget
from src.gui.styles import ModernStylesheet
class Step6_5Panel(QWidget):
"""步骤6.5:非经验统计回归建模"""
def __init__(self, parent=None):
super().__init__(parent)
self.init_ui()
def init_ui(self):
layout = QVBoxLayout()
# 标题
# 训练数据文件(用于独立运行)
self.training_csv_file = FileSelectWidget(
"训练数据CSV:",
"CSV Files (*.csv);;All Files (*.*)"
)
layout.addWidget(self.training_csv_file)
# 参数设置
params_group = QGroupBox("模型参数")
params_layout = QFormLayout()
# 预处理方法
self.preproc_checkboxes = {}
preproc_group = QGroupBox("预处理方法 (可多选)")
preproc_layout = QVBoxLayout()
preproc_grid = QGridLayout()
preproc_methods = ['None', 'MMS', 'SS', 'SNV', 'MA', 'SG', 'MSC', 'D1', 'D2', 'DT', 'CT']
for i, method in enumerate(preproc_methods):
checkbox = QCheckBox(method)
checkbox.setChecked(True)
self.preproc_checkboxes[method] = checkbox
preproc_grid.addWidget(checkbox, i // 4, i % 4)
button_layout = QHBoxLayout()
select_all_btn = QPushButton("全选")
deselect_all_btn = QPushButton("全不选")
select_all_btn.clicked.connect(lambda: self._toggle_checkboxes(self.preproc_checkboxes, True))
deselect_all_btn.clicked.connect(lambda: self._toggle_checkboxes(self.preproc_checkboxes, False))
button_layout.addWidget(select_all_btn)
button_layout.addWidget(deselect_all_btn)
button_layout.addStretch()
preproc_layout.addLayout(preproc_grid)
preproc_layout.addLayout(button_layout)
preproc_group.setLayout(preproc_layout)
params_layout.addRow(preproc_group)
# 算法选择(可多选)
self.algorithm_inputs = {}
algorithms_widget = QWidget()
algorithms_layout = QVBoxLayout()
algorithms_layout.setContentsMargins(0, 0, 0, 0)
algorithms_layout.setSpacing(4)
algorithm_list = ['chl_a', 'nh3', 'mno4', 'tn', 'tp', 'tss']
for algorithm in algorithm_list:
row_widget = QWidget()
row_layout = QHBoxLayout()
row_layout.setContentsMargins(0, 0, 0, 0)
checkbox = QCheckBox(algorithm)
checkbox.setChecked(True)
spinbox = QSpinBox()
spinbox.setRange(0, 500)
spinbox.setValue(0)
spinbox.setMaximumWidth(90)
row_layout.addWidget(checkbox)
row_layout.addWidget(QLabel("对应值列索引:"))
row_layout.addWidget(spinbox)
row_layout.addStretch()
row_widget.setLayout(row_layout)
algorithms_layout.addWidget(row_widget)
self.algorithm_inputs[algorithm] = (checkbox, spinbox)
algorithms_widget.setLayout(algorithms_layout)
params_layout.addRow("非经验算法选择:", algorithms_widget)
# 光谱起始列
self.spectral_start_col = QSpinBox()
self.spectral_start_col.setRange(0, 100)
self.spectral_start_col.setValue(1)
params_layout.addRow("光谱起始列索引:", self.spectral_start_col)
# 窗口大小
self.window = QSpinBox()
self.window.setRange(1, 20)
self.window.setValue(5)
params_layout.addRow("窗口大小:", self.window)
params_group.setLayout(params_layout)
layout.addWidget(params_group)
# 输出文件路径
self.output_dir = FileSelectWidget(
"输出模型目录:",
"Directories;;All Files (*.*)"
)
self.output_dir.line_edit.setPlaceholderText("8_Regression_Modeling")
self.output_dir.browse_btn.clicked.disconnect()
self.output_dir.browse_btn.clicked.connect(self.browse_output_dir)
layout.addWidget(self.output_dir)
# 启用步骤
self.enable_checkbox = QCheckBox("启用此步骤")
self.enable_checkbox.setChecked(True)
layout.addWidget(self.enable_checkbox)
# 独立运行按钮
self.run_button = QPushButton("独立运行此步骤")
self.run_button.setStyleSheet(ModernStylesheet.get_button_stylesheet('success'))
self.run_button.clicked.connect(self.run_step)
layout.addWidget(self.run_button)
layout.addStretch()
self.setLayout(layout)
def get_config(self):
"""获取配置"""
selected_algorithms = [
name for name, (checkbox, _) in self.algorithm_inputs.items()
if checkbox.isChecked()
]
if not selected_algorithms:
selected_algorithms = list(self.algorithm_inputs.keys())
value_cols = {
name: spinbox.value()
for name, (_, spinbox) in self.algorithm_inputs.items()
if name in selected_algorithms
}
preprocessing_methods = [
method for method, checkbox in self.preproc_checkboxes.items()
if checkbox.isChecked()
] or ['None']
config = {
'preprocessing_methods': preprocessing_methods,
'algorithms': selected_algorithms,
'value_cols': value_cols,
'spectral_start_col': self.spectral_start_col.value(),
'window': self.window.value(),
'enabled': self.enable_checkbox.isChecked()
}
output_dir = self.output_dir.get_path()
if not output_dir:
main_window = self.parent().window()
if hasattr(main_window, 'work_dir') and main_window.work_dir:
output_dir = str(Path(main_window.work_dir) / "8_Regression_Modeling")
else:
output_dir = str(Path.cwd() / "8_Regression_Modeling")
config['output_dir'] = output_dir
training_csv_path = self.training_csv_file.get_path()
if training_csv_path:
config['csv_path'] = training_csv_path
return config
def set_config(self, config):
"""设置配置"""
if 'preprocessing_methods' in config:
methods = config['preprocessing_methods']
for method, checkbox in self.preproc_checkboxes.items():
checkbox.setChecked(method in methods)
if 'algorithms' in config:
algorithm_values = config['algorithms']
for algorithm, (checkbox, spinbox) in self.algorithm_inputs.items():
checkbox.setChecked(algorithm in algorithm_values)
if 'value_cols' in config:
value_cols = config['value_cols']
if isinstance(value_cols, dict):
for algorithm, (_, spinbox) in self.algorithm_inputs.items():
if algorithm in value_cols:
spinbox.setValue(value_cols[algorithm])
else:
for _, spinbox in self.algorithm_inputs.values():
spinbox.setValue(value_cols)
if 'spectral_start_col' in config:
self.spectral_start_col.setValue(config['spectral_start_col'])
if 'window' in config:
self.window.setValue(config['window'])
if 'output_dir' in config:
self.output_dir.set_path(config['output_dir'])
if 'csv_path' in config:
self.training_csv_file.set_path(config['csv_path'])
def update_from_config(self, work_dir=None, pipeline=None):
"""从全局配置自动填充训练数据和输出路径
Args:
work_dir: 工作目录路径
pipeline: Pipeline 实例(未使用,保留接口兼容性)
"""
if work_dir:
self.work_dir = work_dir
elif hasattr(self, 'work_dir') and self.work_dir:
pass
else:
self.work_dir = None
# 1. 尝试从 Step5 界面读取训练光谱 CSV 路径
main_window = self.window()
if main_window and hasattr(main_window, 'step5_panel'):
step5_output_path = main_window.step5_panel.output_file.get_path()
if step5_output_path:
existing = self.training_csv_file.get_path()
if not existing or not existing.strip():
self.training_csv_file.set_path(step5_output_path)
# 2. 自动填充输出目录8_Regression_Modeling
if self.work_dir:
output_dir = os.path.join(self.work_dir, "8_Regression_Modeling")
os.makedirs(output_dir, exist_ok=True)
existing_out = self.output_dir.get_path()
if not existing_out or not existing_out.strip():
self.output_dir.set_path(output_dir)
def _get_default_work_dir(self):
"""获取 work_dir优先用 panel 自身缓存的,否则尝试从主窗口取"""
if hasattr(self, 'work_dir') and self.work_dir:
return str(self.work_dir)
mw = self.window()
if mw and hasattr(mw, 'work_dir') and mw.work_dir:
return str(mw.work_dir)
return ""
def browse_output_dir(self):
"""浏览输出目录"""
default = self._get_default_work_dir()
if default:
default = os.path.join(default, "8_Regression_Modeling")
dir_path = QFileDialog.getExistingDirectory(self, "选择输出模型目录", default)
if dir_path:
self.output_dir.set_path(dir_path)
def run_step(self):
"""独立运行步骤6.5"""
training_csv_path = self.training_csv_file.get_path()
if not training_csv_path:
QMessageBox.warning(self, "输入错误", "请选择训练数据CSV文件")
return
if not os.path.exists(training_csv_path):
QMessageBox.warning(self, "输入错误", "训练数据CSV文件不存在")
return
config = self.get_config()
parent = self.parent()
while parent and not hasattr(parent, 'run_single_step'):
parent = parent.parent()
if parent and hasattr(parent, 'run_single_step'):
parent.run_single_step('step6_5', {'step6_5': config})
else:
QMessageBox.critical(self, "错误", "无法找到父级GUI对象")
def _toggle_checkboxes(self, checkboxes_dict, checked):
"""统一设置预处理checkbox状态"""
for checkbox in checkboxes_dict.values():
checkbox.setChecked(checked)