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