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