From 99aeab3076e20d0a5986cc1ced64787f2307b897 Mon Sep 17 00:00:00 2001 From: duxin Date: Mon, 27 Jul 2026 14:52:23 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=B8=AD=E9=97=B4=E5=B1=82?= =?UTF-8?q?=E6=B6=88=E9=99=A4=E7=A1=AC=E7=BC=96=E7=A0=81=E6=B3=A2=E6=AE=B5?= =?UTF-8?q?=E5=8F=B7+CRS=E5=8A=A8=E6=80=81=E6=8E=A2=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 等所有魔法数字 --- src/core/handlers/step11_map_handler.py | 20 ++-- src/core/handlers/step12_kriging.py | 7 +- src/core/handlers/step3_glint_removal.py | 18 +-- src/core/steps/glint_removal_step.py | 44 +++---- src/core/steps/mapping_step.py | 8 +- src/gui/_legacy/water_quality_gui.py | 78 +++++-------- src/gui/core/pipeline_executor.py | 78 +++++-------- src/gui/panels/step11_map_panel.py | 11 +- src/gui/panels/step3_panel.py | 139 +++++++++++++---------- src/new/services/step11_service.py | 13 ++- src/new/services/step12_service.py | 2 + src/new/services/step3_service.py | 26 +++-- src/new/views/step11_view.py | 10 +- src/new/views/step3_view.py | 84 ++++++++------ 14 files changed, 263 insertions(+), 275 deletions(-) diff --git a/src/core/handlers/step11_map_handler.py b/src/core/handlers/step11_map_handler.py index e467b13..d3a43c6 100644 --- a/src/core/handlers/step11_map_handler.py +++ b/src/core/handlers/step11_map_handler.py @@ -40,10 +40,15 @@ def _process_one_map(csv_path: str, base_kwargs: dict, output_dir: str if shared_context is not None: # ★ 快速通道:直接调 ContentMapper,复用预计算的网格/掩膜 from src.postprocessing.map import ContentMapper - mapper = ContentMapper( - input_crs=base_kwargs.get('input_crs', 'EPSG:32651'), - output_crs=base_kwargs.get('output_crs', base_kwargs.get('input_crs', 'EPSG:32651')), - ) + # ★ CRS 动态探测:从边界文件自动获取,不再硬编码 EPSG:32651 + input_crs = base_kwargs.get('input_crs') or None + output_crs = base_kwargs.get('output_crs') or None + boundary_path = base_kwargs.get('boundary_shp_path') + mapper = ContentMapper(input_crs=input_crs, output_crs=output_crs) + if boundary_path: + mapper._ensure_crs(reference_file=str(boundary_path)) + else: + mapper._ensure_crs() result_path = mapper.process_data( csv_file=csv_path, shp_file=base_kwargs.get('boundary_shp_path'), @@ -103,7 +108,8 @@ def _pre_vectorize_boundary(boundary_path: str, work_dir: str) -> str: if proj: srs.ImportFromWkt(proj) else: - srs.ImportFromEPSG(32651) + # 栅格无投影信息时,兜底 WGS84(而非某个特定 UTM 分区) + srs.ImportFromEPSG(4326) layer = out_ds.CreateLayer('boundary', srs, ogr.wkbMultiPolygon) gdal.Polygonize(band, band, layer, 0, [], callback=None) @@ -163,8 +169,8 @@ class Step11MapHandler(BaseStepHandler): base_kwargs = { 'boundary_shp_path': resolved_boundary or None, 'resolution': float(config.get('resolution', 10.0)), - 'input_crs': config.get('input_crs', 'EPSG:32651'), - 'output_crs': config.get('output_crs', config.get('input_crs', 'EPSG:32651')), + 'input_crs': config.get('input_crs') or None, + 'output_crs': config.get('output_crs') or None, 'show_sample_points': bool(config.get('show_sample_points', False)), 'use_distance_diffusion': bool(config.get('use_distance_diffusion', False)), } diff --git a/src/core/handlers/step12_kriging.py b/src/core/handlers/step12_kriging.py index f136080..02d3946 100644 --- a/src/core/handlers/step12_kriging.py +++ b/src/core/handlers/step12_kriging.py @@ -71,11 +71,8 @@ class Step12KrigingHandler(BaseStepHandler): boundary_shp_path=boundary_shp_path, output_image_path=output_image_path, resolution=config.get('resolution', 30), - input_crs=config.get('input_crs', 'EPSG:32651'), - # ★★★ 强制 output_crs = input_crs,避免 ContentMapper 把栅格重投影到 EPSG:4326 ★★★ - # 旧实现:output_crs=config.get('output_crs', 'EPSG:4326') - # 重投影会让栅格和基于投影坐标的掩膜在 visualize_raster 叠加时发生仿射变换撕裂 - output_crs=config.get('input_crs', 'EPSG:32651'), + input_crs=config.get('input_crs') or None, + output_crs=config.get('output_crs') or None, show_sample_points=config.get('show_sample_points', False), base_map_tif=config.get('base_map_tif'), use_distance_diffusion=config.get('use_distance_diffusion', True), diff --git a/src/core/handlers/step3_glint_removal.py b/src/core/handlers/step3_glint_removal.py index b8daaae..b9018ff 100644 --- a/src/core/handlers/step3_glint_removal.py +++ b/src/core/handlers/step3_glint_removal.py @@ -45,16 +45,16 @@ class Step3GlintRemovalHandler(BaseStepHandler): interpolation_method=config.get('interpolation_method', 'nearest'), enabled=config.get('enabled', True), kutser_shp_path=config.get('kutser_shp_path'), - oxy_band=config.get('oxy_band', 38), - lower_oxy=config.get('lower_oxy', 36), - upper_oxy=config.get('upper_oxy', 49), - nir_band=config.get('nir_band', 47), - nir_lower=config.get('nir_lower', 25), - nir_upper=config.get('nir_upper', 37), - goodman_A=config.get('goodman_A', 0.000019), - goodman_B=config.get('goodman_B', 0.1), + 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)), + 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)), hedley_shp_path=config.get('hedley_shp_path'), - hedley_nir_band=config.get('hedley_nir_band', 47), + hedley_nir_wavelength=float(config.get('hedley_nir_wavelength', 842.36)), sugar_bounds=config.get('sugar_bounds'), sugar_sigma=config.get('sugar_sigma', 1.0), sugar_estimate_background=config.get('sugar_estimate_background', True), diff --git a/src/core/steps/glint_removal_step.py b/src/core/steps/glint_removal_step.py index 6aa2de6..25c67e1 100644 --- a/src/core/steps/glint_removal_step.py +++ b/src/core/steps/glint_removal_step.py @@ -209,20 +209,20 @@ class GlintRemovalStep: interpolate_zeros: bool = False, interpolation_method: str = "nearest", enabled: bool = True, - # Kutser 参数 + # Kutser 参数(波长驱动:nm → 自动解析波段号) kutser_shp_path: Optional[str] = None, - oxy_band: int = 38, - lower_oxy: int = 36, - upper_oxy: int = 49, - nir_band: int = 47, - # Goodman 参数 - nir_lower: int = 25, - nir_upper: int = 37, + oxy_wavelength: float = 760.6, + lower_wavelength: float = 742.39, + upper_wavelength: float = 860.48, + nir_wavelength: float = 842.36, + # Goodman 参数(波长驱动:统一为 ~640nm / ~750nm 物理波长) + nir_lower_wavelength: float = 641.93, + nir_upper_wavelength: float = 751.49, goodman_A: float = 0.000019, goodman_B: float = 0.1, - # Hedley 参数 + # Hedley 参数(波长驱动) hedley_shp_path: Optional[str] = None, - hedley_nir_band: int = 47, + hedley_nir_wavelength: float = 842.36, # SUGAR 参数 sugar_bounds: Optional[List[tuple]] = None, sugar_sigma: float = 1.0, @@ -383,10 +383,10 @@ class GlintRemovalStep: # ==================== Kutser ==================== if method == "kutser": - print(f"使用方法: Kutser (氧吸收波段={oxy_band}, NIR波段={nir_band})") + print(f"使用方法: Kutser (波长驱动: oxy={oxy_wavelength}nm, " + f"NIR={nir_wavelength}nm)") hardcoded_bsq = str(deglint_dir / "deglint_kutser.bsq") hardcoded_hdr = hardcoded_bsq.replace(".bsq", ".hdr") - # 将用户指定的 output_path 标准化为 .bsq 路径 if output_path: final_bsq = output_path.replace('.dat', '.bsq').replace('.tif', '.bsq') final_hdr = final_bsq.replace(".bsq", ".hdr") @@ -402,10 +402,10 @@ class GlintRemovalStep: kutser = Kutser( img_path, shp_path=None, - oxy_band=oxy_band, - lower_oxy=lower_oxy, - upper_oxy=upper_oxy, - NIR_band=nir_band, + oxy_wavelength=oxy_wavelength, + lower_wavelength=lower_wavelength, + upper_wavelength=upper_wavelength, + nir_wavelength=nir_wavelength, water_mask=mask_for_algorithm, output_path=hardcoded_bsq, ) @@ -420,7 +420,9 @@ class GlintRemovalStep: # ==================== Goodman ==================== elif method == "goodman": - print(f"使用方法: Goodman (NIR波段范围: {nir_lower}-{nir_upper})") + print(f"使用方法: Goodman (波长驱动: " + f"NIR_lower={nir_lower_wavelength}nm, " + f"NIR_upper={nir_upper_wavelength}nm)") hardcoded_bsq = str(deglint_dir / "deglint_goodman.bsq") hardcoded_hdr = hardcoded_bsq.replace(".bsq", ".hdr") if output_path: @@ -437,8 +439,8 @@ class GlintRemovalStep: goodman = Goodman( img_path, - NIR_lower=nir_lower, - NIR_upper=nir_upper, + nir_lower_wavelength=nir_lower_wavelength, + nir_upper_wavelength=nir_upper_wavelength, A=goodman_A, B=goodman_B, water_mask=mask_for_algorithm, @@ -458,7 +460,7 @@ class GlintRemovalStep: # ==================== Hedley ==================== elif method == "hedley": - print(f"使用方法: Hedley (NIR波段={hedley_nir_band})") + print(f"使用方法: Hedley (波长驱动: NIR={hedley_nir_wavelength}nm)") hardcoded_bsq = str(deglint_dir / "deglint_hedley.bsq") hardcoded_hdr = hardcoded_bsq.replace(".bsq", ".hdr") if output_path: @@ -476,7 +478,7 @@ class GlintRemovalStep: hedley = Hedley( img_path, shp_path=None, - NIR_band=hedley_nir_band, + nir_wavelength=hedley_nir_wavelength, water_mask=mask_for_algorithm, output_path=hardcoded_bsq, ) diff --git a/src/core/steps/mapping_step.py b/src/core/steps/mapping_step.py index e48998a..2a2a731 100644 --- a/src/core/steps/mapping_step.py +++ b/src/core/steps/mapping_step.py @@ -19,11 +19,9 @@ class MappingStep: boundary_shp_path: Optional[str] = None, # ★★★ Plan C: None = 不依赖水域掩膜 ★★★ output_image_path: Optional[str] = None, resolution: float = 30, - input_crs: str = "EPSG:32651", - # ★★★ 强制默认 output_crs = input_crs,禁止重投影到 EPSG:4326 ★★★ - # 历史默认值 'EPSG:4326' 会让 ContentMapper 将插值栅格从投影坐标系转到经纬度坐标系, - # 与基于 EPSG:32651 的水域掩膜叠加时发生仿射变换撕裂(栅格错位、坐标轴扭曲)。 - output_crs: str = "EPSG:32651", + input_crs: Optional[str] = None, + # ★ CRS 动态探测:ContentMapper 自动从掩膜/栅格中读取投影信息 + output_crs: Optional[str] = None, show_sample_points: bool = False, base_map_tif: Optional[str] = None, use_distance_diffusion: bool = True, diff --git a/src/gui/_legacy/water_quality_gui.py b/src/gui/_legacy/water_quality_gui.py index a9f5ab7..1acb2c4 100644 --- a/src/gui/_legacy/water_quality_gui.py +++ b/src/gui/_legacy/water_quality_gui.py @@ -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 diff --git a/src/gui/core/pipeline_executor.py b/src/gui/core/pipeline_executor.py index 2b393b1..e42ddc5 100644 --- a/src/gui/core/pipeline_executor.py +++ b/src/gui/core/pipeline_executor.py @@ -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 + diff --git a/src/gui/panels/step11_map_panel.py b/src/gui/panels/step11_map_panel.py index 9274705..88aac1d 100644 --- a/src/gui/panels/step11_map_panel.py +++ b/src/gui/panels/step11_map_panel.py @@ -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: diff --git a/src/gui/panels/step3_panel.py b/src/gui/panels/step3_panel.py index d8920a7..1d62b13 100644 --- a/src/gui/panels/step3_panel.py +++ b/src/gui/panels/step3_panel.py @@ -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) diff --git a/src/new/services/step11_service.py b/src/new/services/step11_service.py index e906033..0e6e5b0 100644 --- a/src/new/services/step11_service.py +++ b/src/new/services/step11_service.py @@ -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: diff --git a/src/new/services/step12_service.py b/src/new/services/step12_service.py index 98ed3a8..ad31ebc 100644 --- a/src/new/services/step12_service.py +++ b/src/new/services/step12_service.py @@ -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) diff --git a/src/new/services/step3_service.py b/src/new/services/step3_service.py index 729038b..16f697a 100644 --- a/src/new/services/step3_service.py +++ b/src/new/services/step3_service.py @@ -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") diff --git a/src/new/views/step11_view.py b/src/new/views/step11_view.py index b035fcc..b6fe29c 100644 --- a/src/new/views/step11_view.py +++ b/src/new/views/step11_view.py @@ -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: diff --git a/src/new/views/step3_view.py b/src/new/views/step3_view.py index c642b2a..37017a1 100644 --- a/src/new/views/step3_view.py +++ b/src/new/views/step3_view.py @@ -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()