refactor(step4): 剥离 Steps 层 - step1~step3 业务逻辑下沉到独立模块
This commit is contained in:
12
src/core/steps/__init__.py
Normal file
12
src/core/steps/__init__.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""业务步骤层模块"""
|
||||||
|
|
||||||
|
from src.core.steps.water_mask_step import WaterMaskStep
|
||||||
|
from src.core.steps.glint_detection_step import GlintDetectionStep
|
||||||
|
from src.core.steps.glint_removal_step import GlintRemovalStep
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"WaterMaskStep",
|
||||||
|
"GlintDetectionStep",
|
||||||
|
"GlintRemovalStep",
|
||||||
|
]
|
||||||
113
src/core/steps/glint_detection_step.py
Normal file
113
src/core/steps/glint_detection_step.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
步骤2: 耀斑区域检测
|
||||||
|
|
||||||
|
支持多种检测方法: otsu, zscore, percentile, iqr, adaptive, multi_band
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, List, Union
|
||||||
|
|
||||||
|
|
||||||
|
class GlintDetectionStep:
|
||||||
|
"""耀斑区域检测步骤"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(
|
||||||
|
img_path: str,
|
||||||
|
glint_wave: float = 750.0,
|
||||||
|
method: str = "otsu",
|
||||||
|
z_threshold: float = 2.5,
|
||||||
|
percentile: float = 95.0,
|
||||||
|
iqr_multiplier: float = 1.5,
|
||||||
|
window_size: int = 15,
|
||||||
|
multi_band_waves: Optional[List[float]] = None,
|
||||||
|
sub_method: str = "zscore",
|
||||||
|
weights: Optional[List[float]] = None,
|
||||||
|
max_area: Optional[int] = None,
|
||||||
|
buffer_size: Optional[int] = None,
|
||||||
|
water_mask_path: Optional[str] = None,
|
||||||
|
glint_dir: Union[str, Path] = "./2_glint",
|
||||||
|
callback: Optional[callable] = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
执行耀斑区域检测
|
||||||
|
|
||||||
|
Args:
|
||||||
|
img_path: 输入影像文件路径
|
||||||
|
glint_wave: 用于耀斑检测的波段波长(nm)
|
||||||
|
method: 检测方法 ('otsu' | 'zscore' | 'percentile' | 'iqr' | 'adaptive' | 'multi_band')
|
||||||
|
z_threshold: Z-score 方法阈值(默认 2.5)
|
||||||
|
percentile: 百分位数阈值(默认 95.0)
|
||||||
|
iqr_multiplier: IQR 倍数(默认 1.5)
|
||||||
|
window_size: 自适应阈值窗口大小(默认 15)
|
||||||
|
multi_band_waves: 多波段方法的波长列表,如 [750, 800, 850]
|
||||||
|
sub_method: 多波段方法的子方法(默认 'zscore')
|
||||||
|
weights: 多波段方法的权重列表(None 表示等权重)
|
||||||
|
max_area: 最大连通域面积阈值(像素),超过则过滤
|
||||||
|
buffer_size: 岸边缓冲区大小(像素),用于去除岸边附近错误掩膜
|
||||||
|
water_mask_path: 水域掩膜文件路径(dat 格式优先)
|
||||||
|
glint_dir: 工作目录
|
||||||
|
callback: 回调函数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
耀斑掩膜文件路径 (.dat)
|
||||||
|
"""
|
||||||
|
from src.utils.find_severe_glint_area import find_severe_glint_area
|
||||||
|
|
||||||
|
glint_dir = Path(glint_dir)
|
||||||
|
glint_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def notify(status, msg=""):
|
||||||
|
if callback:
|
||||||
|
callback("步骤2", status, msg)
|
||||||
|
|
||||||
|
print("\n" + "=" * 80)
|
||||||
|
print("步骤2: 找到耀斑区域")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
step_start_time = time.time()
|
||||||
|
|
||||||
|
# 确定水体掩膜路径
|
||||||
|
if water_mask_path is not None and Path(water_mask_path).exists():
|
||||||
|
final_water_mask_path = water_mask_path
|
||||||
|
else:
|
||||||
|
final_water_mask_path = None
|
||||||
|
|
||||||
|
output_path = str(glint_dir / "severe_glint_area.dat")
|
||||||
|
|
||||||
|
# 跳过已存在的文件
|
||||||
|
if Path(output_path).exists():
|
||||||
|
print(f"检测到已存在的耀斑掩膜文件,直接使用: {output_path}")
|
||||||
|
notify("skipped", f"耀斑掩膜已设置: {output_path}")
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
# 构建检测参数字典
|
||||||
|
kwargs = {
|
||||||
|
"method": method,
|
||||||
|
"z_threshold": z_threshold,
|
||||||
|
"percentile": percentile,
|
||||||
|
"iqr_multiplier": iqr_multiplier,
|
||||||
|
"window_size": window_size,
|
||||||
|
}
|
||||||
|
if method == "multi_band":
|
||||||
|
if multi_band_waves is not None:
|
||||||
|
kwargs["multi_band_waves"] = multi_band_waves
|
||||||
|
if sub_method is not None:
|
||||||
|
kwargs["sub_method"] = sub_method
|
||||||
|
if weights is not None:
|
||||||
|
kwargs["weights"] = weights
|
||||||
|
if max_area is not None:
|
||||||
|
kwargs["max_area"] = max_area
|
||||||
|
if buffer_size is not None:
|
||||||
|
kwargs["buffer_size"] = buffer_size
|
||||||
|
|
||||||
|
glint_mask_path = find_severe_glint_area(
|
||||||
|
img_path, final_water_mask_path, glint_wave, output_path, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"耀斑掩膜已生成: {glint_mask_path}")
|
||||||
|
print(f"使用检测方法: {method}")
|
||||||
|
notify("completed", f"耀斑掩膜已生成: {glint_mask_path}")
|
||||||
|
return glint_mask_path
|
||||||
314
src/core/steps/glint_removal_step.py
Normal file
314
src/core/steps/glint_removal_step.py
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
步骤3: 去除耀斑
|
||||||
|
|
||||||
|
支持多种方法: subtract_nir, regression_slope, oxygen_absorption, kutser, goodman, hedley, sugar
|
||||||
|
|
||||||
|
每种方法都会:
|
||||||
|
1. 准备水域掩膜(支持 shp 自动转 dat)
|
||||||
|
2. 调用对应的算法类执行处理
|
||||||
|
3. 复制 hdr 文件到输出影像
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, List, Union, Callable
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
class GlintRemovalStep:
|
||||||
|
"""去除耀斑步骤"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(
|
||||||
|
img_path: str,
|
||||||
|
method: str = "subtract_nir",
|
||||||
|
start_wave: Optional[float] = None,
|
||||||
|
end_wave: Optional[float] = None,
|
||||||
|
json_path: Optional[str] = None,
|
||||||
|
left_shoulder_wave: Optional[float] = None,
|
||||||
|
valley_wave: Optional[float] = None,
|
||||||
|
right_shoulder_wave: Optional[float] = None,
|
||||||
|
water_mask: Optional[Union[str, np.ndarray]] = None,
|
||||||
|
interpolated_img_path: Optional[str] = None,
|
||||||
|
interpolate_zeros: bool = False,
|
||||||
|
interpolation_method: str = "nearest",
|
||||||
|
enabled: bool = True,
|
||||||
|
# Kutser 参数
|
||||||
|
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,
|
||||||
|
goodman_A: float = 0.000019,
|
||||||
|
goodman_B: float = 0.1,
|
||||||
|
# Hedley 参数
|
||||||
|
hedley_shp_path: Optional[str] = None,
|
||||||
|
hedley_nir_band: int = 47,
|
||||||
|
# SUGAR 参数
|
||||||
|
sugar_bounds: Optional[List[tuple]] = None,
|
||||||
|
sugar_sigma: float = 1.0,
|
||||||
|
sugar_estimate_background: bool = True,
|
||||||
|
sugar_glint_mask_method: str = "cdf",
|
||||||
|
sugar_iter: Optional[int] = 3,
|
||||||
|
sugar_termination_thresh: float = 20.0,
|
||||||
|
# 内部工具函数
|
||||||
|
_get_image_geo_info=None,
|
||||||
|
_load_image_as_array=None,
|
||||||
|
_save_bands_as_image=None,
|
||||||
|
_copy_hdr_info=None,
|
||||||
|
_prepare_water_mask_for_algorithm=None,
|
||||||
|
_interpolate_zero_pixels_batch=None,
|
||||||
|
deglint_dir: Union[str, Path] = "./3_deglint",
|
||||||
|
water_mask_dir: Union[str, Path] = "./1_water_mask",
|
||||||
|
callback: Optional[Callable] = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
执行去除耀斑处理
|
||||||
|
|
||||||
|
Args:
|
||||||
|
img_path: 输入影像文件路径
|
||||||
|
method: 去耀斑方法
|
||||||
|
...(其余参数同主类 step3_remove_glint)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
去除耀斑后的影像文件路径
|
||||||
|
"""
|
||||||
|
from src.core.glint_removal.Kutser import Kutser
|
||||||
|
from src.core.glint_removal.Goodman import Goodman
|
||||||
|
from src.core.glint_removal.Hedley import Hedley
|
||||||
|
from src.core.glint_removal.SUGAR import SUGAR, correction_iterative
|
||||||
|
from src.core.utils.gdal_helper import (
|
||||||
|
get_image_geo_info as _default_get_geo,
|
||||||
|
load_image_as_array as _default_load,
|
||||||
|
save_bands_as_image as _default_save_bands,
|
||||||
|
copy_hdr_info as _default_copy_hdr,
|
||||||
|
)
|
||||||
|
from src.core.utils.mask_converter import (
|
||||||
|
prepare_water_mask_for_algorithm as _default_prepare,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 使用提供的函数或默认函数
|
||||||
|
if _get_image_geo_info is None:
|
||||||
|
_get_image_geo_info = _default_get_geo
|
||||||
|
if _load_image_as_array is None:
|
||||||
|
_load_image_as_array = _default_load
|
||||||
|
if _save_bands_as_image is None:
|
||||||
|
_save_bands_as_image = _default_save_bands
|
||||||
|
if _copy_hdr_info is None:
|
||||||
|
_copy_hdr_info = _default_copy_hdr
|
||||||
|
if _prepare_water_mask_for_algorithm is None:
|
||||||
|
_prepare_water_mask_for_algorithm = _default_prepare
|
||||||
|
|
||||||
|
deglint_dir = Path(deglint_dir)
|
||||||
|
deglint_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def notify(status, msg=""):
|
||||||
|
if callback:
|
||||||
|
callback("步骤3", status, msg)
|
||||||
|
|
||||||
|
print("\n" + "=" * 80)
|
||||||
|
print("步骤3: 去除耀斑")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
step_start_time = time.time()
|
||||||
|
|
||||||
|
# 方法名标准化
|
||||||
|
raw_method = str(method).lower()
|
||||||
|
if "kutser" in raw_method:
|
||||||
|
method = "kutser"
|
||||||
|
elif "goodman" in raw_method:
|
||||||
|
method = "goodman"
|
||||||
|
elif "hedley" in raw_method:
|
||||||
|
method = "hedley"
|
||||||
|
elif "sugar" in raw_method:
|
||||||
|
method = "sugar"
|
||||||
|
|
||||||
|
# 如果未启用,直接返回原始影像
|
||||||
|
if not enabled:
|
||||||
|
print("已设置跳过去除耀斑(enabled=False),将直接使用原始影像。")
|
||||||
|
notify("skipped", "跳过去耀斑,使用原始影像")
|
||||||
|
return img_path
|
||||||
|
|
||||||
|
# ---- 确定水域掩膜 ----
|
||||||
|
final_water_mask = water_mask
|
||||||
|
if final_water_mask is not None and str(final_water_mask).lower().endswith(".shp"):
|
||||||
|
# shp 自动替换为 dat
|
||||||
|
dat_mask = str(Path(water_mask_dir) / "water_mask_from_shp.dat")
|
||||||
|
if Path(dat_mask).exists():
|
||||||
|
print(f"检测到输入掩膜为 .shp,自动替换为栅格掩膜: {dat_mask}")
|
||||||
|
final_water_mask = dat_mask
|
||||||
|
|
||||||
|
if final_water_mask is None:
|
||||||
|
dat_mask_default = str(Path(water_mask_dir) / "water_mask_from_shp.dat")
|
||||||
|
if Path(dat_mask_default).exists():
|
||||||
|
final_water_mask = dat_mask_default
|
||||||
|
print(f"使用步骤1生成的水域掩膜: {final_water_mask}")
|
||||||
|
|
||||||
|
# ---- 步骤3.1: 0值像素插值 ----
|
||||||
|
if interpolate_zeros:
|
||||||
|
print("\n" + "-" * 80)
|
||||||
|
print("步骤3.1: 对0值像素进行插值")
|
||||||
|
print("-" * 80)
|
||||||
|
interp_start_time = time.time()
|
||||||
|
|
||||||
|
if _interpolate_zero_pixels_batch is None:
|
||||||
|
from src.core.algorithms.interpolation.interpolator import (
|
||||||
|
interpolate_zero_pixels_batch as _interp_batch,
|
||||||
|
)
|
||||||
|
_interpolate_zero_pixels_batch = _interp_batch
|
||||||
|
|
||||||
|
interp_result, _ = _interpolate_zero_pixels_batch(
|
||||||
|
img_path=img_path,
|
||||||
|
interpolation_method=interpolation_method,
|
||||||
|
output_path=None,
|
||||||
|
water_mask=final_water_mask,
|
||||||
|
deglint_dir=str(deglint_dir),
|
||||||
|
callback_progress=lambda msg: print(f" {msg}"),
|
||||||
|
)
|
||||||
|
img_path = interp_result
|
||||||
|
interp_end_time = time.time()
|
||||||
|
print(f"插值完成,使用插值后的影像: {img_path}")
|
||||||
|
|
||||||
|
# ---- 获取影像信息 ----
|
||||||
|
geotransform, projection, width, height, n_bands = _get_image_geo_info(img_path)
|
||||||
|
print(f"影像尺寸: {width} x {height} x {n_bands}")
|
||||||
|
|
||||||
|
mask_for_algorithm = _prepare_water_mask_for_algorithm(
|
||||||
|
final_water_mask, (height, width), geotransform, projection, img_path
|
||||||
|
)
|
||||||
|
|
||||||
|
# ==================== Kutser ====================
|
||||||
|
if method == "kutser":
|
||||||
|
print(f"使用方法: Kutser (氧吸收波段={oxy_band}, NIR波段={nir_band})")
|
||||||
|
output_path = str(deglint_dir / "deglint_kutser.bsq")
|
||||||
|
|
||||||
|
if Path(output_path).exists():
|
||||||
|
print(f"检测到已存在的去耀斑影像文件,直接使用: {output_path}")
|
||||||
|
notify("skipped", f"去耀斑影像已设置: {output_path}")
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
kutser = Kutser(
|
||||||
|
img_path,
|
||||||
|
shp_path=None,
|
||||||
|
oxy_band=oxy_band,
|
||||||
|
lower_oxy=lower_oxy,
|
||||||
|
upper_oxy=upper_oxy,
|
||||||
|
NIR_band=nir_band,
|
||||||
|
water_mask=mask_for_algorithm,
|
||||||
|
output_path=output_path,
|
||||||
|
)
|
||||||
|
kutser.get_corrected_bands()
|
||||||
|
|
||||||
|
if Path(output_path).exists():
|
||||||
|
_copy_hdr_info(img_path, output_path)
|
||||||
|
notify("completed", f"去耀斑影像已生成: {output_path}")
|
||||||
|
return output_path
|
||||||
|
raise RuntimeError(f"Kutser算法未生成输出文件: {output_path}")
|
||||||
|
|
||||||
|
# ==================== Goodman ====================
|
||||||
|
elif method == "goodman":
|
||||||
|
print(f"使用方法: Goodman (NIR波段范围: {nir_lower}-{nir_upper})")
|
||||||
|
output_path = str(deglint_dir / "deglint_goodman.bsq")
|
||||||
|
|
||||||
|
if Path(output_path).exists():
|
||||||
|
print(f"检测到已存在的去耀斑影像文件,直接使用: {output_path}")
|
||||||
|
notify("skipped", f"去耀斑影像已设置: {output_path}")
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
goodman = Goodman(
|
||||||
|
img_path,
|
||||||
|
NIR_lower=nir_lower,
|
||||||
|
NIR_upper=nir_upper,
|
||||||
|
A=goodman_A,
|
||||||
|
B=goodman_B,
|
||||||
|
water_mask=mask_for_algorithm,
|
||||||
|
output_path=output_path,
|
||||||
|
)
|
||||||
|
corrected_bands = goodman.get_corrected_bands()
|
||||||
|
|
||||||
|
if not Path(output_path).exists():
|
||||||
|
_save_bands_as_image(corrected_bands, output_path, geotransform, projection)
|
||||||
|
_copy_hdr_info(img_path, output_path)
|
||||||
|
else:
|
||||||
|
_copy_hdr_info(img_path, output_path)
|
||||||
|
del corrected_bands
|
||||||
|
|
||||||
|
notify("completed", f"去耀斑影像已生成: {output_path}")
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
# ==================== Hedley ====================
|
||||||
|
elif method == "hedley":
|
||||||
|
print(f"使用方法: Hedley (NIR波段={hedley_nir_band})")
|
||||||
|
output_path = str(deglint_dir / "deglint_hedley.bsq")
|
||||||
|
|
||||||
|
if Path(output_path).exists():
|
||||||
|
print(f"检测到已存在的去耀斑影像文件,直接使用: {output_path}")
|
||||||
|
notify("skipped", f"去耀斑影像已设置: {output_path}")
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
hedley = Hedley(
|
||||||
|
img_path,
|
||||||
|
shp_path=None,
|
||||||
|
NIR_band=hedley_nir_band,
|
||||||
|
water_mask=mask_for_algorithm,
|
||||||
|
output_path=output_path,
|
||||||
|
)
|
||||||
|
hedley.get_corrected_bands()
|
||||||
|
|
||||||
|
if Path(output_path).exists():
|
||||||
|
_copy_hdr_info(img_path, output_path)
|
||||||
|
notify("completed", f"去耀斑影像已生成: {output_path}")
|
||||||
|
return output_path
|
||||||
|
raise RuntimeError(f"Hedley算法未生成输出文件: {output_path}")
|
||||||
|
|
||||||
|
# ==================== SUGAR ====================
|
||||||
|
elif method == "sugar":
|
||||||
|
# 方法名标准化
|
||||||
|
glint_method_raw = str(sugar_glint_mask_method).lower()
|
||||||
|
if "cdf" in glint_method_raw or "累积" in glint_method_raw:
|
||||||
|
sugar_glint_mask_method_fixed = "cdf"
|
||||||
|
elif "otsu" in glint_method_raw or "大津" in glint_method_raw:
|
||||||
|
sugar_glint_mask_method_fixed = "otsu"
|
||||||
|
else:
|
||||||
|
sugar_glint_mask_method_fixed = "cdf"
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"使用方法: SUGAR (迭代次数={sugar_iter}, 掩膜方法={sugar_glint_mask_method_fixed})"
|
||||||
|
)
|
||||||
|
output_path = str(deglint_dir / "deglint_sugar.bsq")
|
||||||
|
|
||||||
|
if Path(output_path).exists():
|
||||||
|
print(f"检测到已存在的去耀斑影像文件,直接使用: {output_path}")
|
||||||
|
notify("skipped", f"去耀斑影像已设置: {output_path}")
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
if sugar_bounds is None:
|
||||||
|
sugar_bounds = [(1, 2)]
|
||||||
|
|
||||||
|
correction_iterative(
|
||||||
|
img_path,
|
||||||
|
iter=sugar_iter,
|
||||||
|
bounds=sugar_bounds,
|
||||||
|
estimate_background=sugar_estimate_background,
|
||||||
|
glint_mask_method=sugar_glint_mask_method_fixed,
|
||||||
|
termination_thresh=sugar_termination_thresh,
|
||||||
|
water_mask=mask_for_algorithm,
|
||||||
|
output_path=output_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
if Path(output_path).exists():
|
||||||
|
_copy_hdr_info(img_path, output_path)
|
||||||
|
notify("completed", f"去耀斑影像已生成: {output_path}")
|
||||||
|
return output_path
|
||||||
|
raise RuntimeError(f"SUGAR算法未生成输出文件: {output_path}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"不支持的方法: {method}。支持的方法: kutser, goodman, hedley, sugar"
|
||||||
|
)
|
||||||
148
src/core/steps/water_mask_step.py
Normal file
148
src/core/steps/water_mask_step.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
步骤1: 水域掩膜生成
|
||||||
|
|
||||||
|
支持三种方式:
|
||||||
|
1. 基于 shp 文件栅格化
|
||||||
|
2. 使用现有栅格格式掩膜文件 (.dat/.tif)
|
||||||
|
3. 基于 NDWI 从影像自动生成水体掩膜
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, List, Callable, Union
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
class WaterMaskStep:
|
||||||
|
"""水域掩膜生成步骤"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(
|
||||||
|
mask_path: Optional[str] = None,
|
||||||
|
img_path: Optional[str] = None,
|
||||||
|
ndwi_threshold: float = 0.4,
|
||||||
|
use_ndwi: bool = False,
|
||||||
|
generate_png: bool = True,
|
||||||
|
output_path: Optional[str] = None,
|
||||||
|
water_mask_dir: Union[str, Path] = "./1_water_mask",
|
||||||
|
callback: Optional[Callable] = None,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
执行水域掩膜生成
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mask_path: 水体掩膜文件路径,支持 .shp(需 img_path)或 .dat/.tif(直接使用)
|
||||||
|
img_path: 输入影像文件路径(当 mask_path 为 shp 或 use_ndwi=True 时必须提供)
|
||||||
|
ndwi_threshold: NDWI 阈值(use_ndwi=True 时使用)
|
||||||
|
use_ndwi: 是否使用 NDWI 方法从影像生成水体掩膜
|
||||||
|
generate_png: 是否生成 PNG 预览图(默认 True)
|
||||||
|
output_path: 指定输出掩膜文件的保存路径(可选)
|
||||||
|
water_mask_dir: 工作目录
|
||||||
|
callback: 回调函数,签名为 callback(step, status, message)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dat 格式的水域掩膜文件路径
|
||||||
|
"""
|
||||||
|
from src.utils.extract_water_area import rasterize_shp, ndwi
|
||||||
|
from src.core.utils.preview_generator import (
|
||||||
|
generate_image_preview,
|
||||||
|
generate_water_mask_overlay,
|
||||||
|
)
|
||||||
|
|
||||||
|
water_mask_dir = Path(water_mask_dir)
|
||||||
|
water_mask_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def notify(status, msg=""):
|
||||||
|
if callback:
|
||||||
|
callback("步骤1", status, msg)
|
||||||
|
|
||||||
|
print("\n" + "=" * 80)
|
||||||
|
print("步骤1: 生成或设置水域mask")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
step_start_time = time.time()
|
||||||
|
|
||||||
|
# 生成影像预览图
|
||||||
|
if generate_png and img_path is not None and Path(img_path).exists():
|
||||||
|
preview_path = str(water_mask_dir / "hsi_preview.png")
|
||||||
|
generate_image_preview(
|
||||||
|
img_path=img_path,
|
||||||
|
output_path=preview_path,
|
||||||
|
title="影像预览: RGB波段(基于波长)"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---- NDWI 方法 ----
|
||||||
|
if use_ndwi:
|
||||||
|
if img_path is None:
|
||||||
|
raise ValueError("当 use_ndwi=True 时,必须提供 img_path 参数")
|
||||||
|
if not Path(img_path).exists():
|
||||||
|
raise ValueError(f"影像文件不存在: {img_path}")
|
||||||
|
|
||||||
|
print(f"使用NDWI方法从影像生成水体掩膜,阈值={ndwi_threshold}...")
|
||||||
|
|
||||||
|
ndwi_output_path = output_path or str(water_mask_dir / "water_mask_from_ndwi.dat")
|
||||||
|
os.makedirs(Path(ndwi_output_path).parent, exist_ok=True)
|
||||||
|
|
||||||
|
if Path(ndwi_output_path).exists():
|
||||||
|
print(f"检测到已存在的NDWI掩膜文件,直接使用: {ndwi_output_path}")
|
||||||
|
notify("skipped", f"水域掩膜已设置: {ndwi_output_path}")
|
||||||
|
return ndwi_output_path
|
||||||
|
|
||||||
|
ndwi(img_path, ndwi_threshold, ndwi_output_path)
|
||||||
|
|
||||||
|
if generate_png:
|
||||||
|
overlay_path = water_mask_dir / "water_mask_overlay.png"
|
||||||
|
generate_water_mask_overlay(
|
||||||
|
img_path=img_path, mask_path=ndwi_output_path, output_path=str(overlay_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
notify("completed", f"NDWI水体掩膜已生成: {ndwi_output_path}")
|
||||||
|
return ndwi_output_path
|
||||||
|
|
||||||
|
# ---- 必须提供 mask_path ----
|
||||||
|
if mask_path is None:
|
||||||
|
raise ValueError("必须提供 mask_path 参数或设置 use_ndwi=True")
|
||||||
|
if not Path(mask_path).exists():
|
||||||
|
raise ValueError(f"文件不存在: {mask_path}")
|
||||||
|
|
||||||
|
file_ext = Path(mask_path).suffix.lower()
|
||||||
|
|
||||||
|
# ---- SHP 栅格化 ----
|
||||||
|
if file_ext == ".shp":
|
||||||
|
if img_path is None:
|
||||||
|
raise ValueError("当 mask_path 为 shp 格式时,必须提供 img_path 参数")
|
||||||
|
|
||||||
|
print(f"检测到shp格式的水体掩膜,正在转换为dat格式...")
|
||||||
|
|
||||||
|
shp_output_path = output_path or str(water_mask_dir / "water_mask_from_shp.dat")
|
||||||
|
os.makedirs(Path(shp_output_path).parent, exist_ok=True)
|
||||||
|
|
||||||
|
if Path(shp_output_path).exists():
|
||||||
|
print(f"检测到已存在的栅格化掩膜文件,直接使用: {shp_output_path}")
|
||||||
|
notify("skipped", f"水域掩膜已设置: {shp_output_path}")
|
||||||
|
if generate_png:
|
||||||
|
overlay_path = water_mask_dir / "water_mask_overlay.png"
|
||||||
|
if not overlay_path.exists():
|
||||||
|
generate_water_mask_overlay(img_path, shp_output_path, str(overlay_path))
|
||||||
|
return shp_output_path
|
||||||
|
|
||||||
|
safe_mask_path = os.path.abspath(mask_path).replace("\\", "/")
|
||||||
|
rasterize_shp(safe_mask_path, shp_output_path, img_path)
|
||||||
|
|
||||||
|
if generate_png:
|
||||||
|
overlay_path = water_mask_dir / "water_mask_overlay.png"
|
||||||
|
generate_water_mask_overlay(img_path, shp_output_path, str(overlay_path))
|
||||||
|
|
||||||
|
notify("completed", f"dat格式水域掩膜已生成: {shp_output_path}")
|
||||||
|
return shp_output_path
|
||||||
|
|
||||||
|
# ---- 栅格格式直接使用 ----
|
||||||
|
print(f"检测到栅格格式的水体掩膜,直接使用: {mask_path}")
|
||||||
|
if generate_png and img_path is not None and Path(img_path).exists():
|
||||||
|
overlay_path = water_mask_dir / "water_mask_overlay.png"
|
||||||
|
generate_water_mask_overlay(img_path, mask_path, str(overlay_path))
|
||||||
|
|
||||||
|
notify("completed", f"水域掩膜已设置: {mask_path}")
|
||||||
|
return mask_path
|
||||||
@ -71,6 +71,10 @@ from src.core.utils.preview_generator import (
|
|||||||
)
|
)
|
||||||
# 导入算法层模块
|
# 导入算法层模块
|
||||||
from src.core.algorithms.interpolation.interpolator import interpolate_zero_pixels_batch as _interpolate_zero_pixels_batch
|
from src.core.algorithms.interpolation.interpolator import interpolate_zero_pixels_batch as _interpolate_zero_pixels_batch
|
||||||
|
# 导入业务步骤模块
|
||||||
|
from src.core.steps.water_mask_step import WaterMaskStep
|
||||||
|
from src.core.steps.glint_detection_step import GlintDetectionStep
|
||||||
|
from src.core.steps.glint_removal_step import GlintRemovalStep
|
||||||
# 导入新的耀斑去除算法
|
# 导入新的耀斑去除算法
|
||||||
from src.core.glint_removal.Kutser import Kutser
|
from src.core.glint_removal.Kutser import Kutser
|
||||||
from src.core.glint_removal.Goodman import Goodman
|
from src.core.glint_removal.Goodman import Goodman
|
||||||
@ -260,158 +264,23 @@ class WaterQualityInversionPipeline:
|
|||||||
skip_dependency_check: bool = False,
|
skip_dependency_check: bool = False,
|
||||||
generate_png: bool = True,
|
generate_png: bool = True,
|
||||||
output_path: Optional[str] = None) -> str:
|
output_path: Optional[str] = None) -> str:
|
||||||
"""
|
"""步骤1: 生成或设置水域mask(Facade)"""
|
||||||
步骤1: 生成或设置水域mask
|
|
||||||
|
|
||||||
支持三种方式生成水域掩膜:
|
|
||||||
1. 基于shp文件栅格化
|
|
||||||
2. 使用现有的栅格格式掩膜文件
|
|
||||||
3. 基于NDWI从影像自动生成水体掩膜
|
|
||||||
|
|
||||||
当提供img_path时,会自动生成PNG预览图,基于波长选择RGB波段:
|
|
||||||
- 红波段: ~650nm
|
|
||||||
- 绿波段: ~550nm
|
|
||||||
- 蓝波段: ~460nm
|
|
||||||
|
|
||||||
Args:
|
|
||||||
mask_path: 水体掩膜文件路径,支持:
|
|
||||||
- shp格式文件(.shp):需要提供img_path用于栅格化
|
|
||||||
- dat格式文件(.dat/.tif等栅格格式):直接使用,不需要img_path
|
|
||||||
- None:当use_ndwi=True时,从影像生成NDWI掩膜
|
|
||||||
img_path: 输入影像文件路径(当mask_path为shp格式或use_ndwi=True时必须提供)
|
|
||||||
ndwi_threshold: NDWI阈值(当use_ndwi=True时使用)
|
|
||||||
use_ndwi: 是否使用NDWI方法从影像生成水体掩膜
|
|
||||||
generate_png: 是否生成输入影像的PNG预览图(默认True)
|
|
||||||
output_path: 指定输出掩膜文件的保存路径(可选)。如果提供,掩膜将保存到此路径;
|
|
||||||
如果为None,则使用默认路径(self.water_mask_dir)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dat格式的水域掩膜文件路径
|
|
||||||
"""
|
|
||||||
print("\n" + "="*80)
|
|
||||||
print("步骤1: 生成或设置水域mask")
|
|
||||||
print("="*80)
|
|
||||||
|
|
||||||
step_start_time = time.time()
|
step_start_time = time.time()
|
||||||
try:
|
try:
|
||||||
# 如果提供了img_path且开启生成PNG功能,生成影像预览图
|
result = WaterMaskStep.run(
|
||||||
if generate_png and img_path is not None and Path(img_path).exists():
|
mask_path=mask_path,
|
||||||
self._generate_image_preview(img_path)
|
img_path=img_path,
|
||||||
|
ndwi_threshold=ndwi_threshold,
|
||||||
if use_ndwi:
|
use_ndwi=use_ndwi,
|
||||||
# 使用NDWI方法从影像生成水体掩膜
|
generate_png=generate_png,
|
||||||
if img_path is None:
|
output_path=output_path,
|
||||||
raise ValueError("当use_ndwi=True时,必须提供img_path参数用于生成NDWI掩膜")
|
water_mask_dir=str(self.water_mask_dir),
|
||||||
if not Path(img_path).exists():
|
callback=self._notify,
|
||||||
raise ValueError(f"影像文件不存在: {img_path}")
|
)
|
||||||
|
self.water_mask_path = result
|
||||||
print(f"使用NDWI方法从影像生成水体掩膜,阈值={ndwi_threshold}...")
|
|
||||||
|
|
||||||
# 使用用户指定的输出路径,或使用默认路径
|
|
||||||
if output_path:
|
|
||||||
ndwi_output_path = output_path
|
|
||||||
# 确保输出目录存在
|
|
||||||
os.makedirs(Path(output_path).parent, exist_ok=True)
|
|
||||||
else:
|
|
||||||
ndwi_output_path = str(self.water_mask_dir / "water_mask_from_ndwi.dat")
|
|
||||||
|
|
||||||
# 检查文件是否已存在,避免重复生成
|
|
||||||
if Path(ndwi_output_path).exists():
|
|
||||||
print(f"检测到已存在的NDWI掩膜文件,直接使用: {ndwi_output_path}")
|
|
||||||
self.water_mask_path = ndwi_output_path
|
|
||||||
step_end_time = time.time()
|
|
||||||
self._record_step_time("步骤1: 生成水域mask", step_start_time, step_end_time, status="skipped")
|
|
||||||
print(f"水域掩膜已设置: {self.water_mask_path}")
|
|
||||||
|
|
||||||
# 生成水域掩膜叠加图(如果不存在)
|
|
||||||
overlay_path = self.water_mask_dir / "water_mask_overlay.png"
|
|
||||||
if generate_png and img_path is not None and Path(img_path).exists() and not overlay_path.exists():
|
|
||||||
self._generate_water_mask_overlay(img_path, self.water_mask_path)
|
|
||||||
|
|
||||||
return self.water_mask_path
|
|
||||||
|
|
||||||
# 执行NDWI水体提取
|
|
||||||
from src.utils.extract_water_area import ndwi
|
|
||||||
ndwi(img_path, ndwi_threshold, ndwi_output_path)
|
|
||||||
self.water_mask_path = ndwi_output_path
|
|
||||||
step_end_time = time.time()
|
step_end_time = time.time()
|
||||||
self._record_step_time("步骤1: 生成水域mask", step_start_time, step_end_time)
|
self._record_step_time("步骤1: 生成水域mask", step_start_time, step_end_time)
|
||||||
print(f"已生成NDWI水体掩膜: {self.water_mask_path}")
|
return result
|
||||||
|
|
||||||
# 生成水域掩膜叠加图
|
|
||||||
if generate_png:
|
|
||||||
self._generate_water_mask_overlay(img_path, self.water_mask_path)
|
|
||||||
|
|
||||||
return self.water_mask_path
|
|
||||||
|
|
||||||
elif mask_path is None:
|
|
||||||
raise ValueError("必须提供mask_path参数或设置use_ndwi=True")
|
|
||||||
|
|
||||||
if not Path(mask_path).exists():
|
|
||||||
raise ValueError(f"文件不存在: {mask_path}")
|
|
||||||
|
|
||||||
# 检查文件扩展名,判断是shp文件还是dat文件
|
|
||||||
file_ext = Path(mask_path).suffix.lower()
|
|
||||||
|
|
||||||
if file_ext == '.shp':
|
|
||||||
# 如果是shp文件,需要栅格化为dat
|
|
||||||
if img_path is None:
|
|
||||||
raise ValueError("当mask_path为shp格式时,必须提供img_path参数用于栅格化")
|
|
||||||
|
|
||||||
print(f"检测到shp格式的水体掩膜,正在转换为dat格式...")
|
|
||||||
|
|
||||||
# 使用用户指定的输出路径,或使用默认路径
|
|
||||||
if output_path:
|
|
||||||
shp_output_path = output_path
|
|
||||||
# 确保输出目录存在
|
|
||||||
os.makedirs(Path(output_path).parent, exist_ok=True)
|
|
||||||
else:
|
|
||||||
shp_output_path = str(self.water_mask_dir / "water_mask_from_shp.dat")
|
|
||||||
|
|
||||||
# 检查文件是否已存在,避免重复栅格化
|
|
||||||
if Path(shp_output_path).exists():
|
|
||||||
print(f"检测到已存在的栅格化掩膜文件,直接使用: {shp_output_path}")
|
|
||||||
self.water_mask_path = shp_output_path
|
|
||||||
step_end_time = time.time()
|
|
||||||
self._record_step_time("步骤1: 生成水域mask", step_start_time, step_end_time, status="skipped")
|
|
||||||
print(f"水域掩膜已设置: {self.water_mask_path}")
|
|
||||||
|
|
||||||
# 生成水域掩膜叠加图(如果不存在)
|
|
||||||
overlay_path = self.water_mask_dir / "water_mask_overlay.png"
|
|
||||||
if generate_png and img_path is not None and Path(img_path).exists() and not overlay_path.exists():
|
|
||||||
self._generate_water_mask_overlay(img_path, self.water_mask_path)
|
|
||||||
|
|
||||||
return self.water_mask_path
|
|
||||||
|
|
||||||
# 执行栅格化
|
|
||||||
from src.utils.extract_water_area import rasterize_shp
|
|
||||||
safe_mask_path = os.path.abspath(mask_path).replace('\\', '/')
|
|
||||||
rasterize_shp(safe_mask_path, shp_output_path, img_path)
|
|
||||||
self.water_mask_path = shp_output_path
|
|
||||||
step_end_time = time.time()
|
|
||||||
self._record_step_time("步骤1: 生成水域mask", step_start_time, step_end_time)
|
|
||||||
print(f"已生成dat格式的水域掩膜: {self.water_mask_path}")
|
|
||||||
|
|
||||||
# 生成水域掩膜叠加图
|
|
||||||
if generate_png:
|
|
||||||
self._generate_water_mask_overlay(img_path, self.water_mask_path)
|
|
||||||
|
|
||||||
return self.water_mask_path
|
|
||||||
|
|
||||||
else:
|
|
||||||
# 如果是dat或其他栅格格式,直接使用
|
|
||||||
print(f"检测到栅格格式的水体掩膜,直接使用: {mask_path}")
|
|
||||||
self.water_mask_path = mask_path
|
|
||||||
step_end_time = time.time()
|
|
||||||
self._record_step_time("步骤1: 生成水域mask", step_start_time, step_end_time)
|
|
||||||
print(f"水域掩膜已设置: {self.water_mask_path} (dat格式)")
|
|
||||||
|
|
||||||
# 生成水域掩膜叠加图
|
|
||||||
if generate_png and img_path is not None and Path(img_path).exists():
|
|
||||||
self._generate_water_mask_overlay(img_path, self.water_mask_path)
|
|
||||||
|
|
||||||
return self.water_mask_path
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
step_end_time = time.time()
|
step_end_time = time.time()
|
||||||
self._record_step_time("步骤1: 生成水域mask", step_start_time, step_end_time,
|
self._record_step_time("步骤1: 生成水域mask", step_start_time, step_end_time,
|
||||||
@ -520,100 +389,30 @@ class WaterQualityInversionPipeline:
|
|||||||
buffer_size: Optional[int] = None,
|
buffer_size: Optional[int] = None,
|
||||||
water_mask_path: Optional[str] = None,
|
water_mask_path: Optional[str] = None,
|
||||||
skip_dependency_check: bool = False) -> str:
|
skip_dependency_check: bool = False) -> str:
|
||||||
"""
|
"""步骤2: 找到耀斑区域(Facade)"""
|
||||||
步骤2: 找到耀斑区域
|
|
||||||
|
|
||||||
Args:
|
|
||||||
img_path: 输入影像文件路径
|
|
||||||
glint_wave: 用于提取耀斑严重区域的波段波长(单波段方法使用)
|
|
||||||
method: 检测方法,可选:
|
|
||||||
- 'otsu': Otsu阈值分割(默认)
|
|
||||||
- 'zscore': Z-score统计方法
|
|
||||||
- 'percentile': 百分位数阈值方法
|
|
||||||
- 'iqr': IQR异常值检测
|
|
||||||
- 'adaptive': 自适应阈值方法
|
|
||||||
- 'multi_band': 多波段融合方法
|
|
||||||
z_threshold: Z-score方法的阈值(默认2.5)
|
|
||||||
percentile: 百分位数阈值(默认95.0)
|
|
||||||
iqr_multiplier: IQR倍数(默认1.5)
|
|
||||||
window_size: 自适应阈值窗口大小(默认15)
|
|
||||||
multi_band_waves: 多波段方法的波长列表,如[750, 800, 850]
|
|
||||||
sub_method: 多波段方法的子方法('zscore', 'percentile', 'otsu'),默认'zscore'
|
|
||||||
weights: 多波段方法的权重列表,如果为None则使用等权重
|
|
||||||
max_area: 最大连通域面积阈值(像素数),超过此面积的连通域将被过滤掉,
|
|
||||||
用于去除岸边、浅水、水华等大面积区域(默认None,表示不过滤)
|
|
||||||
buffer_size: 岸边缓冲区大小(像素数),用于去除岸边附近的错误耀斑掩膜
|
|
||||||
(默认None,表示不进行岸边缓冲区去除;设置为正整数时启用)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
耀斑掩膜文件路径
|
|
||||||
"""
|
|
||||||
print("\n" + "="*80)
|
|
||||||
print("步骤2: 找到耀斑区域")
|
|
||||||
print("="*80)
|
|
||||||
|
|
||||||
step_start_time = time.time()
|
step_start_time = time.time()
|
||||||
try:
|
try:
|
||||||
# 使用dat格式的水体掩膜
|
result = GlintDetectionStep.run(
|
||||||
if water_mask_path is not None:
|
img_path=img_path,
|
||||||
# 优先使用传入的参数
|
glint_wave=glint_wave,
|
||||||
final_water_mask_path = water_mask_path
|
method=method,
|
||||||
elif self.water_mask_path is not None:
|
z_threshold=z_threshold,
|
||||||
# 其次使用步骤1生成的水体掩膜
|
percentile=percentile,
|
||||||
final_water_mask_path = self.water_mask_path
|
iqr_multiplier=iqr_multiplier,
|
||||||
else:
|
window_size=window_size,
|
||||||
# 如果没有水体掩膜,根据skip_dependency_check决定行为
|
multi_band_waves=multi_band_waves,
|
||||||
if skip_dependency_check:
|
sub_method=sub_method,
|
||||||
print("警告: 未找到水体掩膜,将对全图进行耀斑检测")
|
weights=weights,
|
||||||
final_water_mask_path = None
|
max_area=max_area,
|
||||||
else:
|
buffer_size=buffer_size,
|
||||||
raise ValueError("请先执行步骤1: 生成水域mask,或提供water_mask_path参数,或设置skip_dependency_check=True")
|
water_mask_path=water_mask_path,
|
||||||
|
glint_dir=str(self.glint_dir),
|
||||||
output_path = str(self.glint_dir / "severe_glint_area.dat")
|
callback=self._notify,
|
||||||
|
|
||||||
# 检查文件是否已存在
|
|
||||||
if Path(output_path).exists():
|
|
||||||
print(f"检测到已存在的耀斑掩膜文件,直接使用: {output_path}")
|
|
||||||
self.glint_mask_path = output_path
|
|
||||||
step_end_time = time.time()
|
|
||||||
self._record_step_time("步骤2: 找到耀斑区域", step_start_time, step_end_time, status="skipped")
|
|
||||||
print(f"耀斑掩膜已设置: {self.glint_mask_path}")
|
|
||||||
return self.glint_mask_path
|
|
||||||
|
|
||||||
# 构建参数字典
|
|
||||||
kwargs = {
|
|
||||||
'method': method,
|
|
||||||
'z_threshold': z_threshold,
|
|
||||||
'percentile': percentile,
|
|
||||||
'iqr_multiplier': iqr_multiplier,
|
|
||||||
'window_size': window_size,
|
|
||||||
}
|
|
||||||
|
|
||||||
# 如果是多波段方法,添加相关参数
|
|
||||||
if method == 'multi_band':
|
|
||||||
if multi_band_waves is not None:
|
|
||||||
kwargs['multi_band_waves'] = multi_band_waves
|
|
||||||
if sub_method is not None:
|
|
||||||
kwargs['sub_method'] = sub_method
|
|
||||||
if weights is not None:
|
|
||||||
kwargs['weights'] = weights
|
|
||||||
|
|
||||||
# 添加连通域面积过滤和岸边缓冲区参数
|
|
||||||
if max_area is not None:
|
|
||||||
kwargs['max_area'] = max_area
|
|
||||||
if buffer_size is not None:
|
|
||||||
kwargs['buffer_size'] = buffer_size
|
|
||||||
|
|
||||||
# 传递dat格式的水体掩膜文件路径
|
|
||||||
self.glint_mask_path = find_severe_glint_area(
|
|
||||||
img_path, final_water_mask_path, glint_wave, output_path, **kwargs
|
|
||||||
)
|
)
|
||||||
|
self.glint_mask_path = result
|
||||||
step_end_time = time.time()
|
step_end_time = time.time()
|
||||||
self._record_step_time("步骤2: 找到耀斑区域", step_start_time, step_end_time)
|
self._record_step_time("步骤2: 找到耀斑区域", step_start_time, step_end_time)
|
||||||
print(f"耀斑掩膜已生成: {self.glint_mask_path}")
|
return result
|
||||||
print(f"使用检测方法: {method}")
|
|
||||||
return self.glint_mask_path
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
step_end_time = time.time()
|
step_end_time = time.time()
|
||||||
self._record_step_time("步骤2: 找到耀斑区域", step_start_time, step_end_time,
|
self._record_step_time("步骤2: 找到耀斑区域", step_start_time, step_end_time,
|
||||||
@ -708,28 +507,21 @@ class WaterQualityInversionPipeline:
|
|||||||
left_shoulder_wave: Optional[float] = None,
|
left_shoulder_wave: Optional[float] = None,
|
||||||
valley_wave: Optional[float] = None,
|
valley_wave: Optional[float] = None,
|
||||||
right_shoulder_wave: Optional[float] = None,
|
right_shoulder_wave: Optional[float] = None,
|
||||||
# 水域掩膜参数
|
|
||||||
water_mask: Optional[Union[str, np.ndarray]] = None,
|
water_mask: Optional[Union[str, np.ndarray]] = None,
|
||||||
# 0值像素插值参数
|
|
||||||
interpolate_zeros: bool = False,
|
interpolate_zeros: bool = False,
|
||||||
interpolation_method: str = 'nearest',
|
interpolation_method: str = 'nearest',
|
||||||
# 是否执行去除耀斑
|
|
||||||
enabled: bool = True,
|
enabled: bool = True,
|
||||||
# Kutser方法参数
|
|
||||||
kutser_shp_path: Optional[str] = None,
|
kutser_shp_path: Optional[str] = None,
|
||||||
oxy_band: int = 38,
|
oxy_band: int = 38,
|
||||||
lower_oxy: int = 36,
|
lower_oxy: int = 36,
|
||||||
upper_oxy: int = 49,
|
upper_oxy: int = 49,
|
||||||
nir_band: int = 47,
|
nir_band: int = 47,
|
||||||
# Goodman方法参数
|
|
||||||
nir_lower: int = 25,
|
nir_lower: int = 25,
|
||||||
nir_upper: int = 37,
|
nir_upper: int = 37,
|
||||||
goodman_A: float = 0.000019,
|
goodman_A: float = 0.000019,
|
||||||
goodman_B: float = 0.1,
|
goodman_B: float = 0.1,
|
||||||
# Hedley方法参数
|
|
||||||
hedley_shp_path: Optional[str] = None,
|
hedley_shp_path: Optional[str] = None,
|
||||||
hedley_nir_band: int = 47,
|
hedley_nir_band: int = 47,
|
||||||
# SUGAR方法参数
|
|
||||||
sugar_bounds: Optional[List[tuple]] = None,
|
sugar_bounds: Optional[List[tuple]] = None,
|
||||||
sugar_sigma: float = 1.0,
|
sugar_sigma: float = 1.0,
|
||||||
sugar_estimate_background: bool = True,
|
sugar_estimate_background: bool = True,
|
||||||
@ -737,286 +529,53 @@ class WaterQualityInversionPipeline:
|
|||||||
sugar_iter: Optional[int] = 3,
|
sugar_iter: Optional[int] = 3,
|
||||||
sugar_termination_thresh: float = 20.0,
|
sugar_termination_thresh: float = 20.0,
|
||||||
skip_dependency_check: bool = False) -> str:
|
skip_dependency_check: bool = False) -> str:
|
||||||
"""
|
"""步骤3: 去除耀斑(Facade)"""
|
||||||
步骤3: 去除耀斑
|
|
||||||
|
|
||||||
Args:
|
|
||||||
img_path: 输入影像文件路径
|
|
||||||
method: 去耀斑方法,支持:
|
|
||||||
- "subtract_nir": 减去NIR方法
|
|
||||||
- "regression_slope": 回归斜率方法
|
|
||||||
- "oxygen_absorption": 氧吸收谷方法
|
|
||||||
- "kutser": Kutser方法(基于氧吸收特征)
|
|
||||||
- "goodman": Goodman方法
|
|
||||||
- "hedley": Hedley方法(基于NIR相关性)
|
|
||||||
- "sugar": SUGAR方法(迭代去耀斑)
|
|
||||||
start_wave: 起始波长(subtract_nir和regression_slope方法需要)
|
|
||||||
end_wave: 结束波长(subtract_nir和regression_slope方法需要)
|
|
||||||
json_path: ROI JSON文件路径(regression_slope方法需要)
|
|
||||||
left_shoulder_wave: 左肩波长(oxygen_absorption方法需要)
|
|
||||||
valley_wave: 谷值波长(oxygen_absorption方法需要)
|
|
||||||
right_shoulder_wave: 右肩波长(oxygen_absorption方法需要)
|
|
||||||
water_mask: 水域掩膜,可以是:
|
|
||||||
- None: 自动使用步骤1生成的水域掩膜(如果存在)
|
|
||||||
- numpy数组: 直接使用数组作为掩膜
|
|
||||||
- 文件路径: 栅格文件路径(.dat/.tif)或shapefile路径(.shp)
|
|
||||||
如果为None且步骤1未生成掩膜,则处理全图
|
|
||||||
interpolate_zeros: 是否对0值像素进行插值(默认False)
|
|
||||||
interpolation_method: 插值方法,支持:
|
|
||||||
- 'nearest': 邻近插值(最快)
|
|
||||||
- 'bilinear': 双线性插值
|
|
||||||
- 'spline': 样条插值(RBF)
|
|
||||||
- 'kriging': 克里金插值(最慢但最准确)
|
|
||||||
# Kutser方法参数
|
|
||||||
kutser_shp_path: 深水区域shp文件路径(可选,已废弃,请使用water_mask)
|
|
||||||
oxy_band: 氧吸收波段索引(默认38,对应760.6nm)
|
|
||||||
lower_oxy: 氧吸收下波段索引(默认36,对应742.39nm)
|
|
||||||
upper_oxy: 氧吸收上波段索引(默认49,对应860.48nm)
|
|
||||||
nir_band: NIR波段索引(默认47,对应842.36nm)
|
|
||||||
# Goodman方法参数
|
|
||||||
nir_lower: NIR下波段索引(默认25,对应641.93nm)
|
|
||||||
nir_upper: NIR上波段索引(默认37,对应751.49nm)
|
|
||||||
goodman_A: Goodman参数A(默认0.000019)
|
|
||||||
goodman_B: Goodman参数B(默认0.1)
|
|
||||||
# Hedley方法参数
|
|
||||||
hedley_shp_path: 深水区域shp文件路径(可选,已废弃,请使用water_mask)
|
|
||||||
hedley_nir_band: NIR波段索引(默认47,对应842.36nm)
|
|
||||||
# SUGAR方法参数
|
|
||||||
sugar_bounds: 优化边界列表,如[(1,2)](默认None,使用[(1,2)])
|
|
||||||
sugar_sigma: LoG平滑sigma(默认1.0)
|
|
||||||
sugar_estimate_background: 是否估计背景光谱(默认True)
|
|
||||||
sugar_glint_mask_method: 耀斑掩膜方法,"cdf"或"otsu"(默认"cdf")
|
|
||||||
sugar_iter: 迭代次数,None表示自动终止(默认3)
|
|
||||||
sugar_termination_thresh: 终止阈值(默认20.0)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
去除耀斑后的影像文件路径
|
|
||||||
"""
|
|
||||||
print("\n" + "="*80)
|
|
||||||
print("步骤3: 去除耀斑")
|
|
||||||
print("="*80)
|
|
||||||
|
|
||||||
step_start_time = time.time()
|
step_start_time = time.time()
|
||||||
try:
|
try:
|
||||||
# 兼容中文和各种格式
|
result = GlintRemovalStep.run(
|
||||||
raw_method = str(method).lower()
|
|
||||||
if 'kutser' in raw_method:
|
|
||||||
method = 'kutser'
|
|
||||||
elif 'goodman' in raw_method:
|
|
||||||
method = 'goodman'
|
|
||||||
elif 'hedley' in raw_method:
|
|
||||||
method = 'hedley'
|
|
||||||
elif 'sugar' in raw_method:
|
|
||||||
method = 'sugar'
|
|
||||||
# 其余方法(subtract_nir, regression_slope, oxygen_absorption)保持原值
|
|
||||||
|
|
||||||
# 如果未启用,直接跳过处理并把原始影像路径作为后续流程输入
|
|
||||||
if not enabled:
|
|
||||||
print("已设置跳过去除耀斑(enabled=False),将直接使用原始影像。")
|
|
||||||
self.deglint_img_path = img_path
|
|
||||||
step_end_time = time.time()
|
|
||||||
self._record_step_time("步骤3: 去除耀斑", step_start_time, step_end_time, status="skipped")
|
|
||||||
return self.deglint_img_path
|
|
||||||
|
|
||||||
# 确定使用的水域掩膜
|
|
||||||
final_water_mask = water_mask
|
|
||||||
|
|
||||||
# 【新增智能转换逻辑】:如果 GUI 传入的是 .shp,且第一步已经生成了 .dat 掩膜,强制替换为栅格 .dat
|
|
||||||
if final_water_mask is not None and str(final_water_mask).lower().endswith('.shp'):
|
|
||||||
if self.water_mask_path is not None and Path(self.water_mask_path).exists():
|
|
||||||
print(f"检测到输入掩膜为 .shp 矢量文件,自动替换为步骤1生成的栅格掩膜: {self.water_mask_path}")
|
|
||||||
final_water_mask = self.water_mask_path
|
|
||||||
|
|
||||||
# 优先级处理
|
|
||||||
if final_water_mask is None:
|
|
||||||
if self.water_mask_path is not None:
|
|
||||||
final_water_mask = self.water_mask_path
|
|
||||||
print(f"使用步骤1生成的水域掩膜: {final_water_mask}")
|
|
||||||
else:
|
|
||||||
print("未提供水域掩膜,将处理全图")
|
|
||||||
final_water_mask = None
|
|
||||||
|
|
||||||
# 步骤3.1: 对0值像素进行插值(如果启用)
|
|
||||||
if interpolate_zeros:
|
|
||||||
print("\n" + "-"*80)
|
|
||||||
print("步骤3.1: 对0值像素进行插值")
|
|
||||||
print("-"*80)
|
|
||||||
interp_start_time = time.time()
|
|
||||||
try:
|
|
||||||
# 准备水域掩膜用于插值
|
|
||||||
interp_water_mask = final_water_mask
|
|
||||||
if interp_water_mask is None and self.water_mask_path:
|
|
||||||
interp_water_mask = self.water_mask_path
|
|
||||||
|
|
||||||
# 执行插值
|
|
||||||
interpolated_img = self._interpolate_zero_pixels(
|
|
||||||
img_path=img_path,
|
img_path=img_path,
|
||||||
|
method=method,
|
||||||
|
start_wave=start_wave,
|
||||||
|
end_wave=end_wave,
|
||||||
|
json_path=json_path,
|
||||||
|
left_shoulder_wave=left_shoulder_wave,
|
||||||
|
valley_wave=valley_wave,
|
||||||
|
right_shoulder_wave=right_shoulder_wave,
|
||||||
|
water_mask=water_mask,
|
||||||
|
interpolate_zeros=interpolate_zeros,
|
||||||
interpolation_method=interpolation_method,
|
interpolation_method=interpolation_method,
|
||||||
water_mask=interp_water_mask
|
enabled=enabled,
|
||||||
|
kutser_shp_path=kutser_shp_path,
|
||||||
|
oxy_band=oxy_band,
|
||||||
|
lower_oxy=lower_oxy,
|
||||||
|
upper_oxy=upper_oxy,
|
||||||
|
nir_band=nir_band,
|
||||||
|
nir_lower=nir_lower,
|
||||||
|
nir_upper=nir_upper,
|
||||||
|
goodman_A=goodman_A,
|
||||||
|
goodman_B=goodman_B,
|
||||||
|
hedley_shp_path=hedley_shp_path,
|
||||||
|
hedley_nir_band=hedley_nir_band,
|
||||||
|
sugar_bounds=sugar_bounds,
|
||||||
|
sugar_sigma=sugar_sigma,
|
||||||
|
sugar_estimate_background=sugar_estimate_background,
|
||||||
|
sugar_glint_mask_method=sugar_glint_mask_method,
|
||||||
|
sugar_iter=sugar_iter,
|
||||||
|
sugar_termination_thresh=sugar_termination_thresh,
|
||||||
|
_get_image_geo_info=self._get_image_geo_info,
|
||||||
|
_load_image_as_array=self._load_image_as_array,
|
||||||
|
_save_bands_as_image=self._save_bands_as_image,
|
||||||
|
_copy_hdr_info=self._copy_hdr_info,
|
||||||
|
_prepare_water_mask_for_algorithm=self._prepare_water_mask_for_algorithm,
|
||||||
|
_interpolate_zero_pixels_batch=_interpolate_zero_pixels_batch,
|
||||||
|
deglint_dir=str(self.deglint_dir),
|
||||||
|
water_mask_dir=str(self.water_mask_dir),
|
||||||
|
callback=self._notify,
|
||||||
)
|
)
|
||||||
# 使用插值后的影像作为后续处理的输入
|
self.deglint_img_path = result
|
||||||
img_path = interpolated_img
|
|
||||||
interp_end_time = time.time()
|
|
||||||
self._record_step_time("步骤3.1: 0值像素插值", interp_start_time, interp_end_time)
|
|
||||||
print(f"插值完成,使用插值后的影像: {img_path}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"警告: 0值像素插值失败: {e},将使用原始影像继续处理")
|
|
||||||
interp_end_time = time.time()
|
|
||||||
self._record_step_time("步骤3.1: 0值像素插值", interp_start_time, interp_end_time,
|
|
||||||
status="failed", error=str(e))
|
|
||||||
|
|
||||||
if method == "kutser":
|
|
||||||
print(f"使用方法: Kutser (氧吸收波段={oxy_band}, NIR波段={nir_band})")
|
|
||||||
|
|
||||||
output_path = str(self.deglint_dir / "deglint_kutser.bsq")
|
|
||||||
bsq_path = output_path if output_path.endswith('.bsq') else output_path.replace('.dat', '.bsq').replace(
|
|
||||||
'.tif', '.bsq')
|
|
||||||
if Path(bsq_path).exists() or Path(output_path).exists():
|
|
||||||
existing_path = bsq_path if Path(bsq_path).exists() else output_path
|
|
||||||
print(f"检测到已存在的去耀斑影像文件,直接使用: {existing_path}")
|
|
||||||
self.deglint_img_path = existing_path
|
|
||||||
self._record_step_time("步骤3: 去除耀斑", step_start_time, time.time(), status="skipped")
|
|
||||||
print(f"去耀斑影像已设置: {self.deglint_img_path}")
|
|
||||||
return self.deglint_img_path
|
|
||||||
|
|
||||||
geotransform, projection, width, height, n_bands = self._get_image_geo_info(img_path)
|
|
||||||
print(f"影像尺寸: {width} x {height} x {n_bands}")
|
|
||||||
|
|
||||||
mask_for_algorithm = self._prepare_water_mask_for_algorithm(
|
|
||||||
final_water_mask, (height, width), geotransform, projection, img_path
|
|
||||||
)
|
|
||||||
|
|
||||||
kutser = Kutser(img_path, shp_path=None, oxy_band=oxy_band,
|
|
||||||
lower_oxy=lower_oxy, upper_oxy=upper_oxy,
|
|
||||||
NIR_band=nir_band, water_mask=mask_for_algorithm,
|
|
||||||
output_path=output_path)
|
|
||||||
kutser.get_corrected_bands()
|
|
||||||
|
|
||||||
if Path(bsq_path).exists() or Path(output_path).exists():
|
|
||||||
self.deglint_img_path = bsq_path if Path(bsq_path).exists() else output_path
|
|
||||||
self._copy_hdr_info(img_path, self.deglint_img_path)
|
|
||||||
else:
|
|
||||||
raise RuntimeError(f"Kutser算法未生成输出文件: {bsq_path}")
|
|
||||||
|
|
||||||
elif method == "goodman":
|
|
||||||
print(f"使用方法: Goodman (NIR波段范围: {nir_lower}-{nir_upper})")
|
|
||||||
output_path = str(self.deglint_dir / "deglint_goodman.bsq")
|
|
||||||
bsq_path = output_path if output_path.endswith('.bsq') else output_path.replace('.dat', '.bsq').replace(
|
|
||||||
'.tif', '.bsq')
|
|
||||||
if Path(bsq_path).exists() or Path(output_path).exists():
|
|
||||||
existing_path = bsq_path if Path(bsq_path).exists() else output_path
|
|
||||||
print(f"检测到已存在的去耀斑影像文件,直接使用: {existing_path}")
|
|
||||||
self.deglint_img_path = existing_path
|
|
||||||
self._record_step_time("步骤3: 去除耀斑", step_start_time, time.time(), status="skipped")
|
|
||||||
return self.deglint_img_path
|
|
||||||
|
|
||||||
geotransform, projection, width, height, n_bands = self._get_image_geo_info(img_path)
|
|
||||||
mask_for_algorithm = self._prepare_water_mask_for_algorithm(
|
|
||||||
final_water_mask, (height, width), geotransform, projection, img_path
|
|
||||||
)
|
|
||||||
|
|
||||||
image_array, geotransform, projection = self._load_image_as_array(img_path)
|
|
||||||
goodman = Goodman(img_path, NIR_lower=nir_lower, NIR_upper=nir_upper,
|
|
||||||
A=goodman_A, B=goodman_B, water_mask=mask_for_algorithm,
|
|
||||||
output_path=output_path)
|
|
||||||
corrected_bands = goodman.get_corrected_bands()
|
|
||||||
|
|
||||||
if not Path(bsq_path).exists() and not Path(output_path).exists():
|
|
||||||
self._save_bands_as_image(corrected_bands, output_path, geotransform, projection)
|
|
||||||
self.deglint_img_path = output_path
|
|
||||||
self._copy_hdr_info(img_path, output_path)
|
|
||||||
else:
|
|
||||||
self.deglint_img_path = bsq_path if Path(bsq_path).exists() else output_path
|
|
||||||
self._copy_hdr_info(img_path, self.deglint_img_path)
|
|
||||||
del corrected_bands
|
|
||||||
|
|
||||||
elif method == "hedley":
|
|
||||||
print(f"使用方法: Hedley (NIR波段={hedley_nir_band})")
|
|
||||||
output_path = str(self.deglint_dir / "deglint_hedley.bsq")
|
|
||||||
bsq_path = output_path if output_path.endswith('.bsq') else output_path.replace('.dat', '.bsq').replace(
|
|
||||||
'.tif', '.bsq')
|
|
||||||
if Path(bsq_path).exists() or Path(output_path).exists():
|
|
||||||
existing_path = bsq_path if Path(bsq_path).exists() else output_path
|
|
||||||
print(f"检测到已存在的去耀斑影像文件,直接使用: {existing_path}")
|
|
||||||
self.deglint_img_path = existing_path
|
|
||||||
self._record_step_time("步骤3: 去除耀斑", step_start_time, time.time(), status="skipped")
|
|
||||||
return self.deglint_img_path
|
|
||||||
|
|
||||||
geotransform, projection, width, height, n_bands = self._get_image_geo_info(img_path)
|
|
||||||
mask_for_algorithm = self._prepare_water_mask_for_algorithm(
|
|
||||||
final_water_mask, (height, width), geotransform, projection, img_path
|
|
||||||
)
|
|
||||||
|
|
||||||
hedley = Hedley(img_path, shp_path=None, NIR_band=hedley_nir_band,
|
|
||||||
water_mask=mask_for_algorithm, output_path=output_path)
|
|
||||||
hedley.get_corrected_bands()
|
|
||||||
|
|
||||||
if Path(bsq_path).exists() or Path(output_path).exists():
|
|
||||||
self.deglint_img_path = bsq_path if Path(bsq_path).exists() else output_path
|
|
||||||
self._copy_hdr_info(img_path, self.deglint_img_path)
|
|
||||||
else:
|
|
||||||
raise RuntimeError(f"Hedley算法未生成输出文件: {bsq_path}")
|
|
||||||
|
|
||||||
elif method == "sugar":
|
|
||||||
sugar_glint_mask_method_raw = str(sugar_glint_mask_method).lower()
|
|
||||||
if 'cdf' in sugar_glint_mask_method_raw or '累积' in sugar_glint_mask_method:
|
|
||||||
sugar_glint_mask_method_fixed = 'cdf'
|
|
||||||
elif 'otsu' in sugar_glint_mask_method_raw or '大津' in sugar_glint_mask_method:
|
|
||||||
sugar_glint_mask_method_fixed = 'otsu'
|
|
||||||
else:
|
|
||||||
sugar_glint_mask_method_fixed = 'cdf'
|
|
||||||
|
|
||||||
print(f"使用方法: SUGAR (迭代次数={sugar_iter}, 掩膜方法={sugar_glint_mask_method_fixed})")
|
|
||||||
output_path = str(self.deglint_dir / "deglint_sugar.bsq")
|
|
||||||
|
|
||||||
if Path(output_path).exists():
|
|
||||||
print(f"检测到已存在的去耀斑影像文件,直接使用: {output_path}")
|
|
||||||
self.deglint_img_path = output_path
|
|
||||||
self._record_step_time("步骤3: 去除耀斑", step_start_time, time.time(), status="skipped")
|
|
||||||
return self.deglint_img_path
|
|
||||||
|
|
||||||
geotransform, projection, width, height, n_bands = self._get_image_geo_info(img_path)
|
|
||||||
|
|
||||||
# 修复BUG:必须传入 (height, width)
|
|
||||||
mask_for_algorithm = self._prepare_water_mask_for_algorithm(
|
|
||||||
final_water_mask, (height, width), geotransform, projection, img_path
|
|
||||||
)
|
|
||||||
|
|
||||||
if sugar_bounds is None:
|
|
||||||
sugar_bounds = [(1, 2)]
|
|
||||||
|
|
||||||
if sugar_iter is None:
|
|
||||||
correction_iterative(
|
|
||||||
img_path, iter=None, bounds=sugar_bounds,
|
|
||||||
estimate_background=sugar_estimate_background,
|
|
||||||
glint_mask_method=sugar_glint_mask_method_fixed,
|
|
||||||
termination_thresh=sugar_termination_thresh,
|
|
||||||
water_mask=mask_for_algorithm,
|
|
||||||
output_path=output_path
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
correction_iterative(
|
|
||||||
img_path, iter=sugar_iter, bounds=sugar_bounds,
|
|
||||||
estimate_background=sugar_estimate_background,
|
|
||||||
glint_mask_method=sugar_glint_mask_method_fixed,
|
|
||||||
water_mask=mask_for_algorithm,
|
|
||||||
output_path=output_path
|
|
||||||
)
|
|
||||||
|
|
||||||
bsq_path = output_path if output_path.endswith('.bsq') else output_path.replace('.dat', '.bsq').replace(
|
|
||||||
'.tif', '.bsq')
|
|
||||||
if Path(bsq_path).exists() or Path(output_path).exists():
|
|
||||||
self.deglint_img_path = bsq_path if Path(bsq_path).exists() else output_path
|
|
||||||
self._copy_hdr_info(img_path, self.deglint_img_path)
|
|
||||||
else:
|
|
||||||
raise RuntimeError(f"SUGAR算法未生成输出文件: {bsq_path}")
|
|
||||||
else:
|
|
||||||
raise ValueError(f"不支持的方法: {method}。支持的方法: kutser, goodman, hedley, sugar")
|
|
||||||
|
|
||||||
step_end_time = time.time()
|
step_end_time = time.time()
|
||||||
self._record_step_time("步骤3: 去除耀斑", step_start_time, step_end_time)
|
self._record_step_time("步骤3: 去除耀斑", step_start_time, step_end_time)
|
||||||
print(f"去耀斑影像已生成: {self.deglint_img_path}")
|
return result
|
||||||
return self.deglint_img_path
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
step_end_time = time.time()
|
step_end_time = time.time()
|
||||||
self._record_step_time("步骤3: 去除耀斑", step_start_time, step_end_time,
|
self._record_step_time("步骤3: 去除耀斑", step_start_time, step_end_time,
|
||||||
|
|||||||
Reference in New Issue
Block a user