feat: 水质分析系统用户体验核心升级
This commit is contained in:
@ -242,7 +242,8 @@ class WaterQualityInversionPipeline:
|
||||
ndwi_threshold: float = 0.4,
|
||||
use_ndwi: bool = False,
|
||||
skip_dependency_check: bool = False,
|
||||
generate_png: bool = True) -> str:
|
||||
generate_png: bool = True,
|
||||
output_path: Optional[str] = None) -> str:
|
||||
"""
|
||||
步骤1: 生成或设置水域mask
|
||||
|
||||
@ -265,6 +266,8 @@ class WaterQualityInversionPipeline:
|
||||
ndwi_threshold: NDWI阈值(当use_ndwi=True时使用)
|
||||
use_ndwi: 是否使用NDWI方法从影像生成水体掩膜
|
||||
generate_png: 是否生成输入影像的PNG预览图(默认True)
|
||||
output_path: 指定输出掩膜文件的保存路径(可选)。如果提供,掩膜将保存到此路径;
|
||||
如果为None,则使用默认路径(self.water_mask_dir)
|
||||
|
||||
Returns:
|
||||
dat格式的水域掩膜文件路径
|
||||
@ -287,12 +290,19 @@ class WaterQualityInversionPipeline:
|
||||
raise ValueError(f"影像文件不存在: {img_path}")
|
||||
|
||||
print(f"使用NDWI方法从影像生成水体掩膜,阈值={ndwi_threshold}...")
|
||||
output_path = str(self.water_mask_dir / "water_mask_from_ndwi.dat")
|
||||
|
||||
# 使用用户指定的输出路径,或使用默认路径
|
||||
if output_path:
|
||||
ndwi_output_path = output_path
|
||||
# 确保输出目录存在
|
||||
os.makedirs(Path(output_path).parent, exist_ok=True)
|
||||
else:
|
||||
ndwi_output_path = str(self.water_mask_dir / "water_mask_from_ndwi.dat")
|
||||
|
||||
# 检查文件是否已存在,避免重复生成
|
||||
if Path(output_path).exists():
|
||||
print(f"检测到已存在的NDWI掩膜文件,直接使用: {output_path}")
|
||||
self.water_mask_path = output_path
|
||||
if Path(ndwi_output_path).exists():
|
||||
print(f"检测到已存在的NDWI掩膜文件,直接使用: {ndwi_output_path}")
|
||||
self.water_mask_path = ndwi_output_path
|
||||
step_end_time = time.time()
|
||||
self._record_step_time("步骤1: 生成水域mask", step_start_time, step_end_time, status="skipped")
|
||||
print(f"水域掩膜已设置: {self.water_mask_path}")
|
||||
@ -306,8 +316,8 @@ class WaterQualityInversionPipeline:
|
||||
|
||||
# 执行NDWI水体提取
|
||||
from src.utils.extract_water_area import ndwi
|
||||
ndwi(img_path, ndwi_threshold, output_path)
|
||||
self.water_mask_path = output_path
|
||||
ndwi(img_path, ndwi_threshold, ndwi_output_path)
|
||||
self.water_mask_path = ndwi_output_path
|
||||
step_end_time = time.time()
|
||||
self._record_step_time("步骤1: 生成水域mask", step_start_time, step_end_time)
|
||||
print(f"已生成NDWI水体掩膜: {self.water_mask_path}")
|
||||
@ -333,12 +343,19 @@ class WaterQualityInversionPipeline:
|
||||
raise ValueError("当mask_path为shp格式时,必须提供img_path参数用于栅格化")
|
||||
|
||||
print(f"检测到shp格式的水体掩膜,正在转换为dat格式...")
|
||||
output_path = str(self.water_mask_dir / "water_mask_from_shp.dat")
|
||||
|
||||
# 使用用户指定的输出路径,或使用默认路径
|
||||
if output_path:
|
||||
shp_output_path = output_path
|
||||
# 确保输出目录存在
|
||||
os.makedirs(Path(output_path).parent, exist_ok=True)
|
||||
else:
|
||||
shp_output_path = str(self.water_mask_dir / "water_mask_from_shp.dat")
|
||||
|
||||
# 检查文件是否已存在,避免重复栅格化
|
||||
if Path(output_path).exists():
|
||||
print(f"检测到已存在的栅格化掩膜文件,直接使用: {output_path}")
|
||||
self.water_mask_path = output_path
|
||||
if Path(shp_output_path).exists():
|
||||
print(f"检测到已存在的栅格化掩膜文件,直接使用: {shp_output_path}")
|
||||
self.water_mask_path = shp_output_path
|
||||
step_end_time = time.time()
|
||||
self._record_step_time("步骤1: 生成水域mask", step_start_time, step_end_time, status="skipped")
|
||||
print(f"水域掩膜已设置: {self.water_mask_path}")
|
||||
@ -352,8 +369,8 @@ class WaterQualityInversionPipeline:
|
||||
|
||||
# 执行栅格化
|
||||
from src.utils.extract_water_area import rasterize_shp
|
||||
rasterize_shp(mask_path, output_path, img_path)
|
||||
self.water_mask_path = output_path
|
||||
rasterize_shp(mask_path, shp_output_path, img_path)
|
||||
self.water_mask_path = shp_output_path
|
||||
step_end_time = time.time()
|
||||
self._record_step_time("步骤1: 生成水域mask", step_start_time, step_end_time)
|
||||
print(f"已生成dat格式的水域掩膜: {self.water_mask_path}")
|
||||
@ -483,13 +500,13 @@ class WaterQualityInversionPipeline:
|
||||
fontsize=12, fontweight='bold')
|
||||
ax.axis('off')
|
||||
|
||||
# 添加比例尺信息
|
||||
# 添加比例尺信息(白色文字,黑色背景下清晰可见)
|
||||
geo_transform = dataset.GetGeoTransform()
|
||||
if geo_transform:
|
||||
pixel_size_x = abs(geo_transform[1])
|
||||
pixel_size_y = abs(geo_transform[5])
|
||||
scale_text = f"分辨率: {pixel_size_x:.2f}m x {pixel_size_y:.2f}m | 尺寸: {width} x {height}"
|
||||
fig.text(0.5, 0.02, scale_text, ha='center', fontsize=10, style='italic')
|
||||
fig.text(0.5, 0.02, scale_text, ha='center', fontsize=10, style='italic', color='white')
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig(png_path, dpi=150, bbox_inches='tight', pad_inches=0.1)
|
||||
|
||||
@ -838,9 +838,19 @@ class VisualizationWorkerThread(QThread):
|
||||
|
||||
class FileSelectWidget(QWidget):
|
||||
"""文件选择组件"""
|
||||
def __init__(self, label_text, file_filter="All Files (*.*)", parent=None):
|
||||
def __init__(self, label_text, file_filter="All Files (*.*)", mode="open", parent=None):
|
||||
"""
|
||||
初始化文件选择组件
|
||||
|
||||
Args:
|
||||
label_text: 标签文本
|
||||
file_filter: 文件过滤器
|
||||
mode: 选择模式 - "open"(打开文件) 或 "save"(保存文件)
|
||||
parent: 父控件
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.file_filter = file_filter
|
||||
self.mode = mode # "open" 或 "save"
|
||||
self.init_ui(label_text)
|
||||
|
||||
def init_ui(self, label_text):
|
||||
@ -850,7 +860,8 @@ class FileSelectWidget(QWidget):
|
||||
self.label = QLabel(label_text)
|
||||
self.label.setMinimumWidth(120)
|
||||
self.line_edit = QLineEdit()
|
||||
self.line_edit.setPlaceholderText("请选择文件...")
|
||||
placeholder = "请选择保存路径..." if self.mode == "save" else "请选择文件..."
|
||||
self.line_edit.setPlaceholderText(placeholder)
|
||||
self.browse_btn = QPushButton("浏览...")
|
||||
self.browse_btn.setMaximumWidth(80)
|
||||
self.browse_btn.clicked.connect(self.browse_file)
|
||||
@ -863,6 +874,11 @@ class FileSelectWidget(QWidget):
|
||||
|
||||
def browse_file(self):
|
||||
"""浏览文件"""
|
||||
if self.mode == "save":
|
||||
file_path, _ = QFileDialog.getSaveFileName(
|
||||
self, "保存文件", "", self.file_filter
|
||||
)
|
||||
else:
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择文件", "", self.file_filter
|
||||
)
|
||||
@ -943,6 +959,29 @@ class Step1Panel(QWidget):
|
||||
self.use_ndwi_radio = QRadioButton("使用NDWI自动生成")
|
||||
method_layout.addWidget(self.use_ndwi_radio)
|
||||
|
||||
# 应用QRadioButton样式(实心选中点)
|
||||
radio_style = """
|
||||
QRadioButton::indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #999;
|
||||
}
|
||||
QRadioButton::indicator:checked {
|
||||
background-color: #0078D7;
|
||||
border: 2px solid #0078D7;
|
||||
}
|
||||
QRadioButton::indicator:unchecked {
|
||||
background-color: white;
|
||||
border: 2px solid #999;
|
||||
}
|
||||
QRadioButton::indicator:hover {
|
||||
border: 2px solid #0078D7;
|
||||
}
|
||||
"""
|
||||
self.use_existing_radio.setStyleSheet(radio_style)
|
||||
self.use_ndwi_radio.setStyleSheet(radio_style)
|
||||
|
||||
method_group.setLayout(method_layout)
|
||||
layout.addWidget(method_group)
|
||||
|
||||
@ -961,7 +1000,7 @@ class Step1Panel(QWidget):
|
||||
layout.addWidget(self.img_file)
|
||||
|
||||
# NDWI参数设置
|
||||
ndwi_group = QGroupBox("NDWI参数设置")
|
||||
self.ndwi_group = QGroupBox("NDWI参数设置")
|
||||
ndwi_layout = QVBoxLayout()
|
||||
|
||||
# NDWI阈值
|
||||
@ -976,20 +1015,33 @@ class Step1Panel(QWidget):
|
||||
threshold_layout.addStretch()
|
||||
ndwi_layout.addLayout(threshold_layout)
|
||||
|
||||
ndwi_group.setLayout(ndwi_layout)
|
||||
layout.addWidget(ndwi_group)
|
||||
self.ndwi_group.setLayout(ndwi_layout)
|
||||
layout.addWidget(self.ndwi_group)
|
||||
|
||||
# 输出文件路径
|
||||
# 输出文件路径(使用save模式)
|
||||
self.output_file = FileSelectWidget(
|
||||
"输出掩膜:",
|
||||
"Mask Files (*.dat *.tif);;All Files (*.*)"
|
||||
"Mask Files (*.dat *.tif);;All Files (*.*)",
|
||||
mode="save"
|
||||
)
|
||||
self.output_file.line_edit.setPlaceholderText("water_mask.dat")
|
||||
layout.addWidget(self.output_file)
|
||||
|
||||
# 提示信息
|
||||
hint = QLabel("提示: 如果掩膜文件是Shapefile(.shp),需要提供参考影像用于栅格化;如果使用NDWI自动生成,只需要提供参考影像")
|
||||
hint.setStyleSheet("color: #666; font-size: 10px;")
|
||||
# 提示信息 - 专业的 Info Alert 样式
|
||||
hint = QLabel("💡 提示: 如果掩膜文件是Shapefile(.shp),需要提供参考影像用于栅格化;如果使用NDWI自动生成,只需要提供参考影像")
|
||||
hint.setWordWrap(True) # 允许自动换行
|
||||
hint.setStyleSheet("""
|
||||
QLabel {
|
||||
color: #0055D4;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
background-color: #E8F4FF;
|
||||
border: 2px solid #0055D4;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin: 8px 0px;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(hint)
|
||||
|
||||
# 启用步骤
|
||||
@ -1014,21 +1066,39 @@ class Step1Panel(QWidget):
|
||||
self.update_ui_state()
|
||||
|
||||
def update_ui_state(self):
|
||||
"""根据选择的掩膜生成方式更新UI状态"""
|
||||
"""根据选择的掩膜生成方式更新UI状态(使用显示/隐藏控制)"""
|
||||
use_ndwi = self.use_ndwi_radio.isChecked()
|
||||
|
||||
# 掩膜文件在NDWI模式下禁用
|
||||
self.mask_file.setEnabled(not use_ndwi)
|
||||
# 动态显示/隐藏组件
|
||||
if use_ndwi:
|
||||
# 使用NDWI模式:隐藏掩膜文件,显示NDWI参数
|
||||
self.mask_file.setVisible(False)
|
||||
self.ndwi_group.setVisible(True)
|
||||
else:
|
||||
# 使用现有掩膜模式:显示掩膜文件,隐藏NDWI参数
|
||||
self.mask_file.setVisible(True)
|
||||
self.ndwi_group.setVisible(False)
|
||||
|
||||
# 影像文件在两种模式下都需要
|
||||
self.img_file.setEnabled(True)
|
||||
# 参考影像和输出掩膜在两种模式下都显示
|
||||
self.img_file.setVisible(True)
|
||||
self.output_file.setVisible(True)
|
||||
|
||||
# NDWI参数在NDWI模式下启用
|
||||
for i in range(self.layout().count()):
|
||||
widget = self.layout().itemAt(i).widget()
|
||||
if widget and isinstance(widget, QGroupBox) and widget.title() == "NDWI参数设置":
|
||||
widget.setEnabled(use_ndwi)
|
||||
break
|
||||
def update_work_directory(self, work_dir):
|
||||
"""
|
||||
接收主窗口传来的工作目录,自动填充输出路径
|
||||
|
||||
Args:
|
||||
work_dir: 工作目录路径
|
||||
"""
|
||||
if not work_dir:
|
||||
return
|
||||
|
||||
# 自动生成输出掩膜的完整路径
|
||||
output_dir = os.path.join(work_dir, "1_water_mask")
|
||||
os.makedirs(output_dir, exist_ok=True) # 确保目录存在
|
||||
|
||||
default_output_path = os.path.join(output_dir, "water_mask_out.dat")
|
||||
self.output_file.set_path(default_output_path)
|
||||
|
||||
def get_config(self):
|
||||
"""获取配置"""
|
||||
@ -5393,6 +5463,7 @@ class WaterQualityGUI(QMainWindow):
|
||||
self.pipeline = None
|
||||
self.worker = None
|
||||
self.config_file = None
|
||||
self.work_dir = None # 工作目录
|
||||
|
||||
# 训练数据模式状态
|
||||
self.has_training_data = True # 默认有训练数据
|
||||
@ -5407,6 +5478,10 @@ class WaterQualityGUI(QMainWindow):
|
||||
self.apply_stylesheet()
|
||||
self._disable_wheel_for_all_spinboxes()
|
||||
|
||||
# 延迟调用工作目录选择对话框,确保主界面已完全渲染
|
||||
# 100ms 延迟足以让 GUI 事件循环启动并显示主窗口
|
||||
QTimer.singleShot(100, self.init_workspace)
|
||||
|
||||
def _init_step_dependencies(self):
|
||||
"""初始化步骤依赖关系和标准输出路径"""
|
||||
# 定义每个步骤的标准输出路径模式(相对于工作目录)
|
||||
@ -5550,6 +5625,59 @@ class WaterQualityGUI(QMainWindow):
|
||||
combobox.setFocusPolicy(Qt.StrongFocus)
|
||||
combobox.wheelEvent = lambda event, cb=combobox: None
|
||||
|
||||
def init_workspace(self):
|
||||
"""
|
||||
初始化工作空间:弹出对话框选择工作目录
|
||||
此方法通过 QTimer 延迟调用,确保主界面已完全渲染后再弹出对话框
|
||||
如果用户取消或关闭,则退出程序
|
||||
"""
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
msg_box = QMessageBox()
|
||||
msg_box.setIcon(QMessageBox.Information)
|
||||
msg_box.setWindowTitle("选择工作目录")
|
||||
msg_box.setText("欢迎使用水质参数反演分析系统!\n\n请选择工作目录来保存所有分析结果。")
|
||||
msg_box.setInformativeText("工作目录将用于存储:\n• 水域掩膜文件\n• 耀斑检测结果\n• 模型训练数据\n• 预测结果与分布图\n\n点击'确定'选择目录")
|
||||
msg_box.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
|
||||
msg_box.setDefaultButton(QMessageBox.Ok)
|
||||
|
||||
result = msg_box.exec_()
|
||||
|
||||
if result == QMessageBox.Cancel:
|
||||
QMessageBox.warning(None, "取消操作", "未选择工作目录,程序将退出。")
|
||||
sys.exit(0)
|
||||
|
||||
# 弹出目录选择对话框
|
||||
work_dir = QFileDialog.getExistingDirectory(
|
||||
self, # 使用 self 作为父窗口,而不是 None
|
||||
"选择工作目录",
|
||||
"",
|
||||
QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks
|
||||
)
|
||||
|
||||
if not work_dir:
|
||||
QMessageBox.critical(self, "错误", "必须选择工作目录才能使用系统!\n程序即将退出。")
|
||||
sys.exit(0)
|
||||
|
||||
self.work_dir = work_dir
|
||||
print(f"✓ 已选择工作目录: {self.work_dir}")
|
||||
|
||||
# 选择完成后,自动填充输出路径
|
||||
self._auto_fill_output_paths()
|
||||
|
||||
def _auto_fill_output_paths(self):
|
||||
"""
|
||||
根据工作目录自动填充各步骤的输出路径
|
||||
"""
|
||||
if not self.work_dir:
|
||||
return
|
||||
|
||||
# Step1: 输出掩膜路径
|
||||
if hasattr(self, 'step1_panel') and hasattr(self.step1_panel, 'output_file'):
|
||||
default_mask_path = os.path.join(self.work_dir, "1_water_mask", "water_mask_out.dat")
|
||||
self.step1_panel.output_file.set_path(default_mask_path)
|
||||
self.step1_panel.update_work_directory(self.work_dir)
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化UI"""
|
||||
self.setWindowTitle("水质参数反演分析系统 v1.0")
|
||||
|
||||
Reference in New Issue
Block a user