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 等所有魔法数字
This commit is contained in:
duxin
2026-07-27 14:52:23 +08:00
parent f6d61693e3
commit 99aeab3076
14 changed files with 263 additions and 275 deletions

View File

@ -1163,62 +1163,38 @@ class WaterQualityGUI(QMainWindow):
if max_band <= 0:
return True
# 3) 不同方法对应不同的波段字段(cfg_key, panel_attr, 推荐值, 标签)
# 3) 波长驱动改造:波段号由 find_band_number() 从 HDR 动态解析,
# 不再需要波段索引越界检查。仅对极端波长值做合理性软警告。
method = step3_cfg.get('method', 'goodman')
if method == 'goodman':
band_fields = [
('nir_lower', 'nir_lower', 65, 'NIR下波段'),
('nir_upper', 'nir_upper', 91, 'NIR上波段'),
]
elif method == 'kutser':
band_fields = [
('oxy_band', 'oxy_band', 38, '氧吸收波段'),
('lower_oxy', 'lower_oxy', 36, '下氧吸收波段'),
('upper_oxy', 'upper_oxy', 49, '上氧吸收波段'),
('nir_band', 'nir_band', 47, 'NIR波段'),
]
elif method == 'hedley':
band_fields = [
('hedley_nir_band', 'hedley_nir_band', 47, 'NIR波段'),
]
else: # sugar 无波段索引
_wavelength_fields = {
'goodman': [
('nir_lower_wavelength', 641.93, 'NIR下界波长(nm)'),
('nir_upper_wavelength', 751.49, 'NIR上界波长(nm)'),
],
'kutser': [
('oxy_wavelength', 760.60, '氧吸收波长(nm)'),
('lower_wavelength', 742.39, '下氧吸收波长(nm)'),
('upper_wavelength', 860.48, '上氧吸收波长(nm)'),
('nir_wavelength', 842.36, 'NIR波长(nm)'),
],
'hedley': [
('hedley_nir_wavelength', 842.36, 'NIR波长(nm)'),
],
}.get(method, [])
# SUGAR 等方法无波长参数,跳过
if not _wavelength_fields:
return True
# 4) 逐字段检查;遇到第一个越界就弹窗(用户处理完继续检查下一个)
for cfg_key, panel_attr, recommended, label in band_fields:
for cfg_key, recommended, label in _wavelength_fields:
requested = step3_cfg.get(cfg_key)
if requested is None or requested <= max_band:
continue # 没设 / 没越界
self.log_message(
f"⚠ step3 波段越界:{label}={requested} > 影像波段数 {max_band}",
"warning",
)
dlg = BandConfirmDialog(
self,
requested_band=requested,
max_band=max_band,
recommended_band=recommended,
method_label=label,
)
result = dlg.exec_()
if result == QDialog.Rejected:
self.log_message("✗ 用户取消运行(step3 波段越界未解决)", "warning")
return False
new_band = dlg.selected_band()
try:
spin = getattr(step3_panel, panel_attr)
spin.setValue(new_band)
except AttributeError:
self.log_message(f"⚠ step3 panel 缺控件 {panel_attr},跳过回写", "warning")
if requested is None:
continue
self.log_message(
f"✓ {label}:{requested} → {new_band}(影像最多 {max_band} 波段)",
"info",
)
# 波长合理性软警告(find_band_number 内部会自动适配)
if requested < 300 or requested > 3000:
self.log_message(
f"⚠ step3 波长异常:{label}={requested}nm(合理范围 300-3000nm)",
"warning",
)
return True

View File

@ -649,65 +649,41 @@ class PipelineExecutor(QObject):
if max_band <= 0:
return True
# 波长驱动改造后,波段号由 find_band_number() 从 HDR 元数据中
# 动态解析,不再需要波段索引越界检查。仅对极端波长值做合理性提醒。
method = step3_cfg.get('method', 'goodman')
if method == 'goodman':
band_fields = [
('nir_lower', 'nir_lower', 65, 'NIR下波段'),
('nir_upper', 'nir_upper', 91, 'NIR上波段'),
]
elif method == 'kutser':
band_fields = [
('oxy_band', 'oxy_band', 38, '氧吸收波段'),
('lower_oxy', 'lower_oxy', 36, '下氧吸收波段'),
('upper_oxy', 'upper_oxy', 49, '上氧吸收波段'),
('nir_band', 'nir_band', 47, 'NIR波段'),
]
elif method == 'hedley':
band_fields = [
('hedley_nir_band', 'hedley_nir_band', 47, 'NIR波段'),
]
else:
_wavelength_fields = {
'goodman': [
('nir_lower_wavelength', 'nir_lower_wavelength', 641.93, 'NIR下界波长(nm)'),
('nir_upper_wavelength', 'nir_upper_wavelength', 751.49, 'NIR上界波长(nm)'),
],
'kutser': [
('oxy_wavelength', 'oxy_wavelength', 760.6, '氧吸收波长(nm)'),
('lower_wavelength', 'lower_wavelength', 742.39, '下氧吸收波长(nm)'),
('upper_wavelength', 'upper_wavelength', 860.48, '上氧吸收波长(nm)'),
('nir_wavelength', 'nir_wavelength', 842.36, 'NIR波长(nm)'),
],
'hedley': [
('hedley_nir_wavelength', 'hedley_nir_wavelength', 842.36, 'NIR波长(nm)'),
],
}.get(method, [])
# SUGAR 等方法无波长参数,跳过
if not _wavelength_fields:
return True
for cfg_key, panel_attr, recommended, label in band_fields:
for cfg_key, panel_attr, recommended, label in _wavelength_fields:
requested = step3_cfg.get(cfg_key)
if requested is None or requested <= max_band:
if requested is None:
continue
global_event_bus.publish('LogMessage', {
'message': f'⚠ step3 波段越界:{label}={requested} > 影像波段数 {max_band}',
'level': 'warning',
})
dlg = BandConfirmDialog(
self.parent(),
requested_band=requested,
max_band=max_band,
recommended_band=recommended,
method_label=label,
)
result = dlg.exec_()
if result == QDialog.Rejected:
# 波长驱动:仅对异常波长做软警告(find_band_number 会自动处理)
if requested < 300 or requested > 3000:
global_event_bus.publish('LogMessage', {
'message': '✗ 用户取消运行(step3 波段越界未解决)',
'message': f'⚠ step3 波长异常:{label}={requested}nm(合理范围 300-3000nm)',
'level': 'warning',
})
return False
new_band = dlg.selected_band()
try:
spin = getattr(step3_panel, panel_attr)
spin.setValue(new_band)
except AttributeError:
global_event_bus.publish('LogMessage', {
'message': f'⚠ step3 panel 缺控件 {panel_attr},跳过回写',
'level': 'warning',
})
continue
global_event_bus.publish('LogMessage', {
'message': f'✓ {label}:{requested} → {new_band}(影像最多 {max_band} 波段)',
'level': 'info',
})
continue
return True
return True

View File

@ -77,8 +77,8 @@ class Step11MapBatchThread(QThread):
return
boundary_shp = self.step10_kwargs.get('boundary_shp_path')
input_crs = self.step10_kwargs.get('input_crs', 'EPSG:32651')
output_crs = self.step10_kwargs.get('output_crs', input_crs)
input_crs = self.step10_kwargs.get('input_crs') or None
output_crs = self.step10_kwargs.get('output_crs') or None
resolution = float(self.step10_kwargs.get('resolution', 30))
# ── ★ 2026-07-01:QThread 内预计算共享空间上下文 ──
@ -364,12 +364,12 @@ class Step11MapPanel(QWidget):
# ---------- 统一组件:输入坐标系 ----------
self.input_crs = QLineEdit()
self.input_crs.setText("EPSG:32651")
self.input_crs.setPlaceholderText("留空=从掩膜/栅格自动探测")
params_layout.addLayout(create_standard_row("输入坐标系:", self.input_crs))
# ---------- 统一组件:输出坐标系 ----------
self.output_crs = QLineEdit()
self.output_crs.setText("EPSG:32651")
self.output_crs.setPlaceholderText("留空=与输入坐标系一致")
params_layout.addLayout(create_standard_row("输出坐标系:", self.output_crs))
# ---------- 复选框 ----------
@ -564,9 +564,8 @@ class Step11MapPanel(QWidget):
self.resolution.setValue(config['resolution'])
if 'input_crs' in config:
self.input_crs.setText(config['input_crs'])
# ★★★ 反灌入时强制 output_crs = input_crs,避免旧 config 中的 EPSG:4326 回填 ★★★
if 'output_crs' in config or 'input_crs' in config:
self.output_crs.setText(config.get('input_crs') or config.get('output_crs') or 'EPSG:32651')
self.output_crs.setText(config.get('input_crs') or config.get('output_crs') or '')
if 'show_sample_points' in config:
self.show_points.setChecked(config['show_sample_points'])
if 'use_distance_diffusion' in config:

View File

@ -12,7 +12,7 @@ from src.gui.panels._step_path_resolver import resolve_subdir, scan_work_dir_for
# ── 【修复点】:在这里加上了 QFrame 的导入 ──
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QFormLayout,
QSpinBox, QComboBox, QCheckBox, QPushButton,
QSpinBox, QDoubleSpinBox, QComboBox, QCheckBox, QPushButton,
QLabel, QLineEdit, QMessageBox, QSizePolicy, QFrame
)
from PyQt5.QtCore import Qt
@ -97,17 +97,21 @@ class Step3Panel(QWidget):
goodman_layout.setContentsMargins(0, 0, 0, 0)
goodman_layout.setSpacing(16)
self.nir_lower = QSpinBox()
self.nir_lower.setRange(0, 200)
self.nir_lower.setValue(65)
self.nir_lower.setButtonSymbols(QSpinBox.NoButtons)
self._add_aligned_row(goodman_layout, "NIR下波段索引:", self.nir_lower, "")
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")
self.nir_lower_wavelength.setButtonSymbols(QDoubleSpinBox.NoButtons)
self._add_aligned_row(goodman_layout, "NIR下界波长:", self.nir_lower_wavelength, "~640nm")
self.nir_upper = QSpinBox()
self.nir_upper.setRange(0, 200)
self.nir_upper.setValue(91)
self.nir_upper.setButtonSymbols(QSpinBox.NoButtons)
self._add_aligned_row(goodman_layout, "NIR上波段索引:", self.nir_upper, "")
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")
self.nir_upper_wavelength.setButtonSymbols(QDoubleSpinBox.NoButtons)
self._add_aligned_row(goodman_layout, "NIR上界波长:", self.nir_upper_wavelength, "~750nm")
self.goodman_a = QLineEdit("0.000019")
self.goodman_a.setValidator(QDoubleValidator(0.0, 1.0, 6, self))
@ -124,29 +128,37 @@ class Step3Panel(QWidget):
kutser_layout.setContentsMargins(0, 0, 0, 0)
kutser_layout.setSpacing(16)
self.oxy_band = QSpinBox()
self.oxy_band.setRange(0, 200)
self.oxy_band.setValue(38)
self.oxy_band.setButtonSymbols(QSpinBox.NoButtons)
self._add_aligned_row(kutser_layout, "氧吸收波段索引:", self.oxy_band, "")
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")
self.oxy_wavelength.setButtonSymbols(QDoubleSpinBox.NoButtons)
self._add_aligned_row(kutser_layout, "氧吸收波长:", self.oxy_wavelength, "O₂-A带 760nm")
self.lower_oxy = QSpinBox()
self.lower_oxy.setRange(0, 200)
self.lower_oxy.setValue(36)
self.lower_oxy.setButtonSymbols(QSpinBox.NoButtons)
self._add_aligned_row(kutser_layout, "下氧吸收波段索引:", self.lower_oxy, "")
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")
self.lower_wavelength.setButtonSymbols(QDoubleSpinBox.NoButtons)
self._add_aligned_row(kutser_layout, "左肩波长:", self.lower_wavelength, "~742nm")
self.upper_oxy = QSpinBox()
self.upper_oxy.setRange(0, 200)
self.upper_oxy.setValue(49)
self.upper_oxy.setButtonSymbols(QSpinBox.NoButtons)
self._add_aligned_row(kutser_layout, "上氧吸收波段索引:", self.upper_oxy, "")
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")
self.upper_wavelength.setButtonSymbols(QDoubleSpinBox.NoButtons)
self._add_aligned_row(kutser_layout, "右肩波长:", self.upper_wavelength, "~860nm")
self.nir_band = QSpinBox()
self.nir_band.setRange(0, 200)
self.nir_band.setValue(47)
self.nir_band.setButtonSymbols(QSpinBox.NoButtons)
self._add_aligned_row(kutser_layout, "NIR波段索引:", self.nir_band, "")
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")
self.nir_wavelength.setButtonSymbols(QDoubleSpinBox.NoButtons)
self._add_aligned_row(kutser_layout, "NIR参考波长:", self.nir_wavelength, "~842nm")
self.kutser_widget.setVisible(False)
params_layout.addWidget(self.kutser_widget)
@ -157,11 +169,13 @@ class Step3Panel(QWidget):
hedley_layout.setContentsMargins(0, 0, 0, 0)
hedley_layout.setSpacing(16)
self.hedley_nir_band = QSpinBox()
self.hedley_nir_band.setRange(0, 200)
self.hedley_nir_band.setValue(47)
self.hedley_nir_band.setButtonSymbols(QSpinBox.NoButtons)
self._add_aligned_row(hedley_layout, "NIR波段索引:", self.hedley_nir_band, "")
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")
self.hedley_nir_wavelength.setButtonSymbols(QDoubleSpinBox.NoButtons)
self._add_aligned_row(hedley_layout, "NIR参考波长:", self.hedley_nir_wavelength, "~842nm")
self.hedley_widget.setVisible(False)
params_layout.addWidget(self.hedley_widget)
@ -319,6 +333,9 @@ class Step3Panel(QWidget):
dialog.exec_()
def _update_band_ranges(self, file_path):
"""波长驱动改造后,波段号由 find_band_number() 动态解析,
GUI 输入的是波长(nm)而非波段索引,不再需要根据 RasterCount 限制范围。
保留此方法仅用于加载影像时输出波段信息日志。"""
from osgeo import gdal
if not file_path or not os.path.isfile(file_path):
return
@ -327,12 +344,8 @@ class Step3Panel(QWidget):
if dataset is None:
return
raster_count = dataset.RasterCount
max_band = max(0, raster_count - 1)
self.nir_lower.setMaximum(max_band)
self.nir_upper.setMaximum(max_band)
self.oxy_band.setMaximum(max_band)
self.nir_band.setMaximum(max_band)
self.hedley_nir_band.setMaximum(max_band)
print(f"[Step3Panel] 影像已加载: {raster_count} 波段, "
f"尺寸 {dataset.RasterXSize}x{dataset.RasterYSize}")
dataset = None
except Exception:
pass
@ -401,19 +414,19 @@ class Step3Panel(QWidget):
method = self.method.currentData()
if method == 'goodman':
config['nir_lower'] = self.nir_lower.value()
config['nir_upper'] = self.nir_upper.value()
config['nir_lower_wavelength'] = self.nir_lower_wavelength.value()
config['nir_upper_wavelength'] = self.nir_upper_wavelength.value()
config['goodman_A'] = self._safe_float(self.goodman_a, 0.000019)
config['goodman_B'] = self._safe_float(self.goodman_b, 0.1)
elif method == 'kutser':
config['oxy_band'] = self.oxy_band.value()
config['lower_oxy'] = self.lower_oxy.value()
config['upper_oxy'] = self.upper_oxy.value()
config['nir_band'] = self.nir_band.value()
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_band'] = self.hedley_nir_band.value()
config['hedley_nir_wavelength'] = self.hedley_nir_wavelength.value()
elif method == 'sugar':
config['sugar_iter'] = self.sugar_iter.value() if self.sugar_iter.value() > 0 else None
@ -447,26 +460,26 @@ class Step3Panel(QWidget):
if idx >= 0:
self.interp_method.setCurrentIndex(idx)
if 'nir_lower' in config:
self.nir_lower.setValue(config['nir_lower'])
if 'nir_upper' in config:
self.nir_upper.setValue(config['nir_upper'])
if 'nir_lower_wavelength' in config:
self.nir_lower_wavelength.setValue(config['nir_lower_wavelength'])
if 'nir_upper_wavelength' in config:
self.nir_upper_wavelength.setValue(config['nir_upper_wavelength'])
if 'goodman_A' in config:
self.goodman_a.setText(f"{config['goodman_A']:.6f}")
if 'goodman_B' in config:
self.goodman_b.setText(f"{config['goodman_B']:.2f}")
if 'oxy_band' in config:
self.oxy_band.setValue(config['oxy_band'])
if 'lower_oxy' in config:
self.lower_oxy.setValue(config['lower_oxy'])
if 'upper_oxy' in config:
self.upper_oxy.setValue(config['upper_oxy'])
if 'nir_band' in config:
self.nir_band.setValue(config['nir_band'])
if 'oxy_wavelength' in config:
self.oxy_wavelength.setValue(config['oxy_wavelength'])
if 'lower_wavelength' in config:
self.lower_wavelength.setValue(config['lower_wavelength'])
if 'upper_wavelength' in config:
self.upper_wavelength.setValue(config['upper_wavelength'])
if 'nir_wavelength' in config:
self.nir_wavelength.setValue(config['nir_wavelength'])
if 'hedley_nir_band' in config:
self.hedley_nir_band.setValue(config['hedley_nir_band'])
if 'hedley_nir_wavelength' in config:
self.hedley_nir_wavelength.setValue(config['hedley_nir_wavelength'])
if 'sugar_iter' in config:
self.sugar_iter.setValue(config['sugar_iter'] if config['sugar_iter'] is not None else 0)