imput导入修正,去除掉废弃内容

This commit is contained in:
duxin
2026-06-30 11:32:40 +08:00
parent f05916bc3a
commit 8ff5b08190
13 changed files with 232 additions and 191 deletions

View File

@ -15,16 +15,11 @@ Step10 面板 - 水色指数反演(散点 CSV 模式)
""" """
import os import os
import sys
import traceback import traceback
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional from typing import Dict, List, Optional
# 路径归一化 helper(与 pipeline.get_step_output_dir 互为表里) from src.gui.panels._step_path_resolver import resolve_subdir, scan_work_dir_for_input
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from _step_path_resolver import resolve_subdir, scan_work_dir_for_input
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QFormLayout, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QFormLayout,

View File

@ -5,15 +5,10 @@ Step10 面板 - 专题图生成
""" """
import os import os
import sys
import traceback import traceback
from pathlib import Path from pathlib import Path
from typing import List, Optional from typing import List, Optional
# 路径归一化 helper(与 pipeline.get_step_output_dir 互为表里)
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from src.gui.panels._step_path_resolver import resolve_subdir, get_step_output_path, scan_work_dir_for_input from src.gui.panels._step_path_resolver import resolve_subdir, get_step_output_path, scan_work_dir_for_input
from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtCore import Qt, QThread, pyqtSignal
@ -649,13 +644,13 @@ class Step11MapPanel(QWidget):
out_dir = resolve_subdir(self._get_default_work_dir(), 'visualization') out_dir = resolve_subdir(self._get_default_work_dir(), 'visualization')
os.makedirs(out_dir, exist_ok=True) os.makedirs(out_dir, exist_ok=True)
tif_stem = Path(geotiff_path).stem tif_stem = Path(geotiff_path).stem
chinese_name = mapper._get_chinese_title(tif_stem)
output_png = os.path.join(out_dir, f"{chinese_name}_专题图.png")
self.run_button.setEnabled(False) self.run_button.setEnabled(False)
try: try:
from src.postprocessing.map import ContentMapper from src.postprocessing.map import ContentMapper
mapper = ContentMapper() mapper = ContentMapper()
chinese_name = mapper._get_chinese_title(tif_stem)
output_png = os.path.join(out_dir, f"{chinese_name}_专题图.png")
result_path = mapper.visualize_raster( result_path = mapper.visualize_raster(
raster_tif_path=geotiff_path, raster_tif_path=geotiff_path,
output_file=output_png, output_file=output_png,

View File

@ -6,7 +6,6 @@ VisualizationPanel - 可视化分析面板
""" """
import os import os
import sys
import traceback import traceback
from pathlib import Path from pathlib import Path
from typing import Optional, List, Union from typing import Optional, List, Union
@ -14,11 +13,7 @@ from typing import Optional, List, Union
import numpy as np import numpy as np
import pandas as pd import pandas as pd
# 路径归一化 helper(与 pipeline.get_step_output_dir 互为表里) from src.gui.panels._step_path_resolver import get_step_output_path, resolve_step_widget, resolve_subdir
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from _step_path_resolver import get_step_output_path, resolve_step_widget, resolve_subdir
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal, QAbstractTableModel from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal, QAbstractTableModel
from PyQt5.QtGui import QPixmap from PyQt5.QtGui import QPixmap
@ -1716,7 +1711,7 @@ class Step12VizPanel(QWidget):
factory = getattr(main_window, '_panel_factory', None) if main_window else None factory = getattr(main_window, '_panel_factory', None) if main_window else None
# [新增] 直接从 Step 1 面板读取原始 .shp 的绝对路径,突破工作目录限制 # [新增] 直接从 Step 1 面板读取原始 .shp 的绝对路径,突破工作目录限制
step1_panel = factory.get_panel('step1_mask') if factory else None step1_panel = factory.get_panel('step1') if factory else None
if step1_panel: if step1_panel:
s1_conf = step1_panel.get_config() s1_conf = step1_panel.get_config()
s1_mask = s1_conf.get('mask_path') s1_mask = s1_conf.get('mask_path')

View File

@ -5,16 +5,11 @@ ReportGenerationPanel - Word 分析报告生成面板
""" """
import os import os
import sys
import traceback import traceback
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
# 路径归一化 helper(与 pipeline.get_step_output_dir 互为表里) from src.gui.panels._step_path_resolver import resolve_subdir
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from _step_path_resolver import resolve_subdir
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (

View File

@ -5,13 +5,9 @@ Step1 面板 - 水域掩膜生成 (已移除“启用此步骤”)
""" """
import os import os
import sys
from pathlib import Path from pathlib import Path
_HERE = os.path.dirname(os.path.abspath(__file__)) from src.gui.panels._step_path_resolver import resolve_subdir
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from _step_path_resolver import resolve_subdir
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel, QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel,

View File

@ -5,13 +5,9 @@ Step2 面板 - 耀斑区域识别 (已移除“启用此步骤”)
""" """
import os import os
import sys
from pathlib import Path from pathlib import Path
_HERE = os.path.dirname(os.path.abspath(__file__)) from src.gui.panels._step_path_resolver import resolve_subdir, scan_work_dir_for_input
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from _step_path_resolver import resolve_subdir, scan_work_dir_for_input
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout, QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout,

View File

@ -5,13 +5,9 @@ Step3 面板 - 耀斑去除 (已移除“启用此步骤”)
""" """
import os import os
import sys
from pathlib import Path from pathlib import Path
_HERE = os.path.dirname(os.path.abspath(__file__)) from src.gui.panels._step_path_resolver import resolve_subdir, scan_work_dir_for_input
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from _step_path_resolver import resolve_subdir, scan_work_dir_for_input
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout, QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout,
@ -268,7 +264,7 @@ class Step3Panel(QWidget):
form_layout.addRow(row_layout) form_layout.addRow(row_layout)
def open_interactive_viewer(self): def open_interactive_viewer(self):
from src.gui.water_quality_gui import InteractiveViewerDialog from src.gui.components.chart_dialogs import InteractiveViewerDialog
img_path = self.img_file.get_path() img_path = self.img_file.get_path()
if not img_path or not os.path.isfile(img_path): if not img_path or not os.path.isfile(img_path):
QMessageBox.warning(self, "警告", "请先选择影像文件!") QMessageBox.warning(self, "警告", "请先选择影像文件!")
@ -381,7 +377,7 @@ class Step3Panel(QWidget):
config['sugar_iter'] = self.sugar_iter.value() if self.sugar_iter.value() > 0 else None config['sugar_iter'] = self.sugar_iter.value() if self.sugar_iter.value() > 0 else None
config['sugar_sigma'] = self._safe_float(self.sugar_sigma, 1.0) config['sugar_sigma'] = self._safe_float(self.sugar_sigma, 1.0)
config['sugar_estimate_background'] = self.sugar_estimate_background.isChecked() config['sugar_estimate_background'] = self.sugar_estimate_background.isChecked()
config['sugar_glint_mask_method'] = self.sugar_glint_mask_method.currentData() config['sugar_glint_mask_method'] = self.sugar_glint_mask_method.currentText()
config['sugar_termination_thresh'] = self._safe_float(self.sugar_termination_thresh, 20.0) config['sugar_termination_thresh'] = self._safe_float(self.sugar_termination_thresh, 20.0)
try: try:
import ast import ast

View File

@ -12,7 +12,6 @@ Step4 面板 - 采样点布设(内嵌交互式光谱探针视图)
""" """
import os import os
import sys
from pathlib import Path from pathlib import Path
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
@ -23,10 +22,7 @@ import pandas as pd
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'sans-serif'] plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'sans-serif']
plt.rcParams['axes.unicode_minus'] = False plt.rcParams['axes.unicode_minus'] = False
_HERE = os.path.dirname(os.path.abspath(__file__)) from src.gui.panels._step_path_resolver import resolve_subdir, scan_work_dir_for_input
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from _step_path_resolver import resolve_subdir, scan_work_dir_for_input
from PyQt5.QtCore import QTimer, Qt from PyQt5.QtCore import QTimer, Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (

View File

@ -5,14 +5,9 @@ Step5 面板 - 数据清洗 (完美对齐无跳动重构版)
""" """
import os import os
import sys
from pathlib import Path from pathlib import Path
# 路径归一化 helper from src.gui.panels._step_path_resolver import resolve_subdir
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from _step_path_resolver import resolve_subdir
import pandas as pd import pandas as pd
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
@ -85,22 +80,32 @@ class Step5CleanPanel(QWidget):
controls_layout = QHBoxLayout() controls_layout = QHBoxLayout()
controls_layout.setContentsMargins(0, 0, 0, 0) controls_layout.setContentsMargins(0, 0, 0, 0)
# ── 【核心修复】:剥离 Suffix,恢复微调按钮,提升输入体验 ──
lbl = QLabel("加载预览行数:")
lbl.setMinimumWidth(100)
controls_layout.addWidget(lbl)
self.preview_rows_spin = QSpinBox() self.preview_rows_spin = QSpinBox()
self.preview_rows_spin.setRange(1, 200) self.preview_rows_spin.setRange(1, 1000) # 放宽一下预览上限
self.preview_rows_spin.setValue(10) self.preview_rows_spin.setValue(10)
self.preview_rows_spin.setSuffix(" 行") self.preview_rows_spin.setAlignment(Qt.AlignCenter) # 数字居中,体验更好
self.preview_rows_spin.setButtonSymbols(QSpinBox.NoButtons) self.preview_rows_spin.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.preview_rows_spin.setMinimumWidth(80) self.preview_rows_spin.setMinimumWidth(90)
controls_layout.addWidget(self.preview_rows_spin)
suffix_lbl = QLabel("行") # 将单位作为独立标签,避免光标被困住
controls_layout.addWidget(suffix_lbl)
controls_layout.addSpacing(16)
self.preview_btn = QPushButton("刷新预览") self.preview_btn = QPushButton("刷新预览")
self.preview_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('normal')) self.preview_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('normal'))
self.preview_btn.setMinimumWidth(100) self.preview_btn.setMinimumWidth(100)
self.preview_btn.clicked.connect(self.load_csv_preview) self.preview_btn.clicked.connect(self.load_csv_preview)
# 使用封装的方法实现严格对齐的行
self._add_row_with_fixed_label(controls_layout, "加载预览行数:", self.preview_rows_spin)
controls_layout.addWidget(self.preview_btn) controls_layout.addWidget(self.preview_btn)
controls_layout.addStretch() controls_layout.addStretch()
# ─────────────────────────────────────────────────────────
self.preview_table = QTableView() self.preview_table = QTableView()
self.preview_table.setEditTriggers(QAbstractItemView.NoEditTriggers) self.preview_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
@ -155,7 +160,7 @@ class Step5CleanPanel(QWidget):
self.reset_preview() self.reset_preview()
def _add_row_with_fixed_label(self, layout, label_text, widget): def _add_row_with_fixed_label(self, layout, label_text, widget):
"""辅助方法:创建绝对锁死宽度的对齐行""" """辅助方法:创建绝对锁死宽度的对齐行 (保留原始代码结构) - 已废弃,保留仅用于兼容"""
lbl = QLabel(label_text) lbl = QLabel(label_text)
lbl.setMinimumWidth(100) lbl.setMinimumWidth(100)
layout.addWidget(lbl) layout.addWidget(lbl)
@ -215,19 +220,12 @@ class Step5CleanPanel(QWidget):
def reset_preview(self, message="请选择 CSV 文件并点击刷新预览"): def reset_preview(self, message="请选择 CSV 文件并点击刷新预览"):
"""重置预览表格""" """重置预览表格"""
try: try:
from src.gui.water_quality_gui import PandasTableModel from src.gui.components.data_models import PandasTableModel
empty_model = PandasTableModel(pd.DataFrame()) empty_model = PandasTableModel(pd.DataFrame())
self.preview_table.setModel(empty_model) self.preview_table.setModel(empty_model)
self.preview_status_label.setText(message) self.preview_status_label.setText(message)
except ImportError: except ImportError:
# 兼容水质软件 V2 版的重构包结构 self.preview_status_label.setText("数据预览模块加载失败")
try:
from src.new.main_view import PandasTableModel
empty_model = PandasTableModel(pd.DataFrame())
self.preview_table.setModel(empty_model)
self.preview_status_label.setText(message)
except ImportError:
self.preview_status_label.setText("数据预览模块加载失败,请检查 PandasTableModel 依赖")
def load_csv_preview(self): def load_csv_preview(self):
"""加载 CSV 预览数据""" """加载 CSV 预览数据"""
@ -240,10 +238,7 @@ class Step5CleanPanel(QWidget):
return return
try: try:
from src.gui.water_quality_gui import PandasTableModel from src.gui.components.data_models import PandasTableModel
except ImportError:
try:
from src.new.main_view import PandasTableModel
except ImportError: except ImportError:
self.reset_preview("数据预览模块加载失败") self.reset_preview("数据预览模块加载失败")
return return

View File

@ -5,13 +5,8 @@ Step6 面板 - 光谱特征提取 (完美对齐无跳动重构版)
""" """
import os import os
import sys
from pathlib import Path from pathlib import Path
# 路径归一化 helper
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from src.gui.panels._step_path_resolver import resolve_subdir, scan_work_dir_for_input from src.gui.panels._step_path_resolver import resolve_subdir, scan_work_dir_for_input
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (

View File

@ -6,7 +6,6 @@ Step7 视图 - 水质光谱指数计算 (完美对齐卡片化重构版)
import os import os
import sys import sys
import csv
from pathlib import Path from pathlib import Path
import pandas as pd import pandas as pd
@ -25,7 +24,7 @@ from src.gui.components.custom_widgets import FileSelectWidget
from src.gui.styles import ModernStylesheet from src.gui.styles import ModernStylesheet
# ========================================== # ==========================================
# 模块级字典 + 防穿透 ListWidget(与 Step10 对齐) # 模块级字典 + 防穿透 ListWidget
# ========================================== # ==========================================
CATEGORY_CHINESE_MAP = { CATEGORY_CHINESE_MAP = {
'Total_Suspended_Matter': '总悬浮物 (TSM)', 'Total_Suspended_Matter': '总悬浮物 (TSM)',
@ -43,7 +42,8 @@ CATEGORY_CHINESE_MAP = {
class NoScrollPassListWidget(QListWidget): class NoScrollPassListWidget(QListWidget):
"""一个绝对不会把滚轮事件传给外层父组件的列表控件(与 Step10 同源)。""" """一个绝对不会把滚轮事件传给外层父组件的列表控件"""
def wheelEvent(self, event): def wheelEvent(self, event):
super().wheelEvent(event) super().wheelEvent(event)
event.accept() event.accept()
@ -52,7 +52,7 @@ class NoScrollPassListWidget(QListWidget):
class Step7InversionPanel(QWidget): class Step7InversionPanel(QWidget):
"""步骤7:水质光谱指数计算""" """步骤7:水质光谱指数计算"""
# --- 保持原有的类级别映射表不变 --- # --- 保留原有的类级别映射表作为兜底 ---
_formula_type_map = { _formula_type_map = {
'NDWI': 'ratio', 'MNDWI': 'ratio', 'AWEI': 'ratio', 'AWEInsh': 'ratio', 'NDWI': 'ratio', 'MNDWI': 'ratio', 'AWEI': 'ratio', 'AWEInsh': 'ratio',
'NDTI': 'ratio', 'WRI': 'ratio', 'NDSS': 'ratio', 'NDSSI': 'ratio', 'NDTI': 'ratio', 'WRI': 'ratio', 'NDSS': 'ratio', 'NDSSI': 'ratio',
@ -74,48 +74,10 @@ class Step7InversionPanel(QWidget):
'COD_4': 'concentration' 'COD_4': 'concentration'
} }
_formula_color_map = {
'ratio': ModernStylesheet.COLORS['panel_bg'], # 比值型用白色底
'concentration': ModernStylesheet.COLORS['primary_light'] # 浓度型用浅蓝底
}
_formula_coef_map = {
'Chla_3B': [25.0456, 116.8924],
'Chla_2B': [5.8753, 56.4025],
'Chla_2B_sim': [38.16, 21.36],
'Chla_4B': [27.79, 137.9],
'Chla_NDCI': [14.039, 86.115, 194.325],
'Chla_NDI': [1.321, 23.94],
'Chla_Peak': [3.614, 5.097],
'Chla_SABG': [10.2, 5.6],
'Chla_MCI': [0.5, 0.2],
'Chla_FAI': [0.8, 0.1],
'TP_1': [0.1, 0.05],
'TP_2': [0.2, 0.1],
'TP_3': [0.15, 0.08],
'TP_4': [0.12, 0.06],
'TP_5': [0.18, 0.09],
'TP_6': [0.25, 0.12],
'TN_1': [0.5, 0.2],
'TN_2': [0.6, 0.3],
'TN_3': [0.45, 0.15],
'TN_4': [0.55, 0.25],
'NH3_1': [0.05, 0.02],
'NH3_2': [0.06, 0.03],
'NH3_3': [0.04, 0.015],
'NH3_4': [0.07, 0.035],
'COD_1': [10.0, 5.0],
'COD_2': [12.0, 6.0],
'COD_3': [8.5, 4.0],
'COD_4': [11.0, 5.5],
}
def __init__(self, parent=None): def __init__(self, parent=None):
self._formula_df = None self._formula_df = None
self._categories = []
self._current_type_filter = "all" # 'all', 'ratio', 'concentration'
super().__init__(parent) super().__init__(parent)
self.init_ui() self.init_ui()
@ -123,7 +85,7 @@ class Step7InversionPanel(QWidget):
# 注入全局样式 # 注入全局样式
self.setStyleSheet(ModernStylesheet.get_main_stylesheet()) self.setStyleSheet(ModernStylesheet.get_main_stylesheet())
# 主布局:增加四周留白(24px)和呼吸间距(20px) # 主布局
main_layout = QVBoxLayout(self) main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(24, 24, 24, 24) main_layout.setContentsMargins(24, 24, 24, 24)
main_layout.setSpacing(20) main_layout.setSpacing(20)
@ -157,7 +119,6 @@ class Step7InversionPanel(QWidget):
self.formula_file.line_edit.setReadOnly(True) self.formula_file.line_edit.setReadOnly(True)
self.formula_file.label.setMinimumWidth(120) self.formula_file.label.setMinimumWidth(120)
# 智能查找自带的公式库文件(兼容开发 + PyInstaller 打包)
builtin_csv = self._resolve_builtin_csv() builtin_csv = self._resolve_builtin_csv()
if builtin_csv: if builtin_csv:
self.formula_file.set_path(builtin_csv) self.formula_file.set_path(builtin_csv)
@ -181,7 +142,6 @@ class Step7InversionPanel(QWidget):
params_layout.setSpacing(16) params_layout.setSpacing(16)
params_layout.setContentsMargins(20, 24, 20, 20) params_layout.setContentsMargins(20, 24, 20, 20)
# 辅助布局:公式选择列表
list_row = QHBoxLayout() list_row = QHBoxLayout()
list_row.setContentsMargins(0, 0, 0, 0) list_row.setContentsMargins(0, 0, 0, 0)
@ -197,7 +157,17 @@ class Step7InversionPanel(QWidget):
# 按钮工具栏 # 按钮工具栏
btn_layout = QHBoxLayout() btn_layout = QHBoxLayout()
btn_layout.setContentsMargins(0, 0, 0, 0) btn_layout.setContentsMargins(0, 0, 0, 0)
btn_layout.setSpacing(10) # 👉 [新增] 增加按钮之间的水平间距,不再挤在一起 btn_layout.setSpacing(10)
# 👉 【新增】: 按类别筛选下拉框 (对齐 Step 10)
btn_layout.addWidget(QLabel("按类别筛选:"))
self.category_combo = QComboBox()
self.category_combo.setStyleSheet("""
QComboBox { padding: 4px 8px; border: 1px solid #C0C0C0; border-radius: 4px; min-height: 24px; }
""")
self.category_combo.currentIndexChanged.connect(self._on_category_changed)
btn_layout.addWidget(self.category_combo)
btn_layout.addSpacing(10)
self.btn_select_all = QPushButton("全选") self.btn_select_all = QPushButton("全选")
self.btn_select_none = QPushButton("清空") self.btn_select_none = QPushButton("清空")
@ -207,26 +177,28 @@ class Step7InversionPanel(QWidget):
for btn in [self.btn_select_all, self.btn_select_none, for btn in [self.btn_select_all, self.btn_select_none,
self.btn_select_ratio, self.btn_select_conc, self.btn_reload]: self.btn_select_ratio, self.btn_select_conc, self.btn_reload]:
base_style = ModernStylesheet.get_button_stylesheet('normal') base_style = ModernStylesheet.get_button_stylesheet('normal')
# 👉 [修改] 追加左右内边距 (padding-left/right),保证无论文字长短都不会被截断
btn.setStyleSheet(base_style + "\nQPushButton { padding-left: 14px; padding-right: 14px; }") btn.setStyleSheet(base_style + "\nQPushButton { padding-left: 14px; padding-right: 14px; }")
# 👉 [修改] 高度从 28 稍微调高到 30,让中文字体上下有更好的呼吸感
btn.setFixedHeight(30) btn.setFixedHeight(30)
# 👉 [新增] 设置尺寸策略:高度固定,宽度根据文字内容自动撑开
btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
btn_layout.addWidget(btn) btn_layout.addWidget(btn)
btn_layout.addStretch() btn_layout.addStretch()
# 👉 【升级】: 统计标签 (显示选中、当前可见、总计)
self.formula_count_label = QLabel("已选: 0 | 显示: 0 / 共 0 个")
self.formula_count_label.setStyleSheet(
"color: #666666; font-size: 13px; font-weight: bold; padding-right: 10px;")
btn_layout.addWidget(self.formula_count_label)
right_layout.addLayout(btn_layout) right_layout.addLayout(btn_layout)
# 列表框 (去掉 setSelectionMode,改为默认行为) # 列表框 (使用自定义防穿透类)
self.formula_list = QListWidget() self.formula_list = NoScrollPassListWidget()
self.formula_list.setMinimumHeight(150) self.formula_list.setMinimumHeight(220) # 稍微加高以展示复杂内容
# 用样式表去掉默认的边框和丑陋的焦点框,增加 hover 和复选框大小调整 self.formula_list.setWordWrap(True) # 允许公式换行
# 禁用原生的 selected 高亮样式,并设置透明背景
self.formula_list.setStyleSheet(f""" self.formula_list.setStyleSheet(f"""
QListWidget {{ QListWidget {{
border: 1px solid {ModernStylesheet.COLORS['border']}; border: 1px solid {ModernStylesheet.COLORS['border']};
@ -235,19 +207,28 @@ class Step7InversionPanel(QWidget):
outline: none; outline: none;
}} }}
QListWidget::item {{ QListWidget::item {{
padding: 4px 8px; /* 稍微减小上下间距,给复选框留空间 */ padding: 8px 8px; /* 加大 padding 让多行显示更舒服 */
border-bottom: 1px solid {ModernStylesheet.COLORS['border_light']}; border-bottom: 1px solid {ModernStylesheet.COLORS['border_light']};
}} }}
QListWidget::item:hover {{ QListWidget::item:hover {{
background-color: {ModernStylesheet.COLORS['hover']}; background-color: {ModernStylesheet.COLORS['hover']};
}} }}
/* 放大复选框并拉开间距 */ QListWidget::item:selected {{
background-color: transparent;
color: #333333;
}}
QListWidget::indicator {{ QListWidget::indicator {{
width: 16px; width: 16px;
height: 16px; height: 16px;
margin-right: 6px; margin-right: 6px;
}} }}
""") """)
# 绑定状态改变事件
self.formula_list.itemChanged.connect(self._update_formula_count)
# 绑定行选中事件,实现点击整行打勾
self.formula_list.itemSelectionChanged.connect(self._on_selection_changed)
right_layout.addWidget(self.formula_list) right_layout.addWidget(self.formula_list)
list_row.addWidget(list_label) list_row.addWidget(list_label)
@ -300,14 +281,7 @@ class Step7InversionPanel(QWidget):
self._load_formulas_from_csv() self._load_formulas_from_csv()
def _resolve_builtin_csv(self): def _resolve_builtin_csv(self):
"""探测内置 waterindex.csv 的实际位置。 """探测内置 waterindex.csv 的实际位置。"""
按优先级探测 4 个候选路径(兼容开发 + PyInstaller 打包):
1. sys._MEIPASS/_internal/model/ (PyInstaller onedir 带 _MEIPASS)
2. sys._MEIPASS/model/ (PyInstaller onefile 或 onedir)
3. <panel_dir>/../model/ (开发模式真实位置:src/gui/model/)
4. <panel_dir>/../../model/ (项目根 model/,兼容旧布局)
"""
panel_dir = os.path.dirname(os.path.abspath(__file__)) panel_dir = os.path.dirname(os.path.abspath(__file__))
candidates = [ candidates = [
os.path.join(panel_dir, '..', 'model', 'waterindex.csv'), os.path.join(panel_dir, '..', 'model', 'waterindex.csv'),
@ -325,9 +299,8 @@ class Step7InversionPanel(QWidget):
def get_config(self) -> dict: def get_config(self) -> dict:
"""收集当前界面的配置字典""" """收集当前界面的配置字典"""
# 只筛选出打勾状态为 Checked 的项
selected_names = [ selected_names = [
self.formula_list.item(i).text() self.formula_list.item(i).data(Qt.UserRole) # 从隐藏属性中提取纯净的名称
for i in range(self.formula_list.count()) for i in range(self.formula_list.count())
if self.formula_list.item(i).checkState() == Qt.Checked if self.formula_list.item(i).checkState() == Qt.Checked
] ]
@ -336,7 +309,7 @@ class Step7InversionPanel(QWidget):
'training_csv_path': self.training_data_widget.get_path(), 'training_csv_path': self.training_data_widget.get_path(),
'formula_csv_file': self.formula_file.get_path(), 'formula_csv_file': self.formula_file.get_path(),
'formula_names': selected_names, 'formula_names': selected_names,
'enabled': True # 默认启用 'enabled': True
} }
output_path = self.output_file.get_path() output_path = self.output_file.get_path()
if output_path: if output_path:
@ -350,15 +323,16 @@ class Step7InversionPanel(QWidget):
if 'formula_csv_file' in config: if 'formula_csv_file' in config:
self.formula_file.set_path(config['formula_csv_file']) self.formula_file.set_path(config['formula_csv_file'])
# 恢复勾选状态
if 'formula_names' in config: if 'formula_names' in config:
target_names = set(config['formula_names']) target_names = set(config['formula_names'])
self.formula_list.blockSignals(True) self.formula_list.blockSignals(True)
for i in range(self.formula_list.count()): for i in range(self.formula_list.count()):
item = self.formula_list.item(i) item = self.formula_list.item(i)
state = Qt.Checked if item.text() in target_names else Qt.Unchecked item_name = item.data(Qt.UserRole)
state = Qt.Checked if item_name in target_names else Qt.Unchecked
item.setCheckState(state) item.setCheckState(state)
self.formula_list.blockSignals(False) self.formula_list.blockSignals(False)
self._update_formula_count()
if 'output_path' in config: if 'output_path' in config:
self.output_file.set_path(config['output_path']) self.output_file.set_path(config['output_path'])
@ -383,48 +357,180 @@ class Step7InversionPanel(QWidget):
break break
if not target_col: if not target_col:
QMessageBox.warning(self, "错误", f"CSV 文件格式不正确,列名应为 'Index_Name' 或 'Formula_Name'。当前列为: {list(df.columns)}") QMessageBox.warning(self, "错误",
f"CSV 文件格式不正确,列名应为 'Index_Name' 或 'Formula_Name'。当前列为: {list(df.columns)}")
self.formula_list.blockSignals(False) self.formula_list.blockSignals(False)
return return
for idx_name in df[target_col]: # 👉 【提取所有分类并更新下拉框】
item = QListWidgetItem(idx_name) cats = set()
for _, row in df.iterrows():
if 'Category' in df.columns and pd.notna(row['Category']):
cats.add(str(row['Category']).strip())
else:
cats.add("综合/未分类")
# 开启复选框功能,并设置为默认选中 self._categories = sorted(list(cats))
self.category_combo.blockSignals(True)
self.category_combo.clear()
self.category_combo.addItem("全部类别", "全部")
for cat in self._categories:
display_name = CATEGORY_CHINESE_MAP.get(cat, cat)
self.category_combo.addItem(display_name, cat)
self.category_combo.blockSignals(False)
# 👉 【构建美观的列表项】
for _, row in df.iterrows():
idx_name = str(row[target_col]).strip()
item = QListWidgetItem()
item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setCheckState(Qt.Checked) item.setCheckState(Qt.Checked)
# 解析公式和分类
formula_str = str(row['Formula']).strip() if 'Formula' in df.columns else ""
cat = str(row['Category']).strip() if 'Category' in df.columns and pd.notna(
row['Category']) else "综合/未分类"
# 解析精确的 Formula_Type
ftype = ''
if 'Formula_Type' in df.columns and pd.notna(row['Formula_Type']):
ftype = str(row['Formula_Type']).strip().lower()
if 'conc' in ftype:
ftype = 'concentration'
elif 'ratio' in ftype:
ftype = 'ratio'
else:
ftype = self._formula_type_map.get(idx_name, 'ratio') ftype = self._formula_type_map.get(idx_name, 'ratio')
# 简化底色判断,保留类型的色彩提示
bg_color = Qt.transparent if ftype == 'ratio' else Qt.GlobalColor.cyan # 保存隐式数据 (极其重要,用于过滤和配置提取)
item.setData(Qt.UserRole, idx_name)
item.setData(Qt.UserRole + 1, cat)
item.setData(Qt.UserRole + 2, ftype)
# 设置带分类和公式的美观文本 (对齐 Step 10)
cat_cn = CATEGORY_CHINESE_MAP.get(cat, cat)
ftype_cn = "比值型" if ftype == 'ratio' else "浓度型"
if formula_str:
item.setText(f"【{cat_cn}】 {idx_name} ({ftype_cn})\n公式: {formula_str}")
else:
item.setText(f"【{cat_cn}】 {idx_name} ({ftype_cn})")
# 设置底色
bg_color = QColor("#FFFFFF") if ftype == 'ratio' else QColor("#E8F4F8")
item.setBackground(bg_color) item.setBackground(bg_color)
self.formula_list.addItem(item) self.formula_list.addItem(item)
self.formula_list.blockSignals(False) self.formula_list.blockSignals(False)
self._update_formula_count()
except Exception as e: except Exception as e:
QMessageBox.critical(self, "加载错误", f"加载公式文件时发生错误:\n{str(e)}") QMessageBox.critical(self, "加载错误", f"加载公式文件时发生错误:\n{str(e)}")
def _select_all_formulas(self): def _refresh_visibility(self):
"""核心过滤引擎:根据下拉框和类型按钮动态隐藏/显示公式"""
current_category = self.category_combo.currentData()
if not current_category:
current_category = "全部"
for i in range(self.formula_list.count()): for i in range(self.formula_list.count()):
self.formula_list.item(i).setCheckState(Qt.Checked) item = self.formula_list.item(i)
item_cat = item.data(Qt.UserRole + 1)
item_type = item.data(Qt.UserRole + 2)
cat_match = (current_category == "全部" or item_cat == current_category)
type_match = (self._current_type_filter == "all" or item_type == self._current_type_filter)
if cat_match and type_match:
item.setHidden(False)
else:
item.setHidden(True)
self._update_formula_count()
def _on_category_changed(self, index: int):
"""下拉框改变分类时触发"""
self._current_type_filter = "all" # 切换分类时默认重置类型过滤
self._refresh_visibility()
def _update_formula_count(self):
"""计算并更新多维度统计标签"""
total = self.formula_list.count()
visible = 0
selected = 0
for i in range(total):
item = self.formula_list.item(i)
if not item.isHidden():
visible += 1
if item.checkState() == Qt.Checked:
selected += 1
type_str = ""
if self._current_type_filter == 'ratio':
type_str = " (仅比值型)"
elif self._current_type_filter == 'concentration':
type_str = " (仅浓度型)"
self.formula_count_label.setText(f"已选: {selected} | 显示: {visible}{type_str} / 共 {total} 个")
def _on_selection_changed(self):
"""实现点击整行任意位置即可切换勾选状态,并立刻取消高亮"""
self.formula_list.blockSignals(True)
for item in self.formula_list.selectedItems():
new_state = Qt.Unchecked if item.checkState() == Qt.Checked else Qt.Checked
item.setCheckState(new_state)
item.setSelected(False) # 立刻取消原生选中状态,避免变白
self.formula_list.blockSignals(False)
self._update_formula_count()
def _select_all_formulas(self):
"""全选并显示所有(当前分类下的)公式"""
self._current_type_filter = "all"
self._refresh_visibility()
self.formula_list.blockSignals(True)
for i in range(self.formula_list.count()):
item = self.formula_list.item(i)
if not item.isHidden():
item.setCheckState(Qt.Checked)
self.formula_list.blockSignals(False)
self._update_formula_count()
def _clear_formula_selection(self): def _clear_formula_selection(self):
"""只清空当前显示项的勾选"""
self.formula_list.blockSignals(True)
for i in range(self.formula_list.count()): for i in range(self.formula_list.count()):
self.formula_list.item(i).setCheckState(Qt.Unchecked) item = self.formula_list.item(i)
if not item.isHidden():
item.setCheckState(Qt.Unchecked)
self.formula_list.blockSignals(False)
self._update_formula_count()
def _select_ratio_formulas(self): def _select_ratio_formulas(self):
"""仅显示比值型,并勾选"""
self._current_type_filter = "ratio"
self._refresh_visibility()
self.formula_list.blockSignals(True)
for i in range(self.formula_list.count()): for i in range(self.formula_list.count()):
item = self.formula_list.item(i) item = self.formula_list.item(i)
ftype = self._formula_type_map.get(item.text(), 'ratio') if not item.isHidden():
item.setCheckState(Qt.Checked if ftype == 'ratio' else Qt.Unchecked) item.setCheckState(Qt.Checked)
self.formula_list.blockSignals(False)
self._update_formula_count()
def _select_conc_formulas(self): def _select_conc_formulas(self):
"""仅显示浓度型,并勾选"""
self._current_type_filter = "concentration"
self._refresh_visibility()
self.formula_list.blockSignals(True)
for i in range(self.formula_list.count()): for i in range(self.formula_list.count()):
item = self.formula_list.item(i) item = self.formula_list.item(i)
ftype = self._formula_type_map.get(item.text(), 'ratio') if not item.isHidden():
item.setCheckState(Qt.Checked if ftype == 'concentration' else Qt.Unchecked) item.setCheckState(Qt.Checked)
self.formula_list.blockSignals(False)
self._update_formula_count()
def _on_run_single_clicked(self): def _on_run_single_clicked(self):
"""点击独立运行按钮:包装参数并通过 EventBus 派发任务""" """点击独立运行按钮:包装参数并通过 EventBus 派发任务"""
@ -433,7 +539,6 @@ class Step7InversionPanel(QWidget):
QMessageBox.warning(self, "输入错误", "请先选择输入的样本数据 (CSV)!") QMessageBox.warning(self, "输入错误", "请先选择输入的样本数据 (CSV)!")
return return
# 检查是否有任何一项处于打勾状态
has_checked = any( has_checked = any(
self.formula_list.item(i).checkState() == Qt.Checked self.formula_list.item(i).checkState() == Qt.Checked
for i in range(self.formula_list.count()) for i in range(self.formula_list.count())
@ -444,7 +549,6 @@ class Step7InversionPanel(QWidget):
return return
config = self.get_config() config = self.get_config()
# 兼容旧架构的数据流,外层包装为 {'step_id': config}
payload = { payload = {
'step_name': 'step7_index', 'step_name': 'step7_index',
'config': {'step7_index': config} 'config': {'step7_index': config}
@ -452,12 +556,6 @@ class Step7InversionPanel(QWidget):
global_event_bus.publish('RequestRunSingleStep', payload) global_event_bus.publish('RequestRunSingleStep', payload)
def update_from_config(self, work_dir=None, pipeline=None): def update_from_config(self, work_dir=None, pipeline=None):
"""从全局配置/Pipeline 同步工作目录。
step6 的训练数据已由 PANEL_REGISTRY 的 dependencies 自动通过 set_config
注入到 self.training_data_widget;此处仅缓存 work_dir 并填入默认输出路径,
不重复拉取,避免与 panel_factory 注入路径冲突。
"""
if work_dir: if work_dir:
self.work_dir = work_dir self.work_dir = work_dir
elif hasattr(self, 'work_dir') and self.work_dir: elif hasattr(self, 'work_dir') and self.work_dir:
@ -465,7 +563,6 @@ class Step7InversionPanel(QWidget):
else: else:
self.work_dir = None self.work_dir = None
# 仅在输出框为空时填入默认路径(不创建目录,留给 pipeline 执行时创建)
if self.work_dir and not self.output_file.get_path(): if self.work_dir and not self.output_file.get_path():
output_dir = os.path.join(self.work_dir, "7_Water_Quality_Indices") output_dir = os.path.join(self.work_dir, "7_Water_Quality_Indices")
default_path = os.path.join(output_dir, "training_spectra_indices.csv").replace('\\', '/') default_path = os.path.join(output_dir, "training_spectra_indices.csv").replace('\\', '/')

View File

@ -5,13 +5,8 @@ Step8 面板 - 机器学习建模
""" """
import os import os
import sys
from pathlib import Path from pathlib import Path
# 路径归一化 helper(与 pipeline.get_step_output_dir 互为表里)
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from src.gui.panels._step_path_resolver import get_step_output_path, resolve_step_widget, resolve_subdir, scan_work_dir_for_input from src.gui.panels._step_path_resolver import get_step_output_path, resolve_step_widget, resolve_subdir, scan_work_dir_for_input
import pandas as pd import pandas as pd

View File

@ -5,16 +5,11 @@ Step11 面板 - 机器学习预测
""" """
import os import os
import sys
from pathlib import Path from pathlib import Path
import pandas as pd import pandas as pd
# 路径归一化 helper(与 pipeline.get_step_output_dir 互为表里) from src.gui.panels._step_path_resolver import get_step_output_path, resolve_step_widget, resolve_subdir, scan_work_dir_for_input
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from _step_path_resolver import get_step_output_path, resolve_step_widget, resolve_subdir, scan_work_dir_for_input
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout, QWidget, QVBoxLayout, QGroupBox, QFormLayout,