#!/usr/bin/env python # -*- coding: utf-8 -*- """ Step1 面板 - 水域掩膜生成 """ import os from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel, QDoubleSpinBox, QCheckBox, QPushButton, QFormLayout, QRadioButton, QMessageBox, ) from PyQt5.QtCore import Qt # 从公共组件库导入 from src.gui.components.custom_widgets import FileSelectWidget from src.gui.styles import ModernStylesheet class Step1Panel(QWidget): """1. 水域掩膜生成""" def __init__(self, parent=None): super().__init__(parent) self.init_ui() def init_ui(self): layout = QVBoxLayout() # 标题 # 掩膜生成方式选择 method_group = QGroupBox("掩膜生成方式") method_layout = QVBoxLayout() # 使用现有掩膜文件 self.use_existing_radio = QRadioButton("使用现有掩膜文件") self.use_existing_radio.setChecked(True) method_layout.addWidget(self.use_existing_radio) # 使用NDWI自动生成 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) # 掩膜文件选择 self.mask_file = FileSelectWidget( "掩膜文件:", "Shapefiles (*.shp);;Raster Files (*.dat *.tif);;All Files (*.*)" ) layout.addWidget(self.mask_file) # 影像文件选择(用于shp栅格化或NDWI生成) self.img_file = FileSelectWidget( "参考影像:", "Image Files (*.bsq *.dat *.tif);;All Files (*.*)" ) layout.addWidget(self.img_file) # NDWI参数设置 self.ndwi_group = QGroupBox("NDWI参数设置") ndwi_layout = QVBoxLayout() # NDWI阈值 threshold_layout = QHBoxLayout() threshold_layout.addWidget(QLabel("NDWI阈值:")) self.ndwi_threshold = QDoubleSpinBox() self.ndwi_threshold.setRange(0.0, 1.0) self.ndwi_threshold.setSingleStep(0.05) self.ndwi_threshold.setValue(0.4) self.ndwi_threshold.setDecimals(2) threshold_layout.addWidget(self.ndwi_threshold) threshold_layout.addStretch() ndwi_layout.addLayout(threshold_layout) self.ndwi_group.setLayout(ndwi_layout) layout.addWidget(self.ndwi_group) # 输出文件路径(使用save模式) self.output_file = FileSelectWidget( "输出掩膜:", "Mask Files (*.dat *.tif);;All Files (*.*)", mode="save" ) self.output_file.line_edit.setPlaceholderText("water_mask.dat") layout.addWidget(self.output_file) # 提示信息 - 专业的 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) # 启用步骤 self.enable_checkbox = QCheckBox("启用此步骤") self.enable_checkbox.setChecked(True) layout.addWidget(self.enable_checkbox) # 独立运行按钮 self.run_btn = QPushButton("独立运行此步骤") self.run_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('success')) self.run_btn.clicked.connect(self.run_step) layout.addWidget(self.run_btn) # 连接信号 self.use_existing_radio.toggled.connect(self.update_ui_state) self.use_ndwi_radio.toggled.connect(self.update_ui_state) layout.addStretch() self.setLayout(layout) # 初始UI状态 self.update_ui_state() def update_ui_state(self): """根据选择的掩膜生成方式更新UI状态(使用显示/隐藏控制)""" use_ndwi = self.use_ndwi_radio.isChecked() # 动态显示/隐藏组件 if use_ndwi: # 使用NDWI模式:隐藏掩膜文件,显示NDWI参数和输出掩膜 self.mask_file.setVisible(False) self.ndwi_group.setVisible(True) self.output_file.setVisible(True) # 显示输出掩膜路径 # 当切换到NDWI模式时,如果工作目录已设置,自动填充输出路径 if hasattr(self, 'work_dir') and self.work_dir: self._auto_fill_output_path() else: # 使用现有掩膜模式:显示掩膜文件,隐藏NDWI参数和输出掩膜 self.mask_file.setVisible(True) self.ndwi_group.setVisible(False) self.output_file.setVisible(False) # 隐藏输出掩膜路径 # 参考影像在两种模式下都显示 self.img_file.setVisible(True) def update_work_directory(self, work_dir): """ 保存工作目录引用,用于后续自动填充路径 Args: work_dir: 工作目录路径 """ if not work_dir: return # 保存工作目录引用 self.work_dir = work_dir # 如果当前选中的是NDWI模式,立即填充输出路径 if self.use_ndwi_radio.isChecked(): self._auto_fill_output_path() def _auto_fill_output_path(self): """ 自动填充输出掩膜路径(仅在NDWI模式下) 确保路径使用正斜杠,避免斜杠混用 """ if not hasattr(self, 'work_dir') or not self.work_dir: return # 生成输出掩膜的完整路径 output_dir = os.path.join(self.work_dir, "1_water_mask") os.makedirs(output_dir, exist_ok=True) # 确保目录存在 # 统一使用正斜杠,避免 \ 和 / 混用 default_output_path = os.path.join(output_dir, "water_mask_out.dat").replace('\\', '/') self.output_file.set_path(default_output_path) def get_config(self): """获取配置""" use_ndwi = self.use_ndwi_radio.isChecked() config = { 'mask_path': None if use_ndwi else self.mask_file.get_path(), 'use_ndwi': use_ndwi, 'ndwi_threshold': self.ndwi_threshold.value() } # 参考影像路径(两种模式都可能需要) img_path = self.img_file.get_path() if img_path: config['img_path'] = img_path # 输出路径:仅在NDWI模式下有效 if use_ndwi: output_path = self.output_file.get_path() if output_path: config['output_path'] = output_path else: # 使用现有掩膜时,不传递output_path,避免底层错误尝试保存文件 config['output_path'] = None return config def set_config(self, config): """设置配置""" if 'mask_path' in config: self.mask_file.set_path(config['mask_path']) if 'img_path' in config: self.img_file.set_path(config['img_path']) if 'output_path' in config: self.output_file.set_path(config['output_path']) if 'use_ndwi' in config: if config['use_ndwi']: self.use_ndwi_radio.setChecked(True) else: self.use_existing_radio.setChecked(True) if 'ndwi_threshold' in config: self.ndwi_threshold.setValue(config['ndwi_threshold']) self.update_ui_state() def run_step(self): """独立运行步骤1""" # 验证输入 if self.use_ndwi_radio.isChecked(): # NDWI模式:需要影像文件 img_path = self.img_file.get_path() if not img_path: QMessageBox.warning(self, "输入错误", "请选择参考影像文件!") return else: # 现有掩膜模式:需要掩膜文件 mask_path = self.mask_file.get_path() if not mask_path: QMessageBox.warning(self, "输入错误", "请选择掩膜文件!") return # 如果是shp文件,还需要影像文件 if mask_path.lower().endswith('.shp'): img_path = self.img_file.get_path() if not img_path: QMessageBox.warning(self, "输入错误", "当使用shp文件时,需要提供参考影像用于栅格化!") return # 获取父窗口并运行步骤 parent = self.parent() while parent and not hasattr(parent, 'run_single_step'): parent = parent.parent() if parent and hasattr(parent, 'run_single_step'): config = {'step1': self.get_config()} parent.run_single_step("step1", config)