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

@ -24,9 +24,9 @@ Step11 后端计算服务(专题图生成 / 克里金插值)
"geotiff_dir": "D:/10_WaterIndex_Images", # 批量 GeoTIFF
"boundary_shp_path": "D:/boundary.shp", # 边界 shp(可选)
"resolution": 30.0, # 空间分辨率(米)
"input_crs": "EPSG:32651",
# ★★★ 强制默认 output_crs = input_crs,禁止从 service 配置误改为 EPSG:4326 ★★★
"output_crs": "EPSG:32651",
# CRS 默认留空,由 ContentMapper 从边界/栅格自动探测
"input_crs": None,
"output_crs": None,
"output_dir": "D:/11_Thematic_Map", # 输出目录
"enabled": True,
"work_dir": "D:/workspace", # 工作目录
@ -188,6 +188,13 @@ def _run_geotiff_mode(geotiff_paths: List[Path],
from src.postprocessing.map import ContentMapper
mapper = ContentMapper()
# ★ CRS 动态探测:从第一个 GeoTIFF 或边界文件自动获取
if geotiff_paths:
mapper._ensure_crs(reference_file=str(geotiff_paths[0]))
elif boundary_shp_path:
mapper._ensure_crs(reference_file=boundary_shp_path)
else:
mapper._ensure_crs()
output_dir.mkdir(parents=True, exist_ok=True)
n_ok = 0
for tif_path in geotiff_paths:

View File

@ -230,6 +230,8 @@ def _try_distribution_maps(work_dir: str, output_dir: Path) -> Dict[str, Any]:
from src.postprocessing.map import ContentMapper
mapper = ContentMapper()
# ★ CRS 动态探测:从第一个 GeoTIFF 自动获取
mapper._ensure_crs(reference_file=str(tif_paths[0]))
for tif_path in tif_paths:
stem = tif_path.stem
chinese_title = mapper._get_chinese_title(stem)

View File

@ -19,10 +19,12 @@ Step3 后端计算服务(耀斑去除)
"interpolation_method": "bilinear",
"water_mask_path": "D:/mask.dat", # 水域掩膜(可选)
"output_path": "D:/deglint_image.bsq",
# 方法专属参数(按 method 任选一组)
"nir_lower": 65, "nir_upper": 91, "goodman_A": 1.9e-5, "goodman_B": 0.1, # goodman
"oxy_band": 38, "lower_oxy": 36, "upper_oxy": 49, "nir_band": 47, # kutser
"hedley_nir_band": 47, # hedley
# 方法专属参数(波长驱动,nm → 自动解析波段号)
"nir_lower_wavelength": 641.93, "nir_upper_wavelength": 751.49,
"goodman_A": 1.9e-5, "goodman_B": 0.1, # goodman
"oxy_wavelength": 760.6, "lower_wavelength": 742.39, # kutser
"upper_wavelength": 860.48, "nir_wavelength": 842.36, # kutser
"hedley_nir_wavelength": 842.36, # hedley
"sugar_iter": 3, "sugar_sigma": 1.0, "sugar_estimate_background": True,
"sugar_glint_mask_method": "cdf", "sugar_termination_thresh": 20.0,
"sugar_bounds": [(1, 2)], # sugar
@ -79,24 +81,24 @@ def _normalize_method(method: str) -> str:
def _build_method_kwargs(method: str, config: Dict[str, Any]) -> Dict[str, Any]:
"""按 method 从 config 中抽取对应的方法专属参数"""
"""按 method 从 config 中抽取对应的方法专属参数(波长驱动)"""
if method == "goodman":
return {
"nir_lower": int(config.get("nir_lower", 65)),
"nir_upper": int(config.get("nir_upper", 91)),
"nir_lower_wavelength": float(config.get("nir_lower_wavelength", 641.93)),
"nir_upper_wavelength": float(config.get("nir_upper_wavelength", 751.49)),
"goodman_A": float(config.get("goodman_A", 0.000019)),
"goodman_B": float(config.get("goodman_B", 0.1)),
}
if method == "kutser":
return {
"oxy_band": int(config.get("oxy_band", 38)),
"lower_oxy": int(config.get("lower_oxy", 36)),
"upper_oxy": int(config.get("upper_oxy", 49)),
"nir_band": int(config.get("nir_band", 47)),
"oxy_wavelength": float(config.get("oxy_wavelength", 760.6)),
"lower_wavelength": float(config.get("lower_wavelength", 742.39)),
"upper_wavelength": float(config.get("upper_wavelength", 860.48)),
"nir_wavelength": float(config.get("nir_wavelength", 842.36)),
}
if method == "hedley":
return {
"hedley_nir_band": int(config.get("hedley_nir_band", 47)),
"hedley_nir_wavelength": float(config.get("hedley_nir_wavelength", 842.36)),
}
if method == "sugar":
bounds = config.get("sugar_bounds")

View File

@ -177,14 +177,11 @@ class Step11View(BaseView):
params_layout.addRow("分辨率(米):", self.resolution)
self.input_crs = QLineEdit()
self.input_crs.setText("EPSG:32651")
self.input_crs.setPlaceholderText("留空=从掩膜/栅格自动探测")
params_layout.addRow("输入坐标系:", self.input_crs)
self.output_crs = QLineEdit()
# ★★★ 强制默认输出坐标系与输入一致,禁止从 GUI 误改为 EPSG:4326 ★★★
# 历史默认值 'EPSG:4326' 会让 ContentMapper 把栅格重投影到经纬度,
# 与基于 EPSG:32651 的水域掩膜叠加时发生仿射变换撕裂(栅格错位、坐标轴扭曲)。
self.output_crs.setText("EPSG:32651")
self.output_crs.setPlaceholderText("留空=与输入坐标系一致")
params_layout.addRow("输出坐标系:", self.output_crs)
self.show_points = QCheckBox("显示采样点")
@ -346,9 +343,8 @@ class Step11View(BaseView):
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

@ -64,14 +64,18 @@ class Step3View(BaseView):
# Goodman 参数组
self.goodman_group = QGroupBox("Goodman方法参数")
goodman_layout = QFormLayout()
self.nir_lower = QSpinBox()
self.nir_lower.setRange(0, 200)
self.nir_lower.setValue(65)
goodman_layout.addRow("NIR下波段索引:", self.nir_lower)
self.nir_upper = QSpinBox()
self.nir_upper.setRange(0, 200)
self.nir_upper.setValue(91)
goodman_layout.addRow("NIR上波段索引:", self.nir_upper)
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)
@ -88,22 +92,30 @@ class Step3View(BaseView):
# Kutser 参数组
self.kutser_group = QGroupBox("Kutser方法参数")
kutser_layout = QFormLayout()
self.oxy_band = QSpinBox()
self.oxy_band.setRange(0, 200)
self.oxy_band.setValue(38)
kutser_layout.addRow("氧吸收波段索引:", self.oxy_band)
self.lower_oxy = QSpinBox()
self.lower_oxy.setRange(0, 200)
self.lower_oxy.setValue(36)
kutser_layout.addRow("下氧吸收波段索引:", self.lower_oxy)
self.upper_oxy = QSpinBox()
self.upper_oxy.setRange(0, 200)
self.upper_oxy.setValue(49)
kutser_layout.addRow("上氧吸收波段索引:", self.upper_oxy)
self.nir_band = QSpinBox()
self.nir_band.setRange(0, 200)
self.nir_band.setValue(47)
kutser_layout.addRow("NIR波段索引:", self.nir_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")
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)
@ -111,10 +123,12 @@ class Step3View(BaseView):
# Hedley 参数组
self.hedley_group = QGroupBox("Hedley方法参数")
hedley_layout = QFormLayout()
self.hedley_nir_band = QSpinBox()
self.hedley_nir_band.setRange(0, 200)
self.hedley_nir_band.setValue(47)
hedley_layout.addRow("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")
hedley_layout.addRow("NIR参考波长:", self.hedley_nir_wavelength)
self.hedley_group.setLayout(hedley_layout)
self.hedley_group.setVisible(False)
layout.addWidget(self.hedley_group)
@ -210,17 +224,17 @@ class Step3View(BaseView):
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.goodman_a.value()
config["goodman_B"] = self.goodman_b.value()
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()
config["sugar_sigma"] = self.sugar_sigma.value()