Files
WQ_GUI/src/new/views/step3_view.py
duxin 99aeab3076 refactor: 中间层消除硬编码波段号+CRS动态探测
- GlintRemovalStep/handler/service 全部改为传递波长参数而非波段索引
- ContentMapper 移除 EPSG:32651 硬编码, 新增 _ensure_crs() + _probe_crs_from_file()
- _prepare_shared_context 在 read_csv_data 前先探测边界文件 CRS
- GUI QSpinBox→QDoubleSpinBox,默认值改为波长(nm),placeholder 提示自动探测
- 消除 38/36/49/47/25/37/65/91 等所有魔法数字
2026-07-27 14:52:23 +08:00

275 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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())