refactor: 耀斑去除算法从波段索引驱动改造为波长驱动

- Kutser/Hedley/Goodman 的 __init__ 参数从硬编码波段号改为目标波长(nm)
- 内部通过 find_band_number() 从 HDR 元数据动态解析波段索引
- Goodman 消除 P0 不一致: 25/37 vs 65/91 统一为 641.93/751.49nm
- 彻底解除传感器耦合,任何传感器均可自动适配
This commit is contained in:
duxin
2026-07-27 14:52:15 +08:00
parent b609a64811
commit f6d61693e3
3 changed files with 82 additions and 28 deletions

View File

@ -9,6 +9,8 @@ except ImportError:
GDAL_AVAILABLE = False GDAL_AVAILABLE = False
print("警告: GDAL未安装将使用numpy处理模式") print("警告: GDAL未安装将使用numpy处理模式")
from src.utils.util import find_band_number
try: try:
from tqdm import tqdm from tqdm import tqdm
TQDM_AVAILABLE = True TQDM_AVAILABLE = True
@ -44,19 +46,35 @@ def _find_water_row_ranges(water_rows):
class Goodman: class Goodman:
def __init__(self, im_aligned, NIR_lower = 25, NIR_upper = 37, A = 0.000019, B = 0.1, def __init__(self, im_aligned,
nir_lower_wavelength=641.93, nir_upper_wavelength=751.49,
A=0.000019, B=0.1,
use_gdal=True, chunk_size=None, water_mask=None, output_path=None): use_gdal=True, chunk_size=None, water_mask=None, output_path=None):
""" """
:param im_aligned (np.ndarray or str): band aligned and calibrated & corrected reflectance image Goodman 耀斑去除算法 — 波长驱动版。
可以是numpy数组或GDAL可读取的文件路径
:param NIR_lower (int): band index which corresponds to 641.93nm, closest band to 640nm ★ P0 修复:原算法类硬编码 NIR_lower=25/NIR_upper=37GUI 硬编码 65/91
:param NIR_upper (int): band index which corresponds to 751.49nm, closest band to 750nm 两套魔法数字对应不同传感器但指向相同物理波长(~640nm / ~750nm)。
:param A (float): the values in Goodman et al's paper, using AVIRIS reflectance (rather than radiance) data 现已统一为波长参数,通过 find_band_number() 动态解析波段号,
:param B (float): the values in Goodman et al's paper, using AVIRIS reflectance (rather than radiance) data 任何传感器均可自动适配,彻底消除不一致。
see Goodman et al, which corrects each pixel independently. The NIR radiance is subtracted from the radiance at each wavelength,
:param im_aligned (np.ndarray or str): band aligned and calibrated & corrected
reflectance image。可以是numpy数组或GDAL可读取的文件路径。
当为文件路径时,通过 HDR 元数据动态解析波段索引;
当为numpy数组时波长参数被视为直接波段索引无 HDR 可查)。
:param nir_lower_wavelength (float): NIR下界目标波长(nm)默认641.93nm
(对应 ~640nm 参考波段,原硬编码 NIR_lower=25/65 指向的物理波长)
:param nir_upper_wavelength (float): NIR上界目标波长(nm)默认751.49nm
(对应 ~750nm 参考波段,原硬编码 NIR_upper=37/91 指向的物理波长)
:param A (float): the values in Goodman et al's paper, using AVIRIS
reflectance (rather than radiance) data
:param B (float): the values in Goodman et al's paper, using AVIRIS
reflectance (rather than radiance) data
see Goodman et al, which corrects each pixel independently. The NIR
radiance is subtracted from the radiance at each wavelength,
but a wavelength-independent offset is also added. but a wavelength-independent offset is also added.
it is not clear how A and B were chosen, but an optimization for a case where in situ data is it is not clear how A and B were chosen, but an optimization for a
available would enable values to be found case where in situ data is available would enable values to be found
:param use_gdal (bool): 是否使用GDAL加速处理需要GDAL可用且输入为文件路径或大数组 :param use_gdal (bool): 是否使用GDAL加速处理需要GDAL可用且输入为文件路径或大数组
:param chunk_size (int): 已废弃,不再使用分块处理,改为逐波段处理 :param chunk_size (int): 已废弃,不再使用分块处理,改为逐波段处理
:param water_mask (np.ndarray or str or None): 水域掩膜1表示水域0表示非水域 :param water_mask (np.ndarray or str or None): 水域掩膜1表示水域0表示非水域
@ -66,8 +84,6 @@ class Goodman:
如果为None则不保存 如果为None则不保存
""" """
self.im_aligned = im_aligned self.im_aligned = im_aligned
self.NIR_lower = NIR_lower
self.NIR_upper = NIR_upper
self.A = A self.A = A
self.B = B self.B = B
self.use_gdal = use_gdal and GDAL_AVAILABLE self.use_gdal = use_gdal and GDAL_AVAILABLE
@ -75,6 +91,22 @@ class Goodman:
self.is_file_path = isinstance(im_aligned, str) self.is_file_path = isinstance(im_aligned, str)
self.output_path = output_path self.output_path = output_path
# ── 波长驱动:通过 HDR 元数据动态解析波段索引 ──
if self.is_file_path:
self.NIR_lower = find_band_number(
float(nir_lower_wavelength), im_aligned)
self.NIR_upper = find_band_number(
float(nir_upper_wavelength), im_aligned)
print(f"[Goodman] 波段解析: "
f"NIR_lower={float(nir_lower_wavelength):.1f}nm→band{self.NIR_lower}, "
f"NIR_upper={float(nir_upper_wavelength):.1f}nm→band{self.NIR_upper}")
else:
# numpy 数组模式:无 HDR 可查,直接作为波段索引(向后兼容)
self.NIR_lower = int(nir_lower_wavelength)
self.NIR_upper = int(nir_upper_wavelength)
print(f"[Goodman] numpy数组模式: 直接使用波段索引 "
f"NIR_lower={self.NIR_lower}, NIR_upper={self.NIR_upper}")
# 获取图像信息(需要在加载掩膜之前获取尺寸) # 获取图像信息(需要在加载掩膜之前获取尺寸)
if self.is_file_path: if self.is_file_path:
if not self.use_gdal: if not self.use_gdal:

View File

@ -7,16 +7,22 @@ try:
except ImportError: except ImportError:
GDAL_AVAILABLE = False GDAL_AVAILABLE = False
from src.utils.util import find_band_number
class Hedley: class Hedley:
def __init__(self, img_path, shp_path=None, NIR_band=47, water_mask=None, def __init__(self, img_path, shp_path=None, nir_wavelength=842.36,
output_path=None, block_size=1000): water_mask=None, output_path=None, block_size=1000):
""" """
Hedley 耀斑去除算法 - 分块逐波段处理版本 Hedley 耀斑去除算法 - 分块逐波段处理版本
波段选择已从硬编码索引改造为波长驱动:
- 传入目标波长(nm),算法通过 find_band_number() 从影像 HDR 元数据中
动态解析最接近的波段号,彻底解除传感器耦合。
:param img_path (str): 输入影像文件路径GDAL可读取的格式 :param img_path (str): 输入影像文件路径GDAL可读取的格式
:param shp_path (str, optional): 深水区域shapefile已废弃请使用water_mask :param shp_path (str, optional): 深水区域shapefile已废弃请使用water_mask
:param NIR_band (int): NIR波段索引默认47对应842.36nm :param nir_wavelength (float): NIR参考波段目标波长(nm),默认842.36nm
:param water_mask (np.ndarray or str or None): 水域掩膜 :param water_mask (np.ndarray or str or None): 水域掩膜
:param output_path (str): 输出文件路径(必须提供,用于分块写入) :param output_path (str): 输出文件路径(必须提供,用于分块写入)
:param block_size (int): 分块大小默认1000 :param block_size (int): 分块大小默认1000
@ -25,7 +31,9 @@ class Hedley:
raise ImportError("GDAL未安装无法读取影像文件") raise ImportError("GDAL未安装无法读取影像文件")
self.img_path = img_path self.img_path = img_path
self.NIR_band = int(float(NIR_band)) # 波长驱动:通过 HDR 元数据动态解析波段索引
self.NIR_band = find_band_number(float(nir_wavelength), img_path)
print(f"[Hedley] 波段解析: NIR={float(nir_wavelength):.1f}nm→band{self.NIR_band}")
self.water_mask = None self.water_mask = None
self.water_mask_path = water_mask self.water_mask_path = water_mask
self.output_path = output_path self.output_path = output_path

View File

@ -7,20 +7,27 @@ try:
except ImportError: except ImportError:
GDAL_AVAILABLE = False GDAL_AVAILABLE = False
from src.utils.util import find_band_number
class Kutser: class Kutser:
def __init__(self, img_path, shp_path=None, oxy_band=38, lower_oxy=36, def __init__(self, img_path, shp_path=None,
upper_oxy=49, NIR_band=47, water_mask=None, output_path=None, oxy_wavelength=760.6, lower_wavelength=742.39,
block_size=1000): upper_wavelength=860.48, nir_wavelength=842.36,
water_mask=None, output_path=None, block_size=1000):
""" """
Kutser 耀斑去除算法 - 分块逐波段处理版本 Kutser 耀斑去除算法 - 分块逐波段处理版本
波段选择已从硬编码索引改造为波长驱动:
- 传入目标波长(nm),算法通过 find_band_number() 从影像 HDR 元数据中
动态解析最接近的波段号,彻底解除传感器耦合。
:param img_path (str): 输入影像文件路径GDAL可读取的格式 :param img_path (str): 输入影像文件路径GDAL可读取的格式
:param shp_path (str, optional): 深水区域shapefile已废弃请使用water_mask :param shp_path (str, optional): 深水区域shapefile已废弃请使用water_mask
:param oxy_band (int): 氧吸收波段索引默认38对应760.6nm :param oxy_wavelength (float): 氧吸收波段目标波长(nm),默认760.6nmO₂-A带
:param lower_oxy (int): 氧吸收下方波段索引默认36对应742.39nm :param lower_wavelength (float): 氧吸收左肩目标波长(nm),默认742.39nm
:param upper_oxy (int): 氧吸收上方波段索引默认49对应860.48nm :param upper_wavelength (float): 氧吸收右肩目标波长(nm),默认860.48nm
:param NIR_band (int): NIR波段索引默认47对应842.36nm :param nir_wavelength (float): NIR参考波段目标波长(nm),默认842.36nm
:param water_mask (np.ndarray or str or None): 水域掩膜 :param water_mask (np.ndarray or str or None): 水域掩膜
:param output_path (str): 输出文件路径(必须提供,用于分块写入) :param output_path (str): 输出文件路径(必须提供,用于分块写入)
:param block_size (int): 分块大小默认1000 :param block_size (int): 分块大小默认1000
@ -29,10 +36,17 @@ class Kutser:
raise ImportError("GDAL未安装无法读取影像文件") raise ImportError("GDAL未安装无法读取影像文件")
self.img_path = img_path self.img_path = img_path
self.oxy_band = int(float(oxy_band)) # 波长驱动:通过 HDR 元数据动态解析波段索引
self.lower_oxy = int(float(lower_oxy)) self.oxy_band = find_band_number(float(oxy_wavelength), img_path)
self.upper_oxy = int(float(upper_oxy)) self.lower_oxy = find_band_number(float(lower_wavelength), img_path)
self.NIR_band = int(float(NIR_band)) self.upper_oxy = find_band_number(float(upper_wavelength), img_path)
self.NIR_band = find_band_number(float(nir_wavelength), img_path)
print(f"[Kutser] 波段解析: "
f"oxy={float(oxy_wavelength):.1f}nm→band{self.oxy_band}, "
f"lower={float(lower_wavelength):.1f}nm→band{self.lower_oxy}, "
f"upper={float(upper_wavelength):.1f}nm→band{self.upper_oxy}, "
f"NIR={float(nir_wavelength):.1f}nm→band{self.NIR_band}")
self.water_mask = None # 延迟加载,在处理前初始化 self.water_mask = None # 延迟加载,在处理前初始化
self.water_mask_path = water_mask self.water_mask_path = water_mask
self.output_path = output_path self.output_path = output_path