#!/usr/bin/env python # -*- coding: utf-8 -*- """ Step5 面板 - 光谱提取 """ import os from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QGroupBox, QFormLayout, QLabel, QSpinBox, QPushButton, QCheckBox, QMessageBox, ) from PyQt5.QtGui import QFont from PyQt5.QtCore import Qt from src.gui.components.custom_widgets import FileSelectWidget from src.gui.styles import ModernStylesheet class Step5Panel(QWidget): """步骤5:光谱提取""" def __init__(self, parent=None): super().__init__(parent) self.init_ui() def init_ui(self): layout = QVBoxLayout() # 标题 title = QLabel("步骤5:训练样本光谱提取") title.setFont(QFont("Arial", 12, QFont.Bold)) layout.addWidget(title) # 去耀斑影像文件(用于独立运行) self.deglint_img_file = FileSelectWidget( "去耀斑影像:", "Image Files (*.bsq *.dat *.tif);;All Files (*.*)" ) layout.addWidget(self.deglint_img_file) # 处理后的CSV文件(用于独立运行) self.csv_file = FileSelectWidget( "处理后CSV:", "CSV Files (*.csv);;All Files (*.*)" ) layout.addWidget(self.csv_file) # 水体掩膜文件(可选,用于独立运行) self.water_mask_file = FileSelectWidget( "水体掩膜:", "Mask Files (*.dat *.tif);;All Files (*.*)" ) self.water_mask_file.line_edit.setPlaceholderText("可选,如不选择则自动生成") layout.addWidget(self.water_mask_file) self.glint_mask_file = FileSelectWidget( "耀斑掩膜:", "Mask Files (*.dat *.tif);;All Files (*.*)" ) layout.addWidget(self.glint_mask_file) step5_glint_hint = QLabel( "提示:独立运行本步骤时必须选择耀斑掩膜(通常为步骤2输出的 severe_glint_area.dat),用于在采样时避开耀斑像元。" ) step5_glint_hint.setWordWrap(True) step5_glint_hint.setStyleSheet("color: #666; font-size: 10px;") layout.addWidget(step5_glint_hint) # 参数设置 params_group = QGroupBox("提取参数") params_layout = QFormLayout() self.radius = QSpinBox() self.radius.setRange(1, 50) self.radius.setValue(5) params_layout.addRow("采样半径(像素):", self.radius) self.source_epsg = QSpinBox() self.source_epsg.setRange(1000, 99999) self.source_epsg.setValue(4326) params_layout.addRow("源坐标系EPSG:", self.source_epsg) params_group.setLayout(params_layout) layout.addWidget(params_group) # 输出文件路径 self.output_file = FileSelectWidget( "输出训练数据:", "CSV Files (*.csv);;All Files (*.*)" ) self.output_file.line_edit.setPlaceholderText("training_spectra.csv") layout.addWidget(self.output_file) # 启用步骤 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) layout.addStretch() self.setLayout(layout) # 信号连接:影像文件路径变化时动态更新波段范围 def get_config(self): """获取配置""" config = { 'radius': self.radius.value(), 'source_epsg': self.source_epsg.value(), } # 添加独立运行所需的文件路径 deglint_img_path = self.deglint_img_file.get_path() if deglint_img_path: config['deglint_img_path'] = deglint_img_path csv_path = self.csv_file.get_path() if csv_path: config['csv_path'] = csv_path water_mask_path = self.water_mask_file.get_path() if water_mask_path: config['boundary_path'] = water_mask_path glint_mask_path = self.glint_mask_file.get_path() if glint_mask_path: config['glint_mask_path'] = glint_mask_path # 注意:step5_extract_training_spectra 不接受 output_path / training_csv_path # 参数,输出路径由 pipeline 内部根据 training_spectra_dir 自动生成。 return config def set_config(self, config): """设置配置""" if 'radius' in config: self.radius.setValue(config['radius']) if 'source_epsg' in config: self.source_epsg.setValue(config['source_epsg']) if 'deglint_img_path' in config: self.deglint_img_file.set_path(config['deglint_img_path']) if 'csv_path' in config: self.csv_file.set_path(config['csv_path']) if 'boundary_path' in config: self.water_mask_file.set_path(config['boundary_path']) if 'glint_mask_path' in config: self.glint_mask_file.set_path(config['glint_mask_path']) def update_from_config(self, work_dir=None, pipeline=None): """从全局配置/Pipeline 或 Step1Panel 自动填充路径,实现上下游数据流转 Args: work_dir: 工作目录路径 pipeline: Pipeline 实例,用于获取步骤1生成的水域掩膜路径 """ # 保存工作目录引用 if work_dir: self.work_dir = work_dir elif hasattr(self, 'work_dir') and self.work_dir: pass else: self.work_dir = None # 1. 尝试从 Pipeline 获取水体掩膜路径 mask_path = None if pipeline and hasattr(pipeline, 'water_mask_path') and pipeline.water_mask_path: mask_path = pipeline.water_mask_path # 2. 如果 Pipeline 中没有,则尝试直接从 Step1 界面读取 main_window = self.window() if not mask_path and hasattr(main_window, 'step1_panel'): if main_window.step1_panel.use_ndwi_radio.isChecked(): mask_path = main_window.step1_panel.output_file.get_path() else: mask_path = main_window.step1_panel.mask_file.get_path() # 若为相对路径,使用 work_dir 合成为绝对路径 if mask_path and not os.path.isabs(mask_path): mask_path = os.path.join(self.work_dir or '', mask_path).replace('\\', '/') # 填充水体掩膜路径 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) # 3. 尝试从 Step2 界面读取耀斑掩膜路径 main_window = self.window() if hasattr(main_window, 'step2_panel'): glint_path = main_window.step2_panel.output_file.get_path() if glint_path: # 若为相对路径,使用 work_dir 合成为绝对路径 if not os.path.isabs(glint_path): glint_path = os.path.join(self.work_dir or '', glint_path).replace('\\', '/') self.glint_mask_file.set_path(glint_path) # 4. 自动填充输出路径(基于工作目录) if self.work_dir: output_dir = os.path.join(self.work_dir, "5_training_spectra") os.makedirs(output_dir, exist_ok=True) default_output_path = os.path.join(output_dir, "training_spectra.csv").replace('\\', '/') self.output_file.set_path(default_output_path) else: self.output_file.set_path("") # 5. 尝试从 Step4 界面读取已处理的水质参数 CSV 路径,自动填入本面板 main_window = self.window() if main_window and hasattr(main_window, 'step4_panel'): step4_output_path = main_window.step4_panel.output_file.get_path() if step4_output_path: # 若为相对路径,使用 work_dir 合成为绝对路径 if not os.path.isabs(step4_output_path): step4_output_path = os.path.join(self.work_dir or '', step4_output_path).replace('\\', '/') existing_csv = self.csv_file.get_path() if not existing_csv or not existing_csv.strip(): self.csv_file.set_path(step4_output_path) def run_step(self): """独立运行步骤5""" # 验证输入 deglint_img_path = self.deglint_img_file.get_path() csv_path = self.csv_file.get_path() if not deglint_img_path: QMessageBox.warning(self, "输入错误", "请选择去耀斑影像文件!") return if not csv_path: QMessageBox.warning(self, "输入错误", "请选择处理后的CSV文件!") return if not self.glint_mask_file.get_path(): QMessageBox.warning( self, "输入错误", "独立运行光谱特征提取时,必须选择耀斑掩膜文件。\n\n" "请提供与去耀斑影像对应的耀斑二值掩膜(一般为步骤2输出的 severe_glint_area.dat)。", ) return # 获取主窗口并运行步骤 main_window = self.window() if hasattr(main_window, 'run_single_step'): config = {'step5': self.get_config()} main_window.run_single_step('step5', config)