步骤四页面修改
This commit is contained in:
@ -1,14 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Step1 面板 - 水域掩膜生成 (完美对齐无跳动重构)
|
||||
Step1 面板 - 水域掩膜生成 (已移除“启用此步骤”)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 路径归一化 helper
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
if _HERE not in sys.path:
|
||||
sys.path.insert(0, _HERE)
|
||||
@ -16,7 +15,7 @@ from _step_path_resolver import resolve_subdir
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel,
|
||||
QCheckBox, QPushButton, QRadioButton, QLineEdit,
|
||||
QPushButton, QRadioButton, QLineEdit,
|
||||
QMessageBox, QStackedWidget, QSizePolicy
|
||||
)
|
||||
from PyQt5.QtCore import Qt
|
||||
@ -27,8 +26,6 @@ from src.gui.styles import ModernStylesheet
|
||||
|
||||
|
||||
class Step1Panel(QWidget):
|
||||
"""1. 水域掩膜生成"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.init_ui()
|
||||
@ -40,9 +37,6 @@ class Step1Panel(QWidget):
|
||||
main_layout.setContentsMargins(24, 24, 24, 24)
|
||||
main_layout.setSpacing(20)
|
||||
|
||||
# ==========================================
|
||||
# 卡片 1:掩膜生成策略
|
||||
# ==========================================
|
||||
method_group = QGroupBox("📁 掩膜生成策略")
|
||||
method_layout = QHBoxLayout()
|
||||
method_layout.setSpacing(24)
|
||||
@ -50,7 +44,6 @@ class Step1Panel(QWidget):
|
||||
|
||||
self.use_existing_radio = QRadioButton("使用现有掩膜文件")
|
||||
self.use_existing_radio.setChecked(True)
|
||||
# 【核心修复】:保留原生圆圈,但直接强制给予足够大(200x35)的安全盒模型,杜绝高DPI下任何形式的截断!
|
||||
self.use_existing_radio.setMinimumSize(200, 35)
|
||||
|
||||
self.use_ndwi_radio = QRadioButton("使用 NDWI 自动生成")
|
||||
@ -62,9 +55,6 @@ class Step1Panel(QWidget):
|
||||
method_group.setLayout(method_layout)
|
||||
main_layout.addWidget(method_group)
|
||||
|
||||
# ==========================================
|
||||
# 卡片 2:文件与参数配置 (绝对像素级对齐)
|
||||
# ==========================================
|
||||
config_group = QGroupBox("⚙️ 文件与参数配置")
|
||||
config_layout = QVBoxLayout()
|
||||
config_layout.setSpacing(16)
|
||||
@ -87,7 +77,6 @@ class Step1Panel(QWidget):
|
||||
|
||||
self.stack = QStackedWidget()
|
||||
|
||||
# 第 0 页:现有掩膜文件
|
||||
self.mask_file = FileSelectWidget(
|
||||
"现有掩膜文件:",
|
||||
"Shapefiles (*.shp);;Raster Files (*.dat *.tif);;All Files (*.*)"
|
||||
@ -95,7 +84,6 @@ class Step1Panel(QWidget):
|
||||
self.mask_file.label.setMinimumWidth(100)
|
||||
self.stack.addWidget(self.mask_file)
|
||||
|
||||
# 第 1 页:NDWI 阈值参数
|
||||
self.ndwi_widget = QWidget()
|
||||
ndwi_layout = QHBoxLayout(self.ndwi_widget)
|
||||
ndwi_layout.setContentsMargins(0, 0, 0, 0)
|
||||
@ -116,7 +104,6 @@ class Step1Panel(QWidget):
|
||||
self.stack.addWidget(self.ndwi_widget)
|
||||
config_layout.addWidget(self.stack)
|
||||
|
||||
# 参考影像
|
||||
self.img_file = FileSelectWidget(
|
||||
"输入参考影像:",
|
||||
"Image Files (*.bsq *.dat *.tif);;All Files (*.*)"
|
||||
@ -127,9 +114,6 @@ class Step1Panel(QWidget):
|
||||
config_group.setLayout(config_layout)
|
||||
main_layout.addWidget(config_group)
|
||||
|
||||
# ==========================================
|
||||
# 卡片 3:输出与执行
|
||||
# ==========================================
|
||||
output_group = QGroupBox("🚀 输出与执行")
|
||||
output_layout = QVBoxLayout()
|
||||
output_layout.setSpacing(16)
|
||||
@ -144,11 +128,8 @@ class Step1Panel(QWidget):
|
||||
self.output_file.line_edit.setPlaceholderText("water_mask.dat")
|
||||
output_layout.addWidget(self.output_file)
|
||||
|
||||
# 完美对齐的底部按钮栏(已移除多余的启用步骤选项)
|
||||
action_layout = QHBoxLayout()
|
||||
self.enable_checkbox = QCheckBox("启用此步骤")
|
||||
self.enable_checkbox.setChecked(True)
|
||||
self.enable_checkbox.setMinimumHeight(24)
|
||||
action_layout.addWidget(self.enable_checkbox)
|
||||
action_layout.addStretch()
|
||||
|
||||
self.run_btn = QPushButton("独立运行步骤")
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Step2 面板 - 耀斑区域识别 (完美对齐卡片化重构)
|
||||
Step2 面板 - 耀斑区域识别 (已移除“启用此步骤”)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 路径归一化 helper
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
if _HERE not in sys.path:
|
||||
sys.path.insert(0, _HERE)
|
||||
@ -16,36 +15,28 @@ from _step_path_resolver import resolve_subdir
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout,
|
||||
QDoubleSpinBox, QSpinBox, QComboBox, QCheckBox, QPushButton,
|
||||
QDoubleSpinBox, QSpinBox, QComboBox, QPushButton,
|
||||
QMessageBox, QLabel
|
||||
)
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
# 从公共组件库导入
|
||||
from src.gui.components.custom_widgets import FileSelectWidget
|
||||
from src.gui.styles import ModernStylesheet
|
||||
|
||||
|
||||
class Step2Panel(QWidget):
|
||||
"""2. 耀斑区域识别"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.work_dir = None
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
# 1. 注入全局样式系统
|
||||
self.setStyleSheet(ModernStylesheet.get_main_stylesheet())
|
||||
|
||||
# 主布局:增加四周留白(24px)和模块间的呼吸间距(20px)
|
||||
main_layout = QVBoxLayout()
|
||||
main_layout.setContentsMargins(24, 24, 24, 24)
|
||||
main_layout.setSpacing(20)
|
||||
|
||||
# ==========================================
|
||||
# 卡片 1:输入数据配置
|
||||
# ==========================================
|
||||
input_group = QGroupBox("📁 输入数据")
|
||||
input_layout = QVBoxLayout()
|
||||
input_layout.setSpacing(16)
|
||||
@ -55,7 +46,7 @@ class Step2Panel(QWidget):
|
||||
"原始影像:",
|
||||
"Image Files (*.bsq *.dat *.tif);;All Files (*.*)"
|
||||
)
|
||||
self.img_file.label.setMinimumWidth(100) # 锁死起跑线
|
||||
self.img_file.label.setMinimumWidth(100)
|
||||
|
||||
self.water_mask_file = FileSelectWidget(
|
||||
"水域掩膜:",
|
||||
@ -68,25 +59,19 @@ class Step2Panel(QWidget):
|
||||
input_group.setLayout(input_layout)
|
||||
main_layout.addWidget(input_group)
|
||||
|
||||
# ==========================================
|
||||
# 卡片 2:核心检测参数
|
||||
# ==========================================
|
||||
params_group = QGroupBox("⚙️ 检测参数")
|
||||
params_layout = QFormLayout()
|
||||
params_layout.setSpacing(16)
|
||||
params_layout.setContentsMargins(20, 24, 20, 20)
|
||||
|
||||
# 耀斑波长
|
||||
self.glint_wave = QDoubleSpinBox()
|
||||
self.glint_wave.setRange(300, 1000)
|
||||
self.glint_wave.setValue(750.0)
|
||||
self.glint_wave.setSuffix(" nm")
|
||||
self.glint_wave.setMinimumWidth(120)
|
||||
# 用 Python 原生 API 彻底没收自带的小箭头,配合全局 styles.py 杜绝重影残影
|
||||
self.glint_wave.setButtonSymbols(QDoubleSpinBox.NoButtons)
|
||||
params_layout.addRow("耀斑检测波长:", self.glint_wave)
|
||||
|
||||
# 检测方法
|
||||
self.method = QComboBox()
|
||||
self.method.addItem("Otsu 阈值法", "otsu")
|
||||
self.method.addItem("Z-Score 方法", "zscore")
|
||||
@ -97,7 +82,6 @@ class Step2Panel(QWidget):
|
||||
self.method.setMinimumWidth(120)
|
||||
params_layout.addRow("检测方法选择:", self.method)
|
||||
|
||||
# 最大连通域面积
|
||||
self.max_area = QSpinBox()
|
||||
self.max_area.setRange(0, 100000)
|
||||
self.max_area.setValue(50)
|
||||
@ -107,7 +91,6 @@ class Step2Panel(QWidget):
|
||||
self.max_area.setMinimumWidth(120)
|
||||
params_layout.addRow("连通域最大面积:", self.max_area)
|
||||
|
||||
# 岸边缓冲区
|
||||
self.buffer_size = QSpinBox()
|
||||
self.buffer_size.setRange(0, 200)
|
||||
self.buffer_size.setValue(10)
|
||||
@ -120,9 +103,6 @@ class Step2Panel(QWidget):
|
||||
params_group.setLayout(params_layout)
|
||||
main_layout.addWidget(params_group)
|
||||
|
||||
# ==========================================
|
||||
# 卡片 3:输出与执行
|
||||
# ==========================================
|
||||
output_group = QGroupBox("🚀 输出与执行")
|
||||
output_layout = QVBoxLayout()
|
||||
output_layout.setSpacing(16)
|
||||
@ -136,12 +116,7 @@ class Step2Panel(QWidget):
|
||||
self.output_file.label.setMinimumWidth(100)
|
||||
output_layout.addWidget(self.output_file)
|
||||
|
||||
# 底部标准按钮栏
|
||||
action_layout = QHBoxLayout()
|
||||
self.enable_checkbox = QCheckBox("启用此步骤")
|
||||
self.enable_checkbox.setChecked(True)
|
||||
action_layout.addWidget(self.enable_checkbox)
|
||||
|
||||
action_layout.addStretch()
|
||||
|
||||
self.run_btn = QPushButton("独立运行步骤")
|
||||
@ -158,7 +133,6 @@ class Step2Panel(QWidget):
|
||||
self.setLayout(main_layout)
|
||||
|
||||
def get_config(self):
|
||||
"""获取配置"""
|
||||
config = {
|
||||
'img_path': self.img_file.get_path(),
|
||||
'glint_wave': self.glint_wave.value(),
|
||||
@ -179,7 +153,6 @@ class Step2Panel(QWidget):
|
||||
return config
|
||||
|
||||
def set_config(self, config):
|
||||
"""设置配置"""
|
||||
if 'img_path' in config:
|
||||
self.img_file.set_path(config['img_path'])
|
||||
if 'glint_wave' in config:
|
||||
@ -198,7 +171,6 @@ class Step2Panel(QWidget):
|
||||
self.output_file.set_path(config['output_path'])
|
||||
|
||||
def update_from_config(self, work_dir=None, pipeline=None):
|
||||
"""从全局配置/Pipeline 或 Step1Panel 自动填充路径,实现上下游数据流转"""
|
||||
if work_dir:
|
||||
self.work_dir = work_dir
|
||||
elif hasattr(self, 'work_dir') and self.work_dir:
|
||||
@ -231,7 +203,6 @@ class Step2Panel(QWidget):
|
||||
self.output_file.set_path("")
|
||||
|
||||
def _on_run_single_clicked(self):
|
||||
"""通过 EventBus 发布单步执行请求"""
|
||||
from src.gui.core.event_bus import global_event_bus
|
||||
|
||||
img_path = self.img_file.get_path()
|
||||
|
||||
@ -1,243 +1,273 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Step3 面板 - 耀斑去除
|
||||
Step3 面板 - 耀斑去除 (已移除“启用此步骤”)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
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 _step_path_resolver import resolve_subdir
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QGroupBox, QFormLayout,
|
||||
QDoubleSpinBox, QSpinBox, QComboBox, QCheckBox, QPushButton,
|
||||
QLabel, QLineEdit, QMessageBox,
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout,
|
||||
QSpinBox, QComboBox, QCheckBox, QPushButton,
|
||||
QLabel, QLineEdit, QMessageBox, QSizePolicy
|
||||
)
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QDoubleValidator
|
||||
|
||||
# 从公共组件库导入
|
||||
from src.gui.components.custom_widgets import FileSelectWidget
|
||||
from src.gui.styles import ModernStylesheet
|
||||
|
||||
|
||||
class Step3Panel(QWidget):
|
||||
"""步骤3:耀斑去除"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout()
|
||||
self.setStyleSheet(ModernStylesheet.get_main_stylesheet())
|
||||
|
||||
# 标题
|
||||
main_layout = QVBoxLayout()
|
||||
main_layout.setContentsMargins(24, 24, 24, 24)
|
||||
main_layout.setSpacing(20)
|
||||
|
||||
input_group = QGroupBox("📁 输入数据")
|
||||
input_layout = QVBoxLayout()
|
||||
input_layout.setSpacing(16)
|
||||
input_layout.setContentsMargins(20, 24, 20, 20)
|
||||
|
||||
# 影像文件
|
||||
self.img_file = FileSelectWidget(
|
||||
"影像文件:",
|
||||
"原始影像:",
|
||||
"Image Files (*.bsq *.dat *.tif);;All Files (*.*)"
|
||||
)
|
||||
layout.addWidget(self.img_file)
|
||||
self.img_file.label.setMinimumWidth(120)
|
||||
|
||||
# 水域掩膜/边界:完整流程可由步骤1自动生成;独立单步运行时须手动指定
|
||||
self.water_mask_file = FileSelectWidget(
|
||||
"水域掩膜/边界:",
|
||||
"Mask/Boundary (*.dat *.tif *.shp);;All Files (*.*)"
|
||||
)
|
||||
layout.addWidget(self.water_mask_file)
|
||||
self.water_mask_file.label.setMinimumWidth(120)
|
||||
|
||||
step3_mask_hint = QLabel(
|
||||
"提示:独立运行本步骤时必须选择水域掩膜或边界(与影像同区域的 .dat/.tif 掩膜,或 .shp 矢量)。"
|
||||
"💡 提示:独立运行本步骤时必须选择水域掩膜或边界(与影像同区域的 .dat/.tif 掩膜,或 .shp 矢量)。"
|
||||
)
|
||||
step3_mask_hint.setWordWrap(True)
|
||||
step3_mask_hint.setStyleSheet("color: #666; font-size: 10px;")
|
||||
layout.addWidget(step3_mask_hint)
|
||||
step3_mask_hint.setStyleSheet(f"""
|
||||
QLabel {{
|
||||
color: {ModernStylesheet.COLORS['primary']};
|
||||
background-color: {ModernStylesheet.COLORS['selected']};
|
||||
border: 1px solid {ModernStylesheet.COLORS['border_light']};
|
||||
border-radius: 6px;
|
||||
padding: 10px 14px;
|
||||
margin-bottom: 4px;
|
||||
}}
|
||||
""")
|
||||
|
||||
# 方法选择
|
||||
method_group = QGroupBox("去耀斑方法")
|
||||
method_layout = QVBoxLayout()
|
||||
input_layout.addWidget(step3_mask_hint)
|
||||
input_layout.addWidget(self.img_file)
|
||||
input_layout.addWidget(self.water_mask_file)
|
||||
input_group.setLayout(input_layout)
|
||||
main_layout.addWidget(input_group)
|
||||
|
||||
params_group = QGroupBox("⚙️ 去耀斑算法设置")
|
||||
params_layout = QVBoxLayout()
|
||||
params_layout.setSpacing(16)
|
||||
params_layout.setContentsMargins(20, 24, 20, 20)
|
||||
|
||||
method_row = QHBoxLayout()
|
||||
method_row.setContentsMargins(0, 0, 0, 0)
|
||||
method_label = QLabel("去耀斑方法:")
|
||||
method_label.setMinimumWidth(120)
|
||||
self.method = QComboBox()
|
||||
for text, data in [('Goodman方法', 'goodman'), ('Kutser方法', 'kutser'),
|
||||
('Hedley方法', 'hedley'), ('SUGAR算法', 'sugar')]:
|
||||
for text, data in [('Goodman 方法', 'goodman'), ('Kutser 方法', 'kutser'),
|
||||
('Hedley 方法', 'hedley'), ('SUGAR 算法', 'sugar')]:
|
||||
self.method.addItem(text, data)
|
||||
self.method.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
self.method.currentIndexChanged.connect(self._on_method_changed)
|
||||
method_layout.addWidget(self.method)
|
||||
method_row.addWidget(method_label)
|
||||
method_row.addWidget(self.method)
|
||||
params_layout.addLayout(method_row)
|
||||
|
||||
method_group.setLayout(method_layout)
|
||||
layout.addWidget(method_group)
|
||||
|
||||
# Goodman参数组
|
||||
self.goodman_group = QGroupBox("Goodman方法参数")
|
||||
goodman_layout = QFormLayout()
|
||||
# --- Goodman ---
|
||||
self.goodman_widget = QWidget()
|
||||
goodman_layout = QFormLayout(self.goodman_widget)
|
||||
goodman_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.nir_lower = QSpinBox()
|
||||
self.nir_lower.setRange(0, 200)
|
||||
self.nir_lower.setValue(65)
|
||||
goodman_layout.addRow("NIR下波段索引:", self.nir_lower)
|
||||
self.nir_lower.setButtonSymbols(QSpinBox.NoButtons)
|
||||
self._add_row_with_fixed_label(goodman_layout, "NIR下波段索引:", self.nir_lower)
|
||||
|
||||
self.nir_upper = QSpinBox()
|
||||
self.nir_upper.setRange(0, 200)
|
||||
self.nir_upper.setValue(91)
|
||||
goodman_layout.addRow("NIR上波段索引:", self.nir_upper)
|
||||
self.nir_upper.setButtonSymbols(QSpinBox.NoButtons)
|
||||
self._add_row_with_fixed_label(goodman_layout, "NIR上波段索引:", self.nir_upper)
|
||||
|
||||
self.goodman_a = QDoubleSpinBox()
|
||||
self.goodman_a.setDecimals(6)
|
||||
self.goodman_a.setRange(0, 1)
|
||||
self.goodman_a.setValue(0.000019)
|
||||
goodman_layout.addRow("参数A:", self.goodman_a)
|
||||
self.goodman_a = QLineEdit("0.000019")
|
||||
self.goodman_a.setValidator(QDoubleValidator(0.0, 1.0, 6, self))
|
||||
self._add_row_with_fixed_label(goodman_layout, "参数 A:", self.goodman_a)
|
||||
|
||||
self.goodman_b = QDoubleSpinBox()
|
||||
self.goodman_b.setDecimals(2)
|
||||
self.goodman_b.setRange(0, 1)
|
||||
self.goodman_b.setValue(0.1)
|
||||
goodman_layout.addRow("参数B:", self.goodman_b)
|
||||
self.goodman_b = QLineEdit("0.10")
|
||||
self.goodman_b.setValidator(QDoubleValidator(0.0, 1.0, 2, self))
|
||||
self._add_row_with_fixed_label(goodman_layout, "参数 B:", self.goodman_b)
|
||||
params_layout.addWidget(self.goodman_widget)
|
||||
|
||||
self.goodman_group.setLayout(goodman_layout)
|
||||
layout.addWidget(self.goodman_group)
|
||||
|
||||
# Kutser参数组
|
||||
self.kutser_group = QGroupBox("Kutser方法参数")
|
||||
kutser_layout = QFormLayout()
|
||||
# --- Kutser ---
|
||||
self.kutser_widget = QWidget()
|
||||
kutser_layout = QFormLayout(self.kutser_widget)
|
||||
kutser_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.oxy_band = QSpinBox()
|
||||
self.oxy_band.setRange(0, 200)
|
||||
self.oxy_band.setValue(38)
|
||||
kutser_layout.addRow("氧吸收波段索引:", self.oxy_band)
|
||||
self.oxy_band.setButtonSymbols(QSpinBox.NoButtons)
|
||||
self._add_row_with_fixed_label(kutser_layout, "氧吸收波段索引:", self.oxy_band)
|
||||
|
||||
self.lower_oxy = QSpinBox()
|
||||
self.lower_oxy.setRange(0, 200)
|
||||
self.lower_oxy.setValue(36)
|
||||
kutser_layout.addRow("下氧吸收波段索引:", self.lower_oxy)
|
||||
self.lower_oxy.setButtonSymbols(QSpinBox.NoButtons)
|
||||
self._add_row_with_fixed_label(kutser_layout, "下氧吸收波段索引:", self.lower_oxy)
|
||||
|
||||
self.upper_oxy = QSpinBox()
|
||||
self.upper_oxy.setRange(0, 200)
|
||||
self.upper_oxy.setValue(49)
|
||||
kutser_layout.addRow("上氧吸收波段索引:", self.upper_oxy)
|
||||
self.upper_oxy.setButtonSymbols(QSpinBox.NoButtons)
|
||||
self._add_row_with_fixed_label(kutser_layout, "上氧吸收波段索引:", self.upper_oxy)
|
||||
|
||||
self.nir_band = QSpinBox()
|
||||
self.nir_band.setRange(0, 200)
|
||||
self.nir_band.setValue(47)
|
||||
kutser_layout.addRow("NIR波段索引:", self.nir_band)
|
||||
self.nir_band.setButtonSymbols(QSpinBox.NoButtons)
|
||||
self._add_row_with_fixed_label(kutser_layout, "NIR波段索引:", self.nir_band)
|
||||
|
||||
self.kutser_group.setLayout(kutser_layout)
|
||||
self.kutser_group.setVisible(False)
|
||||
layout.addWidget(self.kutser_group)
|
||||
self.kutser_widget.setVisible(False)
|
||||
params_layout.addWidget(self.kutser_widget)
|
||||
|
||||
# Hedley参数组
|
||||
self.hedley_group = QGroupBox("Hedley方法参数")
|
||||
hedley_layout = QFormLayout()
|
||||
# --- Hedley ---
|
||||
self.hedley_widget = QWidget()
|
||||
hedley_layout = QFormLayout(self.hedley_widget)
|
||||
hedley_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.hedley_nir_band = QSpinBox()
|
||||
self.hedley_nir_band.setRange(0, 200)
|
||||
self.hedley_nir_band.setValue(47)
|
||||
hedley_layout.addRow("NIR波段索引:", self.hedley_nir_band)
|
||||
self.hedley_nir_band.setButtonSymbols(QSpinBox.NoButtons)
|
||||
self._add_row_with_fixed_label(hedley_layout, "NIR波段索引:", self.hedley_nir_band)
|
||||
|
||||
self.hedley_group.setLayout(hedley_layout)
|
||||
self.hedley_group.setVisible(False)
|
||||
layout.addWidget(self.hedley_group)
|
||||
self.hedley_widget.setVisible(False)
|
||||
params_layout.addWidget(self.hedley_widget)
|
||||
|
||||
# SUGAR参数组
|
||||
self.sugar_group = QGroupBox("SUGAR方法参数")
|
||||
sugar_layout = QFormLayout()
|
||||
# --- SUGAR ---
|
||||
self.sugar_widget = QWidget()
|
||||
sugar_layout = QFormLayout(self.sugar_widget)
|
||||
sugar_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.sugar_iter = QSpinBox()
|
||||
self.sugar_iter.setRange(1, 20)
|
||||
self.sugar_iter.setValue(3)
|
||||
self.sugar_iter.setSpecialValueText("自动")
|
||||
sugar_layout.addRow("迭代次数:", self.sugar_iter)
|
||||
self.sugar_iter.setButtonSymbols(QSpinBox.NoButtons)
|
||||
self._add_row_with_fixed_label(sugar_layout, "迭代次数:", self.sugar_iter)
|
||||
|
||||
self.sugar_sigma = QDoubleSpinBox()
|
||||
self.sugar_sigma.setDecimals(2)
|
||||
self.sugar_sigma.setRange(0.1, 10)
|
||||
self.sugar_sigma.setValue(1.0)
|
||||
sugar_layout.addRow("LoG平滑σ:", self.sugar_sigma)
|
||||
self.sugar_sigma = QLineEdit("1.00")
|
||||
self.sugar_sigma.setValidator(QDoubleValidator(0.1, 10.0, 2, self))
|
||||
self._add_row_with_fixed_label(sugar_layout, "LoG 平滑 σ:", self.sugar_sigma)
|
||||
|
||||
self.sugar_estimate_background = QCheckBox()
|
||||
self.sugar_estimate_background.setChecked(True)
|
||||
sugar_layout.addRow("估计背景光谱:", self.sugar_estimate_background)
|
||||
self._add_row_with_fixed_label(sugar_layout, "估计背景光谱:", self.sugar_estimate_background)
|
||||
|
||||
self.sugar_glint_mask_method = QComboBox()
|
||||
self.sugar_glint_mask_method.addItems(['cdf', 'otsu'])
|
||||
self.sugar_glint_mask_method.setCurrentText('cdf')
|
||||
sugar_layout.addRow("耀斑掩膜方法:", self.sugar_glint_mask_method)
|
||||
self.sugar_glint_mask_method.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
self._add_row_with_fixed_label(sugar_layout, "耀斑掩膜方法:", self.sugar_glint_mask_method)
|
||||
|
||||
self.sugar_termination_thresh = QDoubleSpinBox()
|
||||
self.sugar_termination_thresh.setDecimals(2)
|
||||
self.sugar_termination_thresh.setRange(1, 100)
|
||||
self.sugar_termination_thresh.setValue(20.0)
|
||||
sugar_layout.addRow("终止阈值:", self.sugar_termination_thresh)
|
||||
self.sugar_termination_thresh = QLineEdit("20.00")
|
||||
self.sugar_termination_thresh.setValidator(QDoubleValidator(1.0, 100.0, 2, self))
|
||||
self._add_row_with_fixed_label(sugar_layout, "终止阈值:", self.sugar_termination_thresh)
|
||||
|
||||
self.sugar_bounds = QLineEdit()
|
||||
self.sugar_bounds.setText("[(1, 2)]")
|
||||
sugar_layout.addRow("优化边界:", self.sugar_bounds)
|
||||
self.sugar_bounds = QLineEdit("[(1, 2)]")
|
||||
self._add_row_with_fixed_label(sugar_layout, "优化边界:", self.sugar_bounds)
|
||||
|
||||
self.sugar_group.setLayout(sugar_layout)
|
||||
self.sugar_group.setVisible(False)
|
||||
layout.addWidget(self.sugar_group)
|
||||
self.sugar_widget.setVisible(False)
|
||||
params_layout.addWidget(self.sugar_widget)
|
||||
|
||||
# 插值选项
|
||||
interp_group = QGroupBox("0值像素插值")
|
||||
interp_layout = QFormLayout()
|
||||
|
||||
self.interpolate_zeros = QCheckBox("启用插值")
|
||||
interp_layout.addRow("", self.interpolate_zeros)
|
||||
# --- 通用参数 ---
|
||||
interp_row = QHBoxLayout()
|
||||
interp_row.setContentsMargins(0, 8, 0, 0)
|
||||
self.interpolate_zeros = QCheckBox("启用 0 值像素插值")
|
||||
self.interpolate_zeros.setMinimumWidth(120)
|
||||
|
||||
self.interp_method = QComboBox()
|
||||
for text, data in [('最近邻插值', 'nearest'), ('双线性插值', 'bilinear'),
|
||||
('样条插值', 'spline'), ('克里金插值', 'kriging')]:
|
||||
self.interp_method.addItem(text, data)
|
||||
self.interp_method.setCurrentIndex(1) # 默认双线性插值
|
||||
interp_layout.addRow("插值方法:", self.interp_method)
|
||||
self.interp_method.setCurrentIndex(1)
|
||||
self.interp_method.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
|
||||
interp_group.setLayout(interp_layout)
|
||||
layout.addWidget(interp_group)
|
||||
interp_row.addWidget(self.interpolate_zeros)
|
||||
interp_row.addWidget(self.interp_method)
|
||||
params_layout.addLayout(interp_row)
|
||||
|
||||
# # 实测经纬度参考点
|
||||
# self.ref_csv_file = FileSelectWidget(
|
||||
# "实测经纬度CSV:",
|
||||
# "CSV Files (*.csv);;All Files (*.*)"
|
||||
# )
|
||||
# self.ref_csv_file.line_edit.setPlaceholderText("可选:包含 Lon/Lat 列的 CSV 文件")
|
||||
# layout.addWidget(self.ref_csv_file)
|
||||
params_group.setLayout(params_layout)
|
||||
main_layout.addWidget(params_group)
|
||||
|
||||
# 交互式预览按钮
|
||||
# self.preview_btn = QPushButton("👁️ 打开交互式影像预览")
|
||||
# self.preview_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('info'))
|
||||
# self.preview_btn.clicked.connect(self.open_interactive_viewer)
|
||||
# layout.addWidget(self.preview_btn)
|
||||
output_group = QGroupBox("🚀 输出与执行")
|
||||
output_layout = QVBoxLayout()
|
||||
output_layout.setSpacing(16)
|
||||
output_layout.setContentsMargins(20, 24, 20, 20)
|
||||
|
||||
# 输出文件路径
|
||||
self.output_file = FileSelectWidget(
|
||||
"输出影像:",
|
||||
"Image Files (*.bsq *.dat *.tif);;All Files (*.*)"
|
||||
"结果保存至:",
|
||||
"Image Files (*.bsq *.dat *.tif);;All Files (*.*)",
|
||||
mode="save"
|
||||
)
|
||||
self.output_file.label.setMinimumWidth(120)
|
||||
self.output_file.line_edit.setPlaceholderText("deglint_image.bsq")
|
||||
layout.addWidget(self.output_file)
|
||||
output_layout.addWidget(self.output_file)
|
||||
|
||||
# 启用步骤
|
||||
self.enable_checkbox = QCheckBox("启用此步骤")
|
||||
self.enable_checkbox.setChecked(True)
|
||||
layout.addWidget(self.enable_checkbox)
|
||||
action_layout = QHBoxLayout()
|
||||
action_layout.addStretch()
|
||||
|
||||
# 独立运行按钮
|
||||
self.run_btn = QPushButton("独立运行此步骤")
|
||||
self.run_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('success'))
|
||||
self.run_btn = QPushButton("独立运行步骤")
|
||||
self.run_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('primary'))
|
||||
self.run_btn.setMinimumWidth(140)
|
||||
self.run_btn.clicked.connect(self._on_run_single_clicked)
|
||||
layout.addWidget(self.run_btn)
|
||||
action_layout.addWidget(self.run_btn)
|
||||
|
||||
output_layout.addLayout(action_layout)
|
||||
output_group.setLayout(output_layout)
|
||||
main_layout.addWidget(output_group)
|
||||
|
||||
main_layout.addStretch()
|
||||
self.setLayout(main_layout)
|
||||
|
||||
layout.addStretch()
|
||||
self.setLayout(layout)
|
||||
# 信号连接:影像文件路径变化时动态更新波段范围
|
||||
self.img_file.line_edit.textChanged.connect(self._update_band_ranges)
|
||||
|
||||
def _add_row_with_fixed_label(self, form_layout, label_text, widget):
|
||||
lbl = QLabel(label_text)
|
||||
lbl.setMinimumWidth(120)
|
||||
|
||||
row_layout = QHBoxLayout()
|
||||
row_layout.setContentsMargins(0, 0, 0, 0)
|
||||
row_layout.addWidget(lbl)
|
||||
|
||||
if hasattr(widget, 'setSizePolicy'):
|
||||
widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
|
||||
row_layout.addWidget(widget)
|
||||
form_layout.addRow(row_layout)
|
||||
|
||||
def open_interactive_viewer(self):
|
||||
"""打开交互式影像预览"""
|
||||
from src.gui.water_quality_gui import InteractiveViewerDialog
|
||||
img_path = self.img_file.get_path()
|
||||
if not img_path or not os.path.isfile(img_path):
|
||||
@ -245,19 +275,15 @@ class Step3Panel(QWidget):
|
||||
return
|
||||
|
||||
water_mask = self.water_mask_file.get_path()
|
||||
|
||||
dialog = InteractiveViewerDialog(img_path, self)
|
||||
if water_mask and os.path.isfile(water_mask):
|
||||
dialog.load_water_mask(water_mask)
|
||||
dialog.exec_()
|
||||
|
||||
def _update_band_ranges(self, file_path):
|
||||
"""根据选择的影像动态限制波段索引的输入范围"""
|
||||
from osgeo import gdal
|
||||
|
||||
if not file_path or not os.path.isfile(file_path):
|
||||
return
|
||||
|
||||
try:
|
||||
dataset = gdal.Open(file_path)
|
||||
if dataset is None:
|
||||
@ -274,38 +300,25 @@ class Step3Panel(QWidget):
|
||||
pass
|
||||
|
||||
def update_from_config(self, work_dir=None, pipeline=None):
|
||||
"""
|
||||
从 Step1Panel 自动填充水域掩膜路径,实现上下游数据流转
|
||||
|
||||
Args:
|
||||
work_dir: 工作目录路径
|
||||
pipeline: Pipeline 实例(未使用,保留接口兼容性)
|
||||
"""
|
||||
# 保存工作目录引用
|
||||
if work_dir:
|
||||
self.work_dir = work_dir
|
||||
elif hasattr(self, 'work_dir') and self.work_dir:
|
||||
pass # 保持现有工作目录
|
||||
pass
|
||||
else:
|
||||
self.work_dir = None
|
||||
|
||||
# 从 Step1 界面读取水域掩膜路径
|
||||
main_window = self.window()
|
||||
if hasattr(main_window, 'step1_panel'):
|
||||
if main_window.step1_panel.use_ndwi_radio.isChecked():
|
||||
# NDWI模式,读取输出框的路径
|
||||
mask_path = main_window.step1_panel.output_file.get_path()
|
||||
else:
|
||||
# 导入现有模式,读取输入框的路径
|
||||
mask_path = main_window.step1_panel.mask_file.get_path()
|
||||
|
||||
if mask_path:
|
||||
# 若为相对路径,使用 work_dir 合成为绝对路径
|
||||
if not os.path.isabs(mask_path):
|
||||
mask_path = os.path.join(self.work_dir or '', mask_path).replace('\\', '/')
|
||||
self.water_mask_file.set_path(mask_path)
|
||||
|
||||
# 自动填充输出路径(基于工作目录)
|
||||
if self.work_dir:
|
||||
output_dir = resolve_subdir(self.work_dir, 'deglint')
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
@ -315,36 +328,40 @@ class Step3Panel(QWidget):
|
||||
self.output_file.set_path("")
|
||||
|
||||
def _on_method_changed(self, index):
|
||||
"""方法改变时更新参数显示"""
|
||||
method_id = self.method.currentData()
|
||||
self.goodman_group.setVisible(method_id == 'goodman')
|
||||
self.kutser_group.setVisible(method_id == 'kutser')
|
||||
self.hedley_group.setVisible(method_id == 'hedley')
|
||||
self.sugar_group.setVisible(method_id == 'sugar')
|
||||
self.goodman_widget.setVisible(method_id == 'goodman')
|
||||
self.kutser_widget.setVisible(method_id == 'kutser')
|
||||
self.hedley_widget.setVisible(method_id == 'hedley')
|
||||
self.sugar_widget.setVisible(method_id == 'sugar')
|
||||
|
||||
def _safe_float(self, line_edit, default_val=0.0):
|
||||
try:
|
||||
return float(line_edit.text().strip())
|
||||
except ValueError:
|
||||
return default_val
|
||||
|
||||
def get_config(self):
|
||||
"""获取配置"""
|
||||
config = {
|
||||
'img_path': self.img_file.get_path(),
|
||||
'method': self.method.currentData(), # 使用 currentData() 获取英文ID
|
||||
'enabled': self.enable_checkbox.isChecked(),
|
||||
'method': self.method.currentData(),
|
||||
'interpolate_zeros': self.interpolate_zeros.isChecked(),
|
||||
'interpolation_method': self.interp_method.currentData(), # 使用 currentData()
|
||||
'interpolation_method': self.interp_method.currentData(),
|
||||
}
|
||||
water_mask_path = self.water_mask_file.get_path()
|
||||
if water_mask_path:
|
||||
config['water_mask_path'] = water_mask_path
|
||||
|
||||
output_path = self.output_file.get_path()
|
||||
if output_path:
|
||||
config['output_path'] = output_path
|
||||
|
||||
method = self.method.currentData() # 使用 currentData()
|
||||
method = self.method.currentData()
|
||||
|
||||
if method == 'goodman':
|
||||
config['nir_lower'] = self.nir_lower.value()
|
||||
config['nir_upper'] = self.nir_upper.value()
|
||||
config['goodman_A'] = self.goodman_a.value()
|
||||
config['goodman_B'] = self.goodman_b.value()
|
||||
config['goodman_A'] = self._safe_float(self.goodman_a, 0.000019)
|
||||
config['goodman_B'] = self._safe_float(self.goodman_b, 0.1)
|
||||
|
||||
elif method == 'kutser':
|
||||
config['oxy_band'] = self.oxy_band.value()
|
||||
@ -357,53 +374,45 @@ class Step3Panel(QWidget):
|
||||
|
||||
elif method == 'sugar':
|
||||
config['sugar_iter'] = self.sugar_iter.value() if self.sugar_iter.value() > 0 else None
|
||||
config['sugar_sigma'] = self.sugar_sigma.value()
|
||||
config['sugar_sigma'] = self._safe_float(self.sugar_sigma, 1.0)
|
||||
config['sugar_estimate_background'] = self.sugar_estimate_background.isChecked()
|
||||
config['sugar_glint_mask_method'] = self.sugar_glint_mask_method.currentData()
|
||||
config['sugar_termination_thresh'] = self.sugar_termination_thresh.value()
|
||||
# 解析bounds字符串
|
||||
config['sugar_termination_thresh'] = self._safe_float(self.sugar_termination_thresh, 20.0)
|
||||
try:
|
||||
import ast
|
||||
config['sugar_bounds'] = ast.literal_eval(self.sugar_bounds.text())
|
||||
except:
|
||||
config['sugar_bounds'] = [(1, 2)] # 默认值
|
||||
config['sugar_bounds'] = [(1, 2)]
|
||||
|
||||
return config
|
||||
|
||||
def set_config(self, config):
|
||||
"""设置配置"""
|
||||
if 'img_path' in config:
|
||||
self.img_file.set_path(config['img_path'])
|
||||
if 'water_mask_path' in config:
|
||||
self.water_mask_file.set_path(config['water_mask_path'])
|
||||
if 'output_path' in config:
|
||||
self.output_file.set_path(config['output_path'])
|
||||
if 'reference_csv' in config:
|
||||
self.ref_csv_file.set_path(config['reference_csv'])
|
||||
if 'method' in config:
|
||||
idx = self.method.findData(config['method']) # 使用 findData()
|
||||
idx = self.method.findData(config['method'])
|
||||
if idx >= 0:
|
||||
self.method.setCurrentIndex(idx)
|
||||
if 'enabled' in config:
|
||||
self.enable_checkbox.setChecked(config['enabled'])
|
||||
if 'interpolate_zeros' in config:
|
||||
self.interpolate_zeros.setChecked(config['interpolate_zeros'])
|
||||
if 'interpolation_method' in config:
|
||||
idx = self.interp_method.findData(config['interpolation_method']) # 使用 findData()
|
||||
idx = self.interp_method.findData(config['interpolation_method'])
|
||||
if idx >= 0:
|
||||
self.interp_method.setCurrentIndex(idx)
|
||||
|
||||
# Goodman参数
|
||||
if 'nir_lower' in config:
|
||||
self.nir_lower.setValue(config['nir_lower'])
|
||||
if 'nir_upper' in config:
|
||||
self.nir_upper.setValue(config['nir_upper'])
|
||||
if 'goodman_A' in config:
|
||||
self.goodman_a.setValue(config['goodman_A'])
|
||||
self.goodman_a.setText(f"{config['goodman_A']:.6f}")
|
||||
if 'goodman_B' in config:
|
||||
self.goodman_b.setValue(config['goodman_B'])
|
||||
self.goodman_b.setText(f"{config['goodman_B']:.2f}")
|
||||
|
||||
# Kutser参数
|
||||
if 'oxy_band' in config:
|
||||
self.oxy_band.setValue(config['oxy_band'])
|
||||
if 'lower_oxy' in config:
|
||||
@ -413,35 +422,32 @@ class Step3Panel(QWidget):
|
||||
if 'nir_band' in config:
|
||||
self.nir_band.setValue(config['nir_band'])
|
||||
|
||||
# Hedley参数
|
||||
if 'hedley_nir_band' in config:
|
||||
self.hedley_nir_band.setValue(config['hedley_nir_band'])
|
||||
|
||||
# SUGAR参数
|
||||
if 'sugar_iter' in config:
|
||||
self.sugar_iter.setValue(config['sugar_iter'] if config['sugar_iter'] is not None else 0)
|
||||
if 'sugar_sigma' in config:
|
||||
self.sugar_sigma.setValue(config['sugar_sigma'])
|
||||
self.sugar_sigma.setText(f"{config['sugar_sigma']:.2f}")
|
||||
if 'sugar_estimate_background' in config:
|
||||
self.sugar_estimate_background.setChecked(config['sugar_estimate_background'])
|
||||
if 'sugar_glint_mask_method' in config:
|
||||
idx = self.sugar_glint_mask_method.findData(config['sugar_glint_mask_method']) # 使用 findData()
|
||||
idx = self.sugar_glint_mask_method.findData(config['sugar_glint_mask_method'])
|
||||
if idx >= 0:
|
||||
self.sugar_glint_mask_method.setCurrentIndex(idx)
|
||||
if 'sugar_termination_thresh' in config:
|
||||
self.sugar_termination_thresh.setValue(config['sugar_termination_thresh'])
|
||||
self.sugar_termination_thresh.setText(f"{config['sugar_termination_thresh']:.2f}")
|
||||
if 'sugar_bounds' in config:
|
||||
self.sugar_bounds.setText(str(config['sugar_bounds']))
|
||||
|
||||
def _on_run_single_clicked(self):
|
||||
"""通过 EventBus 发布单步执行请求(解耦面板与 PipelineExecutor)。"""
|
||||
from src.gui.core.event_bus import global_event_bus
|
||||
|
||||
img_path = self.img_file.get_path()
|
||||
if not img_path:
|
||||
QMessageBox.warning(self, "输入错误", "请选择影像文件!")
|
||||
return
|
||||
if self.enable_checkbox.isChecked():
|
||||
|
||||
water_mask_path = self.water_mask_file.get_path()
|
||||
if not water_mask_path:
|
||||
QMessageBox.warning(
|
||||
@ -458,28 +464,3 @@ class Step3Panel(QWidget):
|
||||
'step_name': 'step3',
|
||||
'config': config,
|
||||
})
|
||||
|
||||
def run_step(self):
|
||||
"""独立运行步骤3(旧版 parent 链上溯方式,保留兼容)。"""
|
||||
# 验证输入
|
||||
img_path = self.img_file.get_path()
|
||||
if not img_path:
|
||||
QMessageBox.warning(self, "输入错误", "请选择影像文件!")
|
||||
return
|
||||
if self.enable_checkbox.isChecked():
|
||||
water_mask_path = self.water_mask_file.get_path()
|
||||
if not water_mask_path:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"输入错误",
|
||||
"独立运行耀斑去除时,必须选择水域掩膜或边界文件。\n\n"
|
||||
"请提供与当前影像空间一致的水域栅格掩膜(.dat/.tif),或水域矢量边界(.shp)。\n"
|
||||
"若刚跑过完整流程,可使用步骤1生成的水域掩膜文件。",
|
||||
)
|
||||
return
|
||||
|
||||
# 获取主窗口并运行步骤
|
||||
main_window = self.window()
|
||||
if hasattr(main_window, 'run_single_step'):
|
||||
config = {'step3': self.get_config()}
|
||||
main_window.run_single_step('step3', config)
|
||||
@ -1,14 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Step4 面板 - 采样点布设 (现代化排版重构)
|
||||
Step4 面板 - 采样点布设 (已移除“启用此步骤”)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 路径归一化 helper
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
if _HERE not in sys.path:
|
||||
sys.path.insert(0, _HERE)
|
||||
@ -17,7 +16,7 @@ from _step_path_resolver import resolve_subdir
|
||||
from PyQt5.QtCore import QTimer, Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout,
|
||||
QPushButton, QCheckBox, QSpinBox, QMessageBox, QLabel, QFrame
|
||||
QPushButton, QSpinBox, QMessageBox, QLabel, QFrame
|
||||
)
|
||||
|
||||
from src.gui.components.custom_widgets import FileSelectWidget
|
||||
@ -26,24 +25,17 @@ from src.gui.styles import ModernStylesheet
|
||||
|
||||
|
||||
class Step4SamplingPanel(QWidget):
|
||||
"""步骤4:采样点布设"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
# 注入全局样式系统
|
||||
self.setStyleSheet(ModernStylesheet.get_main_stylesheet())
|
||||
|
||||
# 主布局:增加四周留白(24px)和模块间的呼吸间距(20px)
|
||||
main_layout = QVBoxLayout()
|
||||
main_layout.setContentsMargins(24, 24, 24, 24)
|
||||
main_layout.setSpacing(20)
|
||||
|
||||
# ==========================================
|
||||
# 卡片 1:输入数据配置
|
||||
# ==========================================
|
||||
input_group = QGroupBox("📁 输入数据")
|
||||
input_layout = QVBoxLayout()
|
||||
input_layout.setSpacing(16)
|
||||
@ -62,9 +54,6 @@ class Step4SamplingPanel(QWidget):
|
||||
input_group.setLayout(input_layout)
|
||||
main_layout.addWidget(input_group)
|
||||
|
||||
# ==========================================
|
||||
# 卡片 2:核心参数设置
|
||||
# ==========================================
|
||||
params_group = QGroupBox("⚙️ 采样参数")
|
||||
params_layout = QFormLayout()
|
||||
params_layout.setSpacing(16)
|
||||
@ -91,6 +80,7 @@ class Step4SamplingPanel(QWidget):
|
||||
self.chunk_size.setMinimumWidth(120)
|
||||
params_layout.addRow("内存处理块大小:", self.chunk_size)
|
||||
|
||||
from PyQt5.QtWidgets import QCheckBox
|
||||
self.use_adaptive_sampling = QCheckBox("启用自适应边缘采样")
|
||||
self.use_adaptive_sampling.setChecked(True)
|
||||
params_layout.addRow("智能模式:", self.use_adaptive_sampling)
|
||||
@ -98,15 +88,11 @@ class Step4SamplingPanel(QWidget):
|
||||
params_group.setLayout(params_layout)
|
||||
main_layout.addWidget(params_group)
|
||||
|
||||
# ==========================================
|
||||
# 卡片 3:输出与执行
|
||||
# ==========================================
|
||||
output_group = QGroupBox("🚀 输出与执行")
|
||||
output_layout = QVBoxLayout()
|
||||
output_layout.setSpacing(16)
|
||||
output_layout.setContentsMargins(20, 24, 20, 20)
|
||||
|
||||
# 输出文件选择
|
||||
self.output_file = FileSelectWidget(
|
||||
"结果保存至:",
|
||||
"CSV Files (*.csv);;All Files (*.*)",
|
||||
@ -115,14 +101,8 @@ class Step4SamplingPanel(QWidget):
|
||||
self.output_file.line_edit.setPlaceholderText("sampling_spectra.csv")
|
||||
output_layout.addWidget(self.output_file)
|
||||
|
||||
# 底部操作栏 (水平布局)
|
||||
action_layout = QHBoxLayout()
|
||||
|
||||
self.enable_checkbox = QCheckBox("启用此步骤")
|
||||
self.enable_checkbox.setChecked(True)
|
||||
action_layout.addWidget(self.enable_checkbox)
|
||||
|
||||
action_layout.addStretch() # 把按钮推到右边
|
||||
action_layout.addStretch()
|
||||
|
||||
self.preview_btn = QPushButton("交互式预览采样点")
|
||||
self.preview_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('normal'))
|
||||
@ -145,15 +125,12 @@ class Step4SamplingPanel(QWidget):
|
||||
main_layout.addStretch()
|
||||
self.setLayout(main_layout)
|
||||
|
||||
# 添加心跳定时器
|
||||
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):
|
||||
config = {
|
||||
'interval': self.interval.value(),
|
||||
|
||||
@ -90,8 +90,6 @@ class ModernStylesheet:
|
||||
padding: 0 4px;
|
||||
}}
|
||||
|
||||
/* 【核心修复】彻底删除了 QRadioButton 的全部 CSS 代码,让其 100% 保持 Windows 原生样式 */
|
||||
|
||||
/* --- 纯净版:输入框 & 数字框 --- */
|
||||
QLineEdit, QSpinBox, QDoubleSpinBox {{
|
||||
background-color: {cls.COLORS['panel_bg']};
|
||||
@ -135,53 +133,72 @@ class ModernStylesheet:
|
||||
}}
|
||||
QComboBox:hover {{ border: 1px solid {cls.COLORS['text_secondary']}; }}
|
||||
QComboBox:focus {{ border: 1.5px solid {cls.COLORS['border_focus']}; }}
|
||||
/* 扩大下拉按钮的点击热区并增加左侧分割线 */
|
||||
|
||||
QComboBox::drop-down {{
|
||||
subcontrol-origin: padding;
|
||||
subcontrol-position: top right;
|
||||
width: 32px; /* 把右侧热区加宽到 32px,更容易点中 */
|
||||
border-left: 1px solid {cls.COLORS['border_light']}; /* 加一条优雅的分割线 */
|
||||
width: 32px;
|
||||
border-left: 1px solid {cls.COLORS['border_light']};
|
||||
background-color: transparent;
|
||||
}}
|
||||
|
||||
/* 鼠标悬停在下拉框上时,右边的小区域背景微变,提供点击暗示 */
|
||||
QComboBox:hover::drop-down {{
|
||||
background-color: {cls.COLORS['hover']};
|
||||
border-top-right-radius: {cls.VARS['radius_md']};
|
||||
border-bottom-right-radius: {cls.VARS['radius_md']};
|
||||
}}
|
||||
|
||||
/* 纯 CSS 绘制的现代大号倒三角箭头 */
|
||||
QComboBox::down-arrow {{
|
||||
image: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent; /* 宽度加大到 6px */
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 7px solid {cls.COLORS['text_secondary']}; /* 高度加大到 7px,颜色使用次级文本色 */
|
||||
margin-top: 2px; /* 居中微调 */
|
||||
border-top: 7px solid {cls.COLORS['text_secondary']};
|
||||
margin-top: 2px;
|
||||
}}
|
||||
|
||||
/* 下拉展开时的箭头颜色加深变蓝,反馈更强烈 */
|
||||
QComboBox::down-arrow:on {{
|
||||
border-top: 7px solid {cls.COLORS['primary']};
|
||||
}}
|
||||
|
||||
QCheckBox {{ spacing: 8px; }}
|
||||
QCheckBox::indicator {{
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
/* ======================================================= */
|
||||
/* 【全局绝对统一化】复选框与单选框的经典同心圆样式 */
|
||||
/* ======================================================= */
|
||||
|
||||
QCheckBox, QRadioButton {{
|
||||
spacing: 8px;
|
||||
}}
|
||||
|
||||
/* 未选中状态:完美的空心灰边小圆圈 */
|
||||
QCheckBox::indicator, QRadioButton::indicator {{
|
||||
width: 14px; /* 内部内容宽 14px */
|
||||
height: 14px;
|
||||
/* 总宽 = 14 + 左边框1 + 右边框1 = 16px。16的一半是8,绝对的数学完美圆! */
|
||||
border-radius: 8px;
|
||||
border: 1px solid {cls.COLORS['border']};
|
||||
background-color: {cls.COLORS['panel_bg']};
|
||||
}}
|
||||
QCheckBox::indicator:hover {{ border: 1px solid {cls.COLORS['primary']}; }}
|
||||
QCheckBox::indicator:checked {{
|
||||
background-color: {cls.COLORS['primary']};
|
||||
|
||||
/* 悬停状态:边框变蓝 */
|
||||
QCheckBox::indicator:hover, QRadioButton::indicator:hover {{
|
||||
border: 1px solid {cls.COLORS['primary']};
|
||||
image: none;
|
||||
}}
|
||||
|
||||
/* 选中状态:外圈蓝 + 留白 + 内圈实心蓝点 (经典同心圆) */
|
||||
QCheckBox::indicator:checked, QRadioButton::indicator:checked {{
|
||||
border: 1px solid {cls.COLORS['primary']}; /* 外部蓝色细边框 */
|
||||
|
||||
/* 核心黑科技:利用 Qt 原生径向渐变画出完美的中心实心圆,彻底解决边框挤压导致的方块变形! */
|
||||
background: qradialgradient(
|
||||
cx: 0.5, cy: 0.5, radius: 0.5, fx: 0.5, fy: 0.5,
|
||||
stop: 0.0 {cls.COLORS['primary']}, /* 圆心向外 0~45% 为纯蓝色实心 */
|
||||
stop: 0.45 {cls.COLORS['primary']},
|
||||
stop: 0.55 {cls.COLORS['panel_bg']}, /* 55% 向外为白色(留白) */
|
||||
stop: 1.0 {cls.COLORS['panel_bg']}
|
||||
);
|
||||
image: none; /* 屏蔽掉原生的对勾或点 */
|
||||
}}
|
||||
|
||||
/* ======================================================= */
|
||||
|
||||
QScrollBar:vertical {{
|
||||
border: none;
|
||||
background: transparent;
|
||||
@ -224,7 +241,6 @@ class ModernStylesheet:
|
||||
|
||||
@classmethod
|
||||
def get_sidebar_stylesheet(cls) -> str:
|
||||
"""⚠️ 历史兼容:主框架依赖此方法绘制侧边导航栏"""
|
||||
return f"""
|
||||
QWidget {{ background-color: {cls.COLORS['panel_bg']}; border-right: 1px solid {cls.COLORS['border_light']}; }}
|
||||
QPushButton {{ text-align: left; padding: 12px 20px; border: none; background-color: transparent; font-size: 14px; font-weight: 500; border-left: 4px solid transparent; }}
|
||||
@ -235,5 +251,4 @@ class ModernStylesheet:
|
||||
|
||||
@classmethod
|
||||
def get_toolbar_stylesheet(cls) -> str:
|
||||
"""⚠️ 历史兼容:顶部工具栏样式"""
|
||||
return f"QWidget {{ background-color: {cls.COLORS['panel_bg']}; border-bottom: 1px solid {cls.COLORS['border_light']}; }}"
|
||||
Reference in New Issue
Block a user