Fix step4_panel variable name inconsistency causing AttributeError

This commit is contained in:
DXC
2026-06-11 15:14:26 +08:00
parent 5d75d3371b
commit 1ad4c54b80
6 changed files with 572 additions and 646 deletions

View File

@ -60,10 +60,8 @@ class PreflightDialog(QDialog):
"step4": ("数据清洗", 3),
"step5": ("特征构建", 4),
"step7": ("水质指数", 5),
"step8": ("监督建模", 6),
"step8_non_empirical_modeling": ("回归建模", 7),
"step9": ("水色指数反演", 8),
"step9_concentration": ("浓度反演", 9),
"step10": ("采样点布设", 10),
"step11_ml": ("监督预测", 11),
"step11": ("回归预测", 12),

View File

@ -1,226 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Step11 面板 - 非经验模型预测
"""
import os
from pathlib import Path
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout,
QPushButton, QCheckBox, QComboBox, QLineEdit, QMessageBox,
QFileDialog,
)
from src.gui.components.custom_widgets import FileSelectWidget
from src.gui.styles import ModernStylesheet
class Step11NonEmpiricalPanel(QWidget):
"""步骤11非经验模型预测"""
def __init__(self, parent=None):
super().__init__(parent)
self.init_ui()
def init_ui(self):
layout = QVBoxLayout()
# 采样光谱CSV文件选择
self.sampling_csv_file = FileSelectWidget(
"采样光谱CSV:",
"CSV Files (*.csv);;All Files (*.*)"
)
layout.addWidget(self.sampling_csv_file)
# 模型目录选择
self.models_dir_file = FileSelectWidget(
"模型目录:",
"Directories;;All Files (*.*)"
)
self.models_dir_file.label.setText("模型目录:")
self.models_dir_file.browse_btn.clicked.disconnect()
self.models_dir_file.browse_btn.clicked.connect(self.browse_models_dir)
layout.addWidget(self.models_dir_file)
# 参数设置
params_group = QGroupBox("预测参数")
params_layout = QFormLayout()
self.metric = QComboBox()
self.metric.addItems(['Average Accuracy(%)', 'Min Accuracy(%)', 'Max Accuracy(%)'])
params_layout.addRow("模型选择指标:", self.metric)
self.prediction_column = QLineEdit()
self.prediction_column.setText("prediction")
params_layout.addRow("预测列名:", self.prediction_column)
params_group.setLayout(params_layout)
layout.addWidget(params_group)
# 输出路径
self.output_file = FileSelectWidget(
"输出文件夹:",
"Directories;;All Files (*.*)"
)
self.output_file.label.setText("输出文件夹:")
self.output_file.browse_btn.clicked.disconnect()
self.output_file.browse_btn.clicked.connect(self.browse_output_dir)
layout.addWidget(self.output_file)
# 启用步骤
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 update_from_config(self, work_dir=None, pipeline=None):
"""从全局配置自动填充采样光谱和回归模型目录
Args:
work_dir: 工作目录路径
pipeline: Pipeline 实例(未使用,保留接口兼容性)
"""
try:
import traceback
if work_dir:
self.work_dir = work_dir
elif hasattr(self, 'work_dir') and self.work_dir:
pass
else:
self.work_dir = None
main_window = self.window()
# 1. 尝试从 Step7 界面读取全湖采样点 CSV 路径
if main_window and hasattr(main_window, 'step7_panel'):
step7_widget = getattr(main_window.step7_panel, 'output_file', None)
step7_output_path = ""
if hasattr(step7_widget, 'get_path'):
step7_output_path = step7_widget.get_path() or ""
elif hasattr(step7_widget, 'text'):
step7_output_path = step7_widget.text() or ""
if step7_output_path:
# 若为相对路径,使用 work_dir 合成为绝对路径
if not os.path.isabs(step7_output_path):
step7_output_path = os.path.join(self.work_dir or '', step7_output_path).replace('\\', '/')
existing = self.sampling_csv_file.get_path()
if not existing or not existing.strip():
self.sampling_csv_file.set_path(step7_output_path)
# 2. 尝试从 Step8_Non_Empirical 界面读取回归模型目录
if main_window and hasattr(main_window, 'step8_non_empirical_panel'):
step8_non_empirical_widget = getattr(main_window.step8_non_empirical_panel, 'output_dir', None)
step8_non_empirical_models_dir = ""
if hasattr(step8_non_empirical_widget, 'get_path'):
step8_non_empirical_models_dir = step8_non_empirical_widget.get_path() or ""
elif hasattr(step8_non_empirical_widget, 'text'):
step8_non_empirical_models_dir = step8_non_empirical_widget.text() or ""
if step8_non_empirical_models_dir:
# 若为相对路径,使用 work_dir 合成为绝对路径
if not os.path.isabs(step8_non_empirical_models_dir):
step8_non_empirical_models_dir = os.path.join(self.work_dir or '', step8_non_empirical_models_dir).replace('\\', '/')
existing_models = self.models_dir_file.get_path()
if not existing_models or not existing_models.strip():
self.models_dir_file.set_path(step8_non_empirical_models_dir)
# 3. 自动填充输出路径(非经验模型预测目录)
if self.work_dir:
output_dir = os.path.join(self.work_dir, "11_12_13_predictions/Non_Empirical_Prediction")
os.makedirs(output_dir, exist_ok=True)
existing_out = self.output_file.get_path()
if not existing_out or not existing_out.strip():
self.output_file.set_path(output_dir)
except Exception as e:
import traceback
print(f"{self.__class__.__name__}】自动填充失败,跳过: {e}")
traceback.print_exc()
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_models_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.models_dir_file.set_path(dir_path)
def browse_output_dir(self):
"""浏览输出目录"""
default = self._get_default_work_dir()
if default:
default = os.path.join(default, "11_12_13_predictions/Non_Empirical_Prediction")
dir_path = QFileDialog.getExistingDirectory(self, "选择输出文件夹", default)
if dir_path:
self.output_file.set_path(dir_path)
def get_config(self):
"""获取配置"""
config = {
'metric': self.metric.currentText(),
'prediction_column': self.prediction_column.text(),
'enabled': self.enable_checkbox.isChecked()
}
sampling_csv_path = self.sampling_csv_file.get_path()
if sampling_csv_path:
config['sampling_csv_path'] = sampling_csv_path
models_dir = self.models_dir_file.get_path()
if models_dir:
config['models_dir'] = models_dir
output_path = self.output_file.get_path()
if output_path:
config['output_path'] = output_path
return config
def set_config(self, config):
"""设置配置"""
if 'metric' in config:
idx = self.metric.findText(config['metric'])
if idx >= 0:
self.metric.setCurrentIndex(idx)
if 'prediction_column' in config:
self.prediction_column.setText(config['prediction_column'])
if 'sampling_csv_path' in config:
self.sampling_csv_file.set_path(config['sampling_csv_path'])
if 'models_dir' in config:
self.models_dir_file.set_path(config['models_dir'])
if 'enabled' in config:
self.enable_checkbox.setChecked(config['enabled'])
def run_step(self):
"""独立运行步骤11"""
sampling_csv_path = self.sampling_csv_file.get_path()
if not sampling_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('step11', {'step11': config})
else:
QMessageBox.critical(self, "错误", "无法找到父级GUI对象")

View File

@ -1,415 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Step7 面板 - 机器学习建模
"""
import os
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout, QGridLayout,
QHBoxLayout, QLabel, QLineEdit, QSpinBox, QCheckBox,
QPushButton, QFileDialog, QMessageBox,
)
from PyQt5.QtCore import Qt
from src.gui.components.custom_widgets import FileSelectWidget
from src.gui.styles import ModernStylesheet
# ============================================================
# 中文映射表(内部键名 -> 显示文本)
# ============================================================
# 预处理方法:内部键 -> 显示文本
PREPROC_CHINESE = {
'None': '无 (None)',
'MMS': '最小-最大归一化 (MMS)',
'SS': '标度化 (SS)',
'SNV': '标准正态变换 (SNV)',
'MA': '移动平均 (MA)',
'SG': 'Savitzky-Golay (SG)',
'MSC': '多元散射校正 (MSC)',
'D1': '一阶导数 (D1)',
'D2': '二阶导数 (D2)',
'DT': '去趋势 (DT)',
'CT': '中心化 (CT)',
}
# 模型类型:内部键 -> 显示文本
MODEL_CHINESE = {
# 线性模型
'LinearRegression': '多元线性回归 (MLR)',
'Ridge': '岭回归 (Ridge)',
'Lasso': '套索回归 (Lasso)',
'ElasticNet': '弹性网络 (ElasticNet)',
'PLS': '偏最小二乘 (PLSR)',
# 树模型
'DecisionTree': '决策树 (CART)',
'RF': '随机森林 (RF)',
'ExtraTrees': '极端随机树 (ET)',
'XGBoost': '极值梯度提升 (XGBoost)',
'LightGBM': '轻量梯度提升 (LightGBM)',
'CatBoost': '类别梯度提升 (CatBoost)',
# 集成学习
'GradientBoosting': '梯度提升树 (GBDT)',
'AdaBoost': '自适应提升 (AdaBoost)',
# 其他模型
'SVR': '支持向量回归 (SVR)',
'KNN': 'K近邻回归 (KNN)',
'MLP': '多层感知机 (BP神经网络)',
}
# 数据划分方法:内部键 -> 显示文本
SPLIT_CHINESE = {
'spxy': 'SPXY 算法 (考量X-Y空间)',
'ks': 'KS 算法 (考量X空间)',
'random': '随机划分 (Random)',
}
class Step8Panel(QWidget):
"""步骤8水质参数指数计算"""
def __init__(self, parent=None):
super().__init__(parent)
self.init_ui()
def init_ui(self):
layout = QVBoxLayout()
# 标题
# 训练数据文件(用于独立运行)
self.training_csv_file = FileSelectWidget(
"训练数据:",
"CSV Files (*.csv);;All Files (*.*)"
)
layout.addWidget(self.training_csv_file)
# 机器学习模型页面
self.ml_page = QWidget()
self.create_ml_page()
layout.addWidget(self.ml_page)
# 输出文件路径
self.output_path = FileSelectWidget(
"输出文件:",
"CSV Files (*.csv);;All Files (*.*)",
mode="save"
)
self.output_path.line_edit.setPlaceholderText("自动生成,或手动指定输出文件路径...")
self.output_path.browse_btn.clicked.disconnect()
self.output_path.browse_btn.clicked.connect(self.browse_output_path)
layout.addWidget(self.output_path)
# 启用步骤
self.enable_checkbox = QCheckBox("启用此步骤")
self.enable_checkbox.setChecked(False)
layout.addWidget(self.enable_checkbox)
# 独立运行按钮
self.run_btn = QPushButton("独立运行此步骤")
self.run_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('success'))
self.run_btn.clicked.connect(self.run_step)
layout.addWidget(self.run_btn)
layout.addStretch()
self.setLayout(layout)
def create_ml_page(self):
"""创建机器学习模型页面"""
layout = QVBoxLayout()
# 参数设置
params_group = QGroupBox("训练参数")
params_layout = QFormLayout()
self.feature_start = QLineEdit()
self.feature_start.setText("374.285004")
params_layout.addRow("特征起始列:", self.feature_start)
self.cv_folds = QSpinBox()
self.cv_folds.setRange(2, 10)
self.cv_folds.setValue(3)
params_layout.addRow("交叉验证折数:", self.cv_folds)
params_group.setLayout(params_layout)
layout.addWidget(params_group)
# 预处理方法 - 多选
preproc_group = QGroupBox("预处理方法 (可多选)")
preproc_layout = QVBoxLayout()
preproc_grid = QGridLayout()
self.preproc_checkboxes = {}
preproc_methods = ['None', 'MMS', 'SS', 'SNV', 'MA', 'SG', 'MSC', 'D1', 'D2', 'DT', 'CT']
for i, method in enumerate(preproc_methods):
checkbox = QCheckBox(PREPROC_CHINESE.get(method, method))
checkbox.setChecked(False)
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)
layout.addWidget(preproc_group)
# 模型选择 - 多选
model_group = QGroupBox("模型类型 (可多选)")
model_layout = QVBoxLayout()
model_grid = QGridLayout()
self.model_checkboxes = {}
model_groups = [
("【线性模型】", ['LinearRegression', 'Ridge', 'Lasso', 'ElasticNet', 'PLS']),
("【树模型】", ['DecisionTree', 'RF', 'ExtraTrees', 'XGBoost', 'LightGBM', 'CatBoost']),
("【集成学习】", ['GradientBoosting', 'AdaBoost']),
("【其他模型】", ['SVR', 'KNN', 'MLP'])
]
row = 0
for group_name, models in model_groups:
group_label = QLabel(f"<b>{group_name}</b>")
group_label.setStyleSheet(
f"background-color: {ModernStylesheet.COLORS['hover']}; "
f"padding: 5px; border: 1px solid {ModernStylesheet.COLORS['border_light']}; "
f"border-radius: 3px;"
)
model_grid.addWidget(group_label, row, 0, 1, 4)
row += 1
for i, model in enumerate(models):
checkbox = QCheckBox(MODEL_CHINESE.get(model, model))
checkbox.setChecked(False)
self.model_checkboxes[model] = checkbox
model_grid.addWidget(checkbox, row, i % 4)
if (i + 1) % 4 == 0:
row += 1
row += 1
model_button_layout = QHBoxLayout()
model_select_all = QPushButton("全选")
model_deselect_all = QPushButton("全不选")
model_select_all.clicked.connect(lambda: self._toggle_checkboxes(self.model_checkboxes, True))
model_deselect_all.clicked.connect(lambda: self._toggle_checkboxes(self.model_checkboxes, False))
model_button_layout.addWidget(model_select_all)
model_button_layout.addWidget(model_deselect_all)
model_button_layout.addStretch()
model_layout.addLayout(model_grid)
model_layout.addLayout(model_button_layout)
model_group.setLayout(model_layout)
layout.addWidget(model_group)
# 数据划分方法 - 多选
split_group = QGroupBox("数据划分方法 (可多选)")
split_layout = QVBoxLayout()
split_grid = QGridLayout()
self.split_checkboxes = {}
split_methods = ['spxy', 'ks', 'random']
for i, method in enumerate(split_methods):
checkbox = QCheckBox(SPLIT_CHINESE.get(method, method))
checkbox.setChecked(False)
self.split_checkboxes[method] = checkbox
split_grid.addWidget(checkbox, 0, i)
split_button_layout = QHBoxLayout()
split_select_all = QPushButton("全选")
split_deselect_all = QPushButton("全不选")
split_select_all.clicked.connect(lambda: self._toggle_checkboxes(self.split_checkboxes, True))
split_deselect_all.clicked.connect(lambda: self._toggle_checkboxes(self.split_checkboxes, False))
split_button_layout.addWidget(split_select_all)
split_button_layout.addWidget(split_deselect_all)
split_button_layout.addStretch()
split_layout.addLayout(split_grid)
split_layout.addLayout(split_button_layout)
split_group.setLayout(split_layout)
layout.addWidget(split_group)
self.ml_page.setLayout(layout)
def _toggle_checkboxes(self, checkboxes_dict, checked):
"""统一设置checkbox状态"""
for checkbox in checkboxes_dict.values():
checkbox.setChecked(checked)
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_path(self):
"""浏览输出文件路径(保存对话框)"""
current = self.output_path.get_path().strip()
if current:
initial_dir = os.path.dirname(current)
initial_file = os.path.basename(current)
else:
initial_dir = ""
initial_file = ""
if not initial_dir or not os.path.isdir(initial_dir):
# 默认定位到 indices 目录
work_dir = self._get_default_work_dir()
initial_dir = os.path.join(work_dir, "6_water_quality_indices") if work_dir else ""
if initial_dir and not os.path.isdir(initial_dir):
os.makedirs(initial_dir, exist_ok=True)
file_path, _ = QFileDialog.getSaveFileName(
self, "保存输出文件", os.path.join(initial_dir, initial_file) if initial_file else initial_dir,
"CSV Files (*.csv);;All Files (*.*)"
)
if file_path:
self.output_path.set_path(file_path)
def get_config(self):
"""获取配置"""
preprocessing_methods = [
method for method, checkbox in self.preproc_checkboxes.items()
if checkbox.isChecked()
]
model_names = [
model for model, checkbox in self.model_checkboxes.items()
if checkbox.isChecked()
]
split_methods = [
method for method, checkbox in self.split_checkboxes.items()
if checkbox.isChecked()
]
config = {
'feature_start_column': self.feature_start.text(),
'preprocessing_methods': preprocessing_methods if preprocessing_methods else ['None'],
'model_names': model_names if model_names else ['SVR'],
'split_methods': split_methods if split_methods else ['random'],
'cv_folds': self.cv_folds.value()
}
training_csv_path = self.training_csv_file.get_path()
if training_csv_path:
config['training_csv_path'] = training_csv_path
output_path = self.output_path.get_path()
if output_path:
config['output_path'] = output_path
return config
def set_config(self, config):
"""设置配置"""
if 'feature_start_column' in config:
self.feature_start.setText(str(config['feature_start_column']))
if 'cv_folds' in config:
self.cv_folds.setValue(config['cv_folds'])
if 'preprocessing_methods' in config:
methods = config['preprocessing_methods']
for method, checkbox in self.preproc_checkboxes.items():
checkbox.setChecked(method in methods)
if 'model_names' in config:
models = config['model_names']
for model, checkbox in self.model_checkboxes.items():
checkbox.setChecked(model in models)
if 'split_methods' in config:
methods = config['split_methods']
for method, checkbox in self.split_checkboxes.items():
checkbox.setChecked(method in methods)
if 'training_csv_path' in config:
self.training_csv_file.set_path(config['training_csv_path'])
if 'output_path' in config:
self.output_path.set_path(config['output_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. 尝试从 Step6 界面读取训练数据路径,并确保为绝对路径
main_window = self.window()
if hasattr(main_window, 'step6_panel'):
# 优先直接从 Step6 的输出 widget 读取
step5_output = main_window.step6_panel.output_file.get_path()
if step5_output:
# 若为相对路径,使用 work_dir 合成为绝对路径
if not os.path.isabs(step5_output):
step5_output = os.path.join(self.work_dir or '', step5_output).replace('\\', '/')
self.training_csv_file.set_path(step5_output)
elif hasattr(main_window, 'step6_panel') and hasattr(main_window.step6_panel, 'get_config'):
# 回退:从 Step6 的 config 字典中查找可能的键名
step6_cfg = main_window.step6_panel.get_config()
step6_csv = (
step6_cfg.get('training_csv_path')
or step6_cfg.get('output_file')
or step6_cfg.get('csv_path')
or step6_cfg.get('output_csv')
)
if step6_csv:
# 若为相对路径,使用 work_dir 合成为绝对路径
if not os.path.isabs(step6_csv):
step6_csv = os.path.join(self.work_dir or '', step6_csv).replace('\\', '/')
self.training_csv_file.set_path(step6_csv)
# 2. 自动填充输出文件路径(基于工作目录和输入文件名)
# 输入是 training_spectra.csv → 输出 {work_dir}/6_water_quality_indices/training_spectra_indices.csv
# 输入是 sampling_spectra.csv → 输出 {work_dir}/6_water_quality_indices/sampling_spectra_indices.csv
if self.work_dir:
indices_dir = os.path.join(self.work_dir, "6_water_quality_indices")
os.makedirs(indices_dir, exist_ok=True)
training_csv = self.training_csv_file.get_path()
if training_csv:
basename = os.path.splitext(os.path.basename(training_csv))[0]
output_file = f"{basename}_indices.csv"
else:
output_file = "water_quality_indices.csv"
output_path = os.path.join(indices_dir, output_file).replace('\\', '/')
self.output_path.set_path(output_path)
else:
self.output_path.set_path("")
def run_step(self):
"""独立运行步骤7"""
training_csv_path = self.training_csv_file.get_path()
if not training_csv_path:
QMessageBox.warning(self, "输入错误", "请选择训练数据CSV文件")
return
main_window = self.window()
if hasattr(main_window, 'run_single_step'):
config = {'step7': self.get_config()}
main_window.run_single_step('step7', config)
def get_training_params(self):
"""获取模型训练参数"""
return {
'pipeline_type': 'machine_learning',
'feature_start': float(self.feature_start.text()),
'cv_folds': self.cv_folds.value(),
'preprocess_methods': [method for method, cb in self.preproc_checkboxes.items() if cb.isChecked()],
'model_types': [model for model, cb in self.model_checkboxes.items() if cb.isChecked()],
'split_methods': [method for method, cb in self.split_checkboxes.items() if cb.isChecked()]
}

View File

@ -1,239 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Step10 面板 - 浓度反演(基于 QAA 物理反演的二次反演)
"""
import os
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout, QHBoxLayout,
QLabel, QCheckBox, QPushButton, QMessageBox, QComboBox,
QFileDialog,
)
from PyQt5.QtGui import QFont
from PyQt5.QtCore import Qt
from src.gui.components.custom_widgets import FileSelectWidget
from src.gui.styles import ModernStylesheet
class Step10ConcentrationPanel(QWidget):
"""步骤10浓度反演物理模型二次反演"""
def __init__(self, parent=None):
super().__init__(parent)
self.init_ui()
def init_ui(self):
layout = QVBoxLayout()
title = QLabel("步骤10浓度反演物理模型二次反演")
title.setFont(QFont("Arial", 12, QFont.Bold))
layout.addWidget(title)
# 输入 QAA 结果文件
self.input_file = FileSelectWidget(
"QAA 结果文件:",
"CSV Files (*.csv);;All Files (*.*)"
)
self.input_file.line_edit.setPlaceholderText(
"选择 Step 8 输出的 a_lambda_results.csv"
)
layout.addWidget(self.input_file)
# 输出路径
self.output_file = FileSelectWidget(
"输出文件:",
"CSV Files (*.csv);;All Files (*.*)",
mode="save"
)
self.output_file.line_edit.setPlaceholderText(
"自动生成到 9_Concentration或手动指定..."
)
layout.addWidget(self.output_file)
# 选择反演指标
indicators_group = QGroupBox("选择反演指标")
indicators_layout = QFormLayout()
self.chla_check = QCheckBox("叶绿素 A (Chl-a)")
self.chla_check.setChecked(True)
self.cdom_check = QCheckBox("CDOM 吸收系数 a_dg(440)")
self.cdom_check.setChecked(True)
self.turbidity_check = QCheckBox("浊度 (Turbidity)")
self.turbidity_check.setChecked(True)
self.tn_check = QCheckBox("总氮 (TN)")
self.tn_check.setChecked(True)
self.tp_check = QCheckBox("总磷 (TP)")
self.tp_check.setChecked(True)
chk_layout = QVBoxLayout()
chk_layout.setSpacing(6)
for cb in [self.chla_check, self.cdom_check,
self.turbidity_check, self.tn_check, self.tp_check]:
chk_layout.addWidget(cb)
indicators_layout.addRow("水质参数:", chk_layout)
indicators_group.setLayout(indicators_layout)
layout.addWidget(indicators_group)
# 水体类型(用于比吸收系数自适应)
lake_group = QGroupBox("水体类型")
lake_layout = QFormLayout()
self.lake_case_combo = QComboBox()
self.lake_case_combo.addItems([
"通用 (medium)",
"oligotrophic_clear寡营养清澈",
"bloom_dominant藻华主导",
"turbid_mixed高浊混合",
])
self.lake_case_combo.setCurrentIndex(0)
lake_layout.addRow("水体类型:", self.lake_case_combo)
lake_group.setLayout(lake_layout)
layout.addWidget(lake_group)
# 启用步骤
self.enable_checkbox = QCheckBox("启用此步骤")
self.enable_checkbox.setChecked(False)
layout.addWidget(self.enable_checkbox)
# 独立运行按钮
self.run_btn = QPushButton("执行浓度反演")
self.run_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('success'))
self.run_btn.clicked.connect(self.run_step)
layout.addWidget(self.run_btn)
layout.addStretch()
self.setLayout(layout)
def _get_default_work_dir(self) -> str:
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_path(self):
current = self.output_file.get_path().strip()
if current:
initial_dir = os.path.dirname(current)
initial_file = os.path.basename(current)
else:
initial_dir = ""
initial_file = ""
if not initial_dir or not os.path.isdir(initial_dir):
work_dir = self._get_default_work_dir()
initial_dir = os.path.join(work_dir, "9_Concentration") if work_dir else ""
if initial_dir and not os.path.isdir(initial_dir):
os.makedirs(initial_dir, exist_ok=True)
file_path, _ = QFileDialog.getSaveFileName(
self, "保存输出文件",
os.path.join(initial_dir, initial_file) if initial_file else initial_dir,
"CSV Files (*.csv);;All Files (*.*)"
)
if file_path:
self.output_file.set_path(file_path)
def get_config(self) -> dict:
enabled_indicators = []
if self.chla_check.isChecked():
enabled_indicators.append('chla')
if self.cdom_check.isChecked():
enabled_indicators.append('cdom')
if self.turbidity_check.isChecked():
enabled_indicators.append('turbidity')
if self.tn_check.isChecked():
enabled_indicators.append('tn')
if self.tp_check.isChecked():
enabled_indicators.append('tp')
lake_case_map = {
0: "medium",
1: "oligotrophic_clear",
2: "bloom_dominant",
3: "turbid_mixed",
}
lake_case = lake_case_map.get(self.lake_case_combo.currentIndex(), "medium")
return {
'input_csv': self.input_file.get_path(),
'output_csv': self.output_file.get_path(),
'enabled_indicators': enabled_indicators,
'lake_case': lake_case,
}
def set_config(self, config: dict):
if 'input_csv' in config:
self.input_file.set_path(config['input_csv'])
if 'output_csv' in config:
self.output_file.set_path(config['output_csv'])
def update_from_config(self, work_dir=None, pipeline=None):
if work_dir:
self.work_dir = work_dir
elif hasattr(self, 'work_dir') and self.work_dir:
pass
else:
self.work_dir = None
if self.work_dir:
step8_dir = os.path.join(self.work_dir, "8_QAA_Inversion")
if os.path.isdir(step8_dir):
candidates = []
for f in sorted(os.listdir(step8_dir)):
if f.lower().endswith('.csv'):
candidates.append(os.path.join(step8_dir, f))
if candidates:
self.input_file.set_path(candidates[0])
conc_dir = os.path.join(self.work_dir, "9_Concentration")
os.makedirs(conc_dir, exist_ok=True)
output_path = os.path.join(conc_dir, "final_concentrations.csv").replace('\\', '/')
self.output_file.set_path(output_path)
def run_step(self):
input_path = self.input_file.get_path()
if not input_path:
QMessageBox.warning(self, "输入错误", "请选择 QAA 结果文件!")
return
main_window = self.window()
if hasattr(main_window, 'run_single_step'):
config = {'step9_concentration': self.get_config()}
main_window.run_single_step('step9_concentration', config)
else:
self._run_concentration_direct()
def _run_concentration_direct(self):
from src.core.algorithms.concentration_inversion import ConcentrationPipeline
input_path = self.input_file.get_path()
output_path = self.output_file.get_path()
if not output_path:
work_dir = self._get_default_work_dir()
conc_dir = os.path.join(work_dir, "9_Concentration") if work_dir else ""
if conc_dir and not os.path.isdir(conc_dir):
os.makedirs(conc_dir, exist_ok=True)
output_path = os.path.join(conc_dir, "final_concentrations.csv").replace('\\', '/')
lake_case_map = {
0: "medium",
1: "oligotrophic_clear",
2: "bloom_dominant",
3: "turbid_mixed",
}
lake_case = lake_case_map.get(self.lake_case_combo.currentIndex(), "medium")
try:
pipeline = ConcentrationPipeline(lake_case=lake_case)
result_csv = pipeline.run_pipeline(input_path, output_path)
QMessageBox.information(
self, "执行成功",
f"浓度反演完成!\n结果已保存到:\n{result_csv}"
)
except Exception as e:
QMessageBox.critical(self, "执行错误", f"浓度反演失败:\n{str(e)}")

View File

@ -1918,8 +1918,8 @@ class WaterQualityGUI(QMainWindow):
self.step3_panel = Step3Panel()
self.step_stack.addTab(self.create_scroll_area(self.step3_panel), QIcon(self.get_icon_path("3.png")), "耀斑去除")
self.step4_panel = Step4SamplingPanel()
self.step_stack.addTab(self.create_scroll_area(self.step4_panel), QIcon(self.get_icon_path("4.png")), "采样点布设")
self.step4_sampling_panel = Step4SamplingPanel()
self.step_stack.addTab(self.create_scroll_area(self.step4_sampling_panel), QIcon(self.get_icon_path("4.png")), "采样点布设")
self.step5_clean_panel = Step5CleanPanel()
self.step_stack.addTab(self.create_scroll_area(self.step5_clean_panel), QIcon(self.get_icon_path("5.png")), "数据清洗")
@ -2153,7 +2153,7 @@ class WaterQualityGUI(QMainWindow):
# Step4采样点布设切换时自动填充输出路径
elif index == 3:
self.step4_panel.update_from_config(work_dir=self.work_dir)
self.step4_sampling_panel.update_from_config(work_dir=self.work_dir)
# Step5数据清洗切换时自动填充数据流转路径
elif index == 4: