fix(ui):修复 3 项 UI 交互痛点(输出路径不显示 / Step4 预览缺失 / Step5 CSV NaN 报错)
1. base_view.py:update_from_config 路由修复
原默认实现只缓存 work_dir + pipeline,不触发 update_work_directory,
导致所有派生 view 的输出路径自动填入从未执行。
补一行 self.update_work_directory(work_dir) 后,13 个 view 全部受益。
2. step4_view.py:恢复采样点交互式预览
从旧 panel 移植 preview_btn 按钮 + QTimer 2s 心跳(_check_csv_exists)
+ SamplingViewerDialog 弹窗。
用户在执行 Step 4 后点击按钮即可点击散点查看各采样点光谱曲线。
3. step5_view.py:CSV 预览 NaN 崩溃修复
pd.read_csv(csv_path, nrows=n) → pd.read_csv(csv_path, nrows=n).fillna("")。
避免底层 Qt 模型在解析 float64 空值时崩溃(PandasTableModel 路径必填)。
This commit is contained in:
@ -47,9 +47,16 @@ class BaseView(QWidget):
|
||||
self.work_dir = work_dir
|
||||
|
||||
def update_from_config(self, work_dir: str, pipeline=None):
|
||||
"""主窗口推送全局状态——子类可重写以联动 pipeline 状态"""
|
||||
"""主窗口推送全局状态——子类可重写以联动 pipeline 状态
|
||||
|
||||
默认实现:缓存 work_dir + pipeline 后,**自动调用 update_work_directory**
|
||||
以触发各 view 的输出路径自动填入。这是 main_view.set_work_dir 的唯一入口,
|
||||
必须让派生类的 update_work_directory 真正执行;子类如需联动 pipeline
|
||||
可在 update_work_directory 里读取 self.pipeline。
|
||||
"""
|
||||
self.work_dir = work_dir
|
||||
self.pipeline = pipeline
|
||||
self.update_work_directory(work_dir)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 核心通讯:沿父链向上查找 run_single_step 容器
|
||||
|
||||
@ -2,19 +2,24 @@
|
||||
"""
|
||||
Step4View —— Step 4(采样点布设)的端到端模块化 view
|
||||
|
||||
UI 从 ``src/gui/panels/step4_sampling_panel.py`` 原样搬迁。
|
||||
注:原 panel 中的 QTimer 心跳检查(``_check_csv_exists``)和交互式预览按钮
|
||||
在 view 层留接口位,运行时实际由 main_view 的 dispatch_execute 路由到 service
|
||||
触发状态更新(后续 service 迁移时再补全交互逻辑)。
|
||||
UI 从 ``src/gui/panels/step4_sampling_panel.py`` 原样搬迁,包括:
|
||||
|
||||
- 去耀斑影像 / 水域掩膜 / 采样参数 / 输出路径输入控件
|
||||
- 独立运行按钮(绿色 success 样式)
|
||||
- **交互式预览按钮 + QTimer 心跳**——CSV 生成后点击即弹
|
||||
``SamplingViewerDialog``,可点击散点查看各采样点光谱曲线。
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
from PyQt5.QtWidgets import (
|
||||
QCheckBox, QFormLayout, QGroupBox, QPushButton, QSpinBox, QVBoxLayout,
|
||||
QCheckBox, QFormLayout, QGroupBox, QMessageBox, QPushButton, QSpinBox,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from src.gui.components.custom_widgets import FileSelectWidget
|
||||
from src.gui.dialogs import SamplingViewerDialog
|
||||
from src.gui.styles import ModernStylesheet
|
||||
from src.new.core.base_view import BaseView
|
||||
|
||||
@ -88,9 +93,23 @@ class Step4View(BaseView):
|
||||
self.run_btn.clicked.connect(self._on_run_clicked)
|
||||
layout.addWidget(self.run_btn)
|
||||
|
||||
# 交互式预览按钮(旧 panel 移植:仅在 CSV 已生成时启用)
|
||||
self.preview_btn = QPushButton("📊 交互式预览采样点与光谱")
|
||||
self.preview_btn.setEnabled(False)
|
||||
self.preview_btn.clicked.connect(self._open_sampling_viewer)
|
||||
layout.addWidget(self.preview_btn)
|
||||
|
||||
layout.addStretch()
|
||||
self.setLayout(layout)
|
||||
|
||||
# 心跳定时器:每 2 秒检查一次输出 CSV 是否已生成,刷新预览按钮
|
||||
self._status_timer = QTimer(self)
|
||||
self._status_timer.timeout.connect(self._check_csv_exists)
|
||||
self._status_timer.start(2000)
|
||||
|
||||
# 输出路径输入框变化时同步刷新预览按钮
|
||||
self.output_file.line_edit.textChanged.connect(self._on_output_changed)
|
||||
|
||||
def get_config(self) -> dict:
|
||||
config = {
|
||||
"interval": self.interval.value(),
|
||||
@ -131,6 +150,36 @@ class Step4View(BaseView):
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
default_output_path = os.path.join(output_dir, "sampling_spectra.csv").replace("\\", "/")
|
||||
self.output_file.set_path(default_output_path)
|
||||
# 路径刚自动填充,立即同步一次预览按钮状态
|
||||
self._check_csv_exists()
|
||||
|
||||
def _on_run_clicked(self):
|
||||
self.dispatch_execute("step4", self.get_config())
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 采样点预览:CSV 存在才启用;点击弹窗 SamplingViewerDialog
|
||||
# (从旧 src/gui/panels/step4_sampling_panel.py 原样移植)
|
||||
# ------------------------------------------------------------------
|
||||
def _check_csv_exists(self) -> bool:
|
||||
"""检查输出 CSV 是否存在,驱动预览按钮启停"""
|
||||
csv_path = self.output_file.get_path()
|
||||
enabled = bool(csv_path and os.path.isabs(csv_path) and os.path.exists(csv_path))
|
||||
self.preview_btn.setEnabled(enabled)
|
||||
return enabled
|
||||
|
||||
def _on_output_changed(self, _text=None):
|
||||
"""输出路径输入框内容变化时同步刷新预览按钮"""
|
||||
self._check_csv_exists()
|
||||
|
||||
def _open_sampling_viewer(self):
|
||||
"""打开交互式采样点查看器弹窗"""
|
||||
csv_path = self.output_file.get_path()
|
||||
if not csv_path or not os.path.exists(csv_path):
|
||||
QMessageBox.warning(
|
||||
self, "文件不存在",
|
||||
f"采样点 CSV 文件不存在:{csv_path}\n请先执行 Step 4 生成数据。",
|
||||
)
|
||||
return
|
||||
dialog = SamplingViewerDialog(csv_path, self)
|
||||
dialog.exec_()
|
||||
self._check_csv_exists()
|
||||
|
||||
@ -3,17 +3,20 @@
|
||||
Step5View —— Step 5(数据清洗)的端到端模块化 view
|
||||
|
||||
UI 从 ``src/gui/panels/step5_clean_panel.py`` 原样搬迁;CSV 预览表格在
|
||||
view 层保留静态占位(运行时由 main_view 控制实际刷新逻辑)。
|
||||
view 层挂上旧版 ``PandasTableModel``,点击"刷新预览"即可读取前 N 行
|
||||
渲染到 ``QTableView``,完全本地化,不触发任何 service。
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import pandas as pd
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QCheckBox, QGroupBox, QHBoxLayout, QHeaderView,
|
||||
QLabel, QPushButton, QSpinBox, QTableView, QVBoxLayout,
|
||||
)
|
||||
|
||||
from src.gui.components.custom_widgets import FileSelectWidget
|
||||
from src.gui.components.data_models import PandasTableModel
|
||||
from src.gui.styles import ModernStylesheet
|
||||
from src.new.core.base_view import BaseView
|
||||
|
||||
@ -39,7 +42,7 @@ class Step5View(BaseView):
|
||||
hint.setStyleSheet("color: #666; font-size: 10px;")
|
||||
layout.addWidget(hint)
|
||||
|
||||
# CSV 数据预览(静态占位)
|
||||
# CSV 数据预览(实时 pandas 渲染 + PandasTableModel)
|
||||
preview_group = QGroupBox("CSV数据预览")
|
||||
preview_layout = QVBoxLayout()
|
||||
controls_layout = QHBoxLayout()
|
||||
@ -49,7 +52,10 @@ class Step5View(BaseView):
|
||||
self.preview_rows_spin.setValue(10)
|
||||
controls_layout.addWidget(self.preview_rows_spin)
|
||||
self.preview_btn = QPushButton("刷新预览")
|
||||
self.preview_btn.setEnabled(False) # 后续 service 迁移时再启用
|
||||
self.preview_btn.setStyleSheet(
|
||||
ModernStylesheet.get_button_stylesheet("primary")
|
||||
)
|
||||
self.preview_btn.clicked.connect(self._on_preview_clicked)
|
||||
controls_layout.addWidget(self.preview_btn)
|
||||
controls_layout.addStretch()
|
||||
|
||||
@ -118,3 +124,30 @@ class Step5View(BaseView):
|
||||
|
||||
def _on_run_clicked(self):
|
||||
self.dispatch_execute("step5", self.get_config())
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# CSV 预览:pandas → PandasTableModel → QTableView
|
||||
# ------------------------------------------------------------------
|
||||
def _on_preview_clicked(self):
|
||||
csv_path = self.csv_file.get_path()
|
||||
if not csv_path:
|
||||
self.preview_status_label.setText("⚠ 请先选择 CSV 文件")
|
||||
self.preview_table.setModel(None)
|
||||
return
|
||||
if not os.path.isfile(csv_path):
|
||||
self.preview_status_label.setText(f"⚠ 文件不存在: {csv_path}")
|
||||
self.preview_table.setModel(None)
|
||||
return
|
||||
try:
|
||||
n = int(self.preview_rows_spin.value())
|
||||
# .fillna("") 把所有 NaN 替换为空字符串,避免底层 Qt 模型在
|
||||
# 解析 float64 空值时崩溃(pandas → PandasTableModel 路径必填)
|
||||
df = pd.read_csv(csv_path, nrows=n).fillna("")
|
||||
model = PandasTableModel(df)
|
||||
self.preview_table.setModel(model)
|
||||
self.preview_status_label.setText(
|
||||
f"✓ 已加载 {len(df)} 行 / {len(df.columns)} 列(来源: {os.path.basename(csv_path)})"
|
||||
)
|
||||
except Exception as e: # noqa: BLE001 —— 预览失败时降级提示,不影响主流程
|
||||
self.preview_status_label.setText(f"⚠ 读取失败: {e}")
|
||||
self.preview_table.setModel(None)
|
||||
|
||||
Reference in New Issue
Block a user