# -*- coding: utf-8 -*- """ Step3View —— Step 3(耀斑去除)的端到端模块化 view UI 从 ``src/gui/panels/step3_panel.py`` 原样搬迁;含 4 种去耀斑方法 (Goodman / Kutser / Hedley / SUGAR)的参数组切换。 """ import os from PyQt5.QtWidgets import ( QCheckBox, QComboBox, QDoubleSpinBox, QFormLayout, QGroupBox, QLabel, QLineEdit, QPushButton, QSpinBox, QVBoxLayout, ) from src.gui.components.custom_widgets import FileSelectWidget from src.gui.styles import ModernStylesheet from src.new.core.base_view import BaseView def _resolve_subdir(work_dir: str, subdir_name: str) -> str: return os.path.join(work_dir, subdir_name).replace("\\", "/") class Step3View(BaseView): """Step 3: 耀斑去除""" def init_ui(self): layout = QVBoxLayout() # 影像文件 self.img_file = FileSelectWidget( "影像文件:", "Image Files (*.bsq *.dat *.tif);;All Files (*.*)", ) layout.addWidget(self.img_file) # 水域掩膜/边界 self.water_mask_file = FileSelectWidget( "水域掩膜/边界:", "Mask/Boundary (*.dat *.tif *.shp);;All Files (*.*)", ) layout.addWidget(self.water_mask_file) hint = QLabel( "提示:独立运行本步骤时必须选择水域掩膜或边界(与影像同区域的 .dat/.tif 掩膜,或 .shp 矢量)。" ) hint.setWordWrap(True) hint.setStyleSheet("color: #666; font-size: 10px;") layout.addWidget(hint) # 方法选择 method_group = QGroupBox("去耀斑方法") method_layout = QVBoxLayout() self.method = QComboBox() for text, data in [("Goodman方法", "goodman"), ("Kutser方法", "kutser"), ("Hedley方法", "hedley"), ("SUGAR算法", "sugar")]: self.method.addItem(text, data) self.method.currentIndexChanged.connect(self._on_method_changed) method_layout.addWidget(self.method) method_group.setLayout(method_layout) layout.addWidget(method_group) # Goodman 参数组 self.goodman_group = QGroupBox("Goodman方法参数") goodman_layout = QFormLayout() self.nir_lower_wavelength = QDoubleSpinBox() self.nir_lower_wavelength.setDecimals(2) self.nir_lower_wavelength.setRange(300.0, 3000.0) self.nir_lower_wavelength.setValue(641.93) self.nir_lower_wavelength.setSuffix(" nm") goodman_layout.addRow("NIR下界波长:", self.nir_lower_wavelength) self.nir_upper_wavelength = QDoubleSpinBox() self.nir_upper_wavelength.setDecimals(2) self.nir_upper_wavelength.setRange(300.0, 3000.0) self.nir_upper_wavelength.setValue(751.49) self.nir_upper_wavelength.setSuffix(" nm") goodman_layout.addRow("NIR上界波长:", self.nir_upper_wavelength) self.goodman_a = QDoubleSpinBox() self.goodman_a.setDecimals(6) self.goodman_a.setRange(0, 1) self.goodman_a.setValue(0.000019) goodman_layout.addRow("参数A:", self.goodman_a) self.goodman_b = QDoubleSpinBox() self.goodman_b.setDecimals(2) self.goodman_b.setRange(0, 1) self.goodman_b.setValue(0.1) goodman_layout.addRow("参数B:", self.goodman_b) self.goodman_group.setLayout(goodman_layout) layout.addWidget(self.goodman_group) # Kutser 参数组 self.kutser_group = QGroupBox("Kutser方法参数") kutser_layout = QFormLayout() self.oxy_wavelength = QDoubleSpinBox() self.oxy_wavelength.setDecimals(2) self.oxy_wavelength.setRange(300.0, 3000.0) self.oxy_wavelength.setValue(760.60) self.oxy_wavelength.setSuffix(" nm") kutser_layout.addRow("氧吸收波长:", self.oxy_wavelength) self.lower_wavelength = QDoubleSpinBox() self.lower_wavelength.setDecimals(2) self.lower_wavelength.setRange(300.0, 3000.0) self.lower_wavelength.setValue(742.39) self.lower_wavelength.setSuffix(" nm") kutser_layout.addRow("左肩波长:", self.lower_wavelength) self.upper_wavelength = QDoubleSpinBox() self.upper_wavelength.setDecimals(2) self.upper_wavelength.setRange(300.0, 3000.0) self.upper_wavelength.setValue(860.48) self.upper_wavelength.setSuffix(" nm") kutser_layout.addRow("右肩波长:", self.upper_wavelength) self.nir_wavelength = QDoubleSpinBox() self.nir_wavelength.setDecimals(2) self.nir_wavelength.setRange(300.0, 3000.0) self.nir_wavelength.setValue(842.36) self.nir_wavelength.setSuffix(" nm") kutser_layout.addRow("NIR参考波长:", self.nir_wavelength) self.kutser_group.setLayout(kutser_layout) self.kutser_group.setVisible(False) layout.addWidget(self.kutser_group) # Hedley 参数组 self.hedley_group = QGroupBox("Hedley方法参数") hedley_layout = QFormLayout() self.hedley_nir_wavelength = QDoubleSpinBox() self.hedley_nir_wavelength.setDecimals(2) self.hedley_nir_wavelength.setRange(300.0, 3000.0) self.hedley_nir_wavelength.setValue(842.36) self.hedley_nir_wavelength.setSuffix(" nm") hedley_layout.addRow("NIR参考波长:", self.hedley_nir_wavelength) self.hedley_group.setLayout(hedley_layout) self.hedley_group.setVisible(False) layout.addWidget(self.hedley_group) # SUGAR 参数组 self.sugar_group = QGroupBox("SUGAR方法参数") sugar_layout = QFormLayout() self.sugar_iter = QSpinBox() self.sugar_iter.setRange(1, 20) self.sugar_iter.setValue(3) sugar_layout.addRow("迭代次数:", self.sugar_iter) self.sugar_sigma = QDoubleSpinBox() self.sugar_sigma.setDecimals(2) self.sugar_sigma.setRange(0.1, 10) self.sugar_sigma.setValue(1.0) sugar_layout.addRow("LoG平滑σ:", self.sugar_sigma) self.sugar_estimate_background = QCheckBox() self.sugar_estimate_background.setChecked(True) sugar_layout.addRow("估计背景光谱:", self.sugar_estimate_background) self.sugar_glint_mask_method = QComboBox() self.sugar_glint_mask_method.addItems(["cdf", "otsu"]) sugar_layout.addRow("耀斑掩膜方法:", self.sugar_glint_mask_method) self.sugar_termination_thresh = QDoubleSpinBox() self.sugar_termination_thresh.setDecimals(2) self.sugar_termination_thresh.setRange(1, 100) self.sugar_termination_thresh.setValue(20.0) sugar_layout.addRow("终止阈值:", self.sugar_termination_thresh) self.sugar_bounds = QLineEdit() self.sugar_bounds.setText("[(1, 2)]") sugar_layout.addRow("优化边界:", self.sugar_bounds) self.sugar_group.setLayout(sugar_layout) self.sugar_group.setVisible(False) layout.addWidget(self.sugar_group) # 插值选项 interp_group = QGroupBox("0值像素插值") interp_layout = QFormLayout() self.interpolate_zeros = QCheckBox("启用插值") interp_layout.addRow("", self.interpolate_zeros) self.interp_method = QComboBox() for text, data in [("最近邻插值", "nearest"), ("双线性插值", "bilinear"), ("样条插值", "spline"), ("克里金插值", "kriging")]: self.interp_method.addItem(text, data) self.interp_method.setCurrentIndex(1) interp_layout.addRow("插值方法:", self.interp_method) interp_group.setLayout(interp_layout) layout.addWidget(interp_group) # 输出文件路径 self.output_file = FileSelectWidget( "输出影像:", "Image Files (*.bsq *.dat *.tif);;All Files (*.*)", ) self.output_file.line_edit.setPlaceholderText("deglint_image.bsq") layout.addWidget(self.output_file) # 启用步骤 self.enable_checkbox = QCheckBox("启用此步骤") self.enable_checkbox.setChecked(True) layout.addWidget(self.enable_checkbox) # 执行按钮 self.run_btn = QPushButton("执行 Step 3: 耀斑去除") self.run_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet("success")) self.run_btn.clicked.connect(self._on_run_clicked) layout.addWidget(self.run_btn) layout.addStretch() self.setLayout(layout) def _on_method_changed(self, index): """方法切换时显示对应参数组""" method_id = self.method.currentData() self.goodman_group.setVisible(method_id == "goodman") self.kutser_group.setVisible(method_id == "kutser") self.hedley_group.setVisible(method_id == "hedley") self.sugar_group.setVisible(method_id == "sugar") def get_config(self) -> dict: config = { "img_path": self.img_file.get_path(), "method": self.method.currentData(), "enabled": self.enable_checkbox.isChecked(), "interpolate_zeros": self.interpolate_zeros.isChecked(), "interpolation_method": self.interp_method.currentData(), } water_mask_path = self.water_mask_file.get_path() if water_mask_path: config["water_mask_path"] = water_mask_path output_path = self.output_file.get_path() if output_path: config["output_path"] = output_path method = self.method.currentData() if method == "goodman": config["nir_lower_wavelength"] = self.nir_lower_wavelength.value() config["nir_upper_wavelength"] = self.nir_upper_wavelength.value() config["goodman_A"] = self.goodman_a.value() config["goodman_B"] = self.goodman_b.value() elif method == "kutser": config["oxy_wavelength"] = self.oxy_wavelength.value() config["lower_wavelength"] = self.lower_wavelength.value() config["upper_wavelength"] = self.upper_wavelength.value() config["nir_wavelength"] = self.nir_wavelength.value() elif method == "hedley": config["hedley_nir_wavelength"] = self.hedley_nir_wavelength.value() elif method == "sugar": config["sugar_iter"] = self.sugar_iter.value() config["sugar_sigma"] = self.sugar_sigma.value() config["sugar_estimate_background"] = self.sugar_estimate_background.isChecked() config["sugar_glint_mask_method"] = self.sugar_glint_mask_method.currentText() config["sugar_termination_thresh"] = self.sugar_termination_thresh.value() try: import ast config["sugar_bounds"] = ast.literal_eval(self.sugar_bounds.text()) except Exception: config["sugar_bounds"] = [(1, 2)] return config def set_config(self, config: dict): if config.get("img_path"): self.img_file.set_path(config["img_path"]) if config.get("water_mask_path"): self.water_mask_file.set_path(config["water_mask_path"]) if config.get("output_path"): self.output_file.set_path(config["output_path"]) if config.get("method"): idx = self.method.findData(config["method"]) if idx >= 0: self.method.setCurrentIndex(idx) if "enabled" in config: self.enable_checkbox.setChecked(config["enabled"]) def update_work_directory(self, work_dir: str): super().update_work_directory(work_dir) if work_dir: output_dir = _resolve_subdir(work_dir, "3_Deglint") os.makedirs(output_dir, exist_ok=True) default_output_path = os.path.join(output_dir, "deglint_image.bsq").replace("\\", "/") self.output_file.set_path(default_output_path) def _on_run_clicked(self): self.dispatch_execute("step3", self.get_config())