feat(gui): 一键运行智能预检
4 段预检彻底解决切换 PipelineRunner 后报 TypeError/静默跳过等问题, 并升级一键运行 UX: - 预检 1: work_path + log + scan + auto_populate(无需弹窗, 静默回填) - 预检 2: step3 波段越界 60s 倒计时弹窗(BandConfirmDialog) + gdal 主线程同步读 RasterCount, 越界时 SpinBox 回写 UI - 预检 3: img_path 硬校验(warning + 跳 step1 + return) - 预检 4: csv_path 软提示(information + 不 return, 让用户在 QMessageBox.question 二次确认时自己决定是否跳过训练) 新增 src/gui/dialogs.py: BandConfirmDialog(QDialog 子类, 60s 倒计时)
This commit is contained in:
147
src/gui/dialogs.py
Normal file
147
src/gui/dialogs.py
Normal file
@ -0,0 +1,147 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
自定义确认对话框集合
|
||||
|
||||
按"职责单一 + 不污染主 GUI 文件"原则拆分。
|
||||
与 water_quality_gui.py 保持 1:1 风格(中文注释 / 顶部 encoding 声明)。
|
||||
"""
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog,
|
||||
QLabel,
|
||||
QSpinBox,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QDialogButtonBox,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
|
||||
class BandConfirmDialog(QDialog):
|
||||
"""波段越界智能确认对话框(60 秒倒计时)
|
||||
|
||||
场景
|
||||
----
|
||||
用户在 step3 面板里设置了 nir_band=66,但实际影像只有 50 波段。
|
||||
Pipeline 一旦按 66 去取波段就会报 IndexError。
|
||||
|
||||
行为约定
|
||||
--------
|
||||
- 启动时 QTimer 开始 60s 倒计时,按钮文字同步显示 "确定 (Ns)"。
|
||||
- 用户手动调整 QSpinBox + 点"确定":立即 accept(),返回当前 spinbox 值。
|
||||
- 用户 60s 未操作:定时器归零时自动 accept(),返回当前 spinbox 值
|
||||
(**默认值为影像最大波段数 = 用户拿不到想要波段时的兜底**)。
|
||||
- 用户点"取消运行":reject(),调用方应中止 run_full_pipeline。
|
||||
"""
|
||||
|
||||
DEFAULT_TIMEOUT = 60 # 秒
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: QWidget = None,
|
||||
requested_band: int = 66,
|
||||
max_band: int = 50,
|
||||
recommended_band: int = 66,
|
||||
method_label: str = "NIR",
|
||||
timeout_seconds: int = DEFAULT_TIMEOUT,
|
||||
):
|
||||
super().__init__(parent)
|
||||
self._requested_band = requested_band
|
||||
self._max_band = max_band
|
||||
self._recommended_band = recommended_band
|
||||
self._method_label = method_label
|
||||
self._timeout_seconds = timeout_seconds
|
||||
self._remaining = timeout_seconds
|
||||
self._selected_band = max_band # 默认 = 最大波段(兜底)
|
||||
|
||||
self.setWindowTitle("波段索引越界")
|
||||
self.setModal(True)
|
||||
self.setMinimumWidth(420)
|
||||
self._init_ui()
|
||||
self._start_timer()
|
||||
|
||||
def _init_ui(self):
|
||||
"""搭建 UI:警告文字 + 灰色推荐 + SpinBox + 倒计时按钮"""
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# 1) 主提示(带 HTML 强调)
|
||||
self._msg_label = QLabel(
|
||||
f"影像仅有 <b>{self._max_band}</b> 个波段,"
|
||||
f"无法读取第 <b>{self._requested_band}</b> 波段({self._method_label})。"
|
||||
)
|
||||
self._msg_label.setWordWrap(True)
|
||||
self._msg_label.setFont(QFont("Microsoft YaHei", 10))
|
||||
layout.addWidget(self._msg_label)
|
||||
|
||||
# 2) 灰色小字推荐
|
||||
hint_label = QLabel(
|
||||
f"(推荐近红外波段序号:{self._recommended_band})"
|
||||
)
|
||||
hint_label.setStyleSheet("color: #888; font-size: 10px;")
|
||||
layout.addWidget(hint_label)
|
||||
|
||||
# 3) 波段选择 SpinBox(默认值 = 最大波段 = 超时兜底)
|
||||
spin_row = QHBoxLayout()
|
||||
spin_row.addWidget(QLabel(f"请选择要使用的{self._method_label}索引:"))
|
||||
self._spin = QSpinBox()
|
||||
self._spin.setRange(0, self._max_band)
|
||||
self._spin.setValue(self._max_band)
|
||||
self._spin.setSuffix(f" / 0~{self._max_band}")
|
||||
spin_row.addWidget(self._spin)
|
||||
spin_row.addStretch(1)
|
||||
layout.addLayout(spin_row)
|
||||
|
||||
# 4) 倒计时说明
|
||||
countdown_tip = QLabel(
|
||||
f"⏱ {self._timeout_seconds} 秒内不操作,将自动使用最大波段 "
|
||||
f"({self._max_band})继续运行。"
|
||||
)
|
||||
countdown_tip.setStyleSheet("color: #555; font-size: 9px;")
|
||||
countdown_tip.setWordWrap(True)
|
||||
layout.addWidget(countdown_tip)
|
||||
|
||||
# 5) 按钮组(手动"确定 (Ns)" + "取消运行")
|
||||
btn_box = QDialogButtonBox()
|
||||
self._ok_btn = QPushButton(f"确定 ({self._remaining}s)")
|
||||
self._ok_btn.setDefault(True)
|
||||
self._ok_btn.clicked.connect(self.accept)
|
||||
self._cancel_btn = QPushButton("取消运行")
|
||||
self._cancel_btn.clicked.connect(self.reject)
|
||||
btn_box.addButton(self._ok_btn, QDialogButtonBox.AcceptRole)
|
||||
btn_box.addButton(self._cancel_btn, QDialogButtonBox.RejectRole)
|
||||
layout.addWidget(btn_box)
|
||||
|
||||
def _start_timer(self):
|
||||
"""启动 1Hz 倒计时;归零时自动 accept()"""
|
||||
self._timer = QTimer(self)
|
||||
self._timer.setInterval(1000)
|
||||
self._timer.timeout.connect(self._tick)
|
||||
self._timer.start()
|
||||
|
||||
def _tick(self):
|
||||
"""每秒刷新按钮文字;归零时停表 + accept()"""
|
||||
self._remaining -= 1
|
||||
if self._remaining <= 0:
|
||||
self._timer.stop()
|
||||
self.accept() # 超时:返回当前 spinbox 值(= max_band)
|
||||
else:
|
||||
self._ok_btn.setText(f"确定 ({self._remaining}s)")
|
||||
|
||||
# ── 暴露给调用方的结果接口 ──────────────────────────────
|
||||
def selected_band(self) -> int:
|
||||
"""弹窗关闭后由调用方取回用户选定的波段索引"""
|
||||
return self._selected_band
|
||||
|
||||
def accept(self):
|
||||
"""点"确定"或倒计时归零触发:记录当前 spinbox 值后真正关闭"""
|
||||
self._selected_band = self._spin.value()
|
||||
self._timer.stop()
|
||||
super().accept()
|
||||
|
||||
def reject(self):
|
||||
"""点"取消运行"触发:停表 + 关闭,调用方需中止流程"""
|
||||
self._timer.stop()
|
||||
super().reject()
|
||||
Reference in New Issue
Block a user