feat(step8): 新增水色指数反演模块 waterindex_inversion + CSV 公式驱动架构
This commit is contained in:
22
src/core/algorithms/waterindex_inversion.py
Normal file
22
src/core/algorithms/waterindex_inversion.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
水色指数反演模块(包入口)
|
||||||
|
|
||||||
|
从 waterindex.csv 读取公式,对去耀斑 BSQ 高光谱影像进行全图矩阵运算,
|
||||||
|
输出带完整坐标信息的 GeoTIFF。
|
||||||
|
|
||||||
|
公式格式(waterindex.csv):
|
||||||
|
- 波长占位符:w{nm},如 w686, w708, w665
|
||||||
|
- 支持混合大小写:w686 / W665 均可
|
||||||
|
- 示例:NDCI = (w708 - w665) / (w708 + w665)
|
||||||
|
|
||||||
|
输出:
|
||||||
|
- GeoTIFF (Float32),LZW 压缩,带 Tile
|
||||||
|
- 完整克隆原始 BSQ 的 GeoTransform / Projection / NoData
|
||||||
|
- Step 14 可直接用 rasterio 读取数组和空间范围
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 重新导出 WaterIndexProcessor(向后兼容所有已有 import)
|
||||||
|
from src.core.algorithms.waterindex_inversion import WaterIndexProcessor
|
||||||
|
|
||||||
|
__all__ = ['WaterIndexProcessor']
|
||||||
646
src/core/algorithms/waterindex_inversion/__init__.py
Normal file
646
src/core/algorithms/waterindex_inversion/__init__.py
Normal file
@ -0,0 +1,646 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
水色指数反演模块
|
||||||
|
|
||||||
|
直接读取去耀斑高光谱 BSQ 影像,应用 waterindex.csv 中的公式,
|
||||||
|
输出各水质参数指数的 GeoTIFF 栅格图像。
|
||||||
|
|
||||||
|
公式格式(waterindex.csv):
|
||||||
|
- 波长占位符:w{nm},如 w686, w708, w665
|
||||||
|
- 支持混合大小写:w686 / W665 均可
|
||||||
|
- 示例:NDCI = (w708 - w665) / (w708 + w665)
|
||||||
|
BGA_Am09KBBI = (w686 - w658) / (w686 + w658)
|
||||||
|
|
||||||
|
输出:
|
||||||
|
- GeoTIFF (Float32),LZW 压缩,带 Tile
|
||||||
|
- 完整克隆原始 BSQ 的 GeoTransform / Projection / NoData
|
||||||
|
- Step 14 可直接用 rasterio 读取进行克里金插值
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from osgeo import gdal, osr
|
||||||
|
|
||||||
|
# GDAL 驱动注册
|
||||||
|
gdal.UseExceptions()
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 公共工具
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _get_resource_path(relative_path: str) -> str:
|
||||||
|
"""获取 waterindex.csv 等资源的绝对路径,兼容 PyInstaller 打包。"""
|
||||||
|
if hasattr(sys, '_MEIPASS'):
|
||||||
|
base = sys._MEIPASS
|
||||||
|
else:
|
||||||
|
base = os.path.abspath(
|
||||||
|
os.path.join(os.path.dirname(os.path.dirname(__file__)), '..', '..', '..')
|
||||||
|
)
|
||||||
|
return os.path.join(base, relative_path)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# WaterIndexProcessor
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
class WaterIndexProcessor:
|
||||||
|
"""
|
||||||
|
水色指数处理器
|
||||||
|
|
||||||
|
读取 waterindex.csv 中的公式,应用于 BSQ 高光谱影像,
|
||||||
|
输出带完整坐标信息的 GeoTIFF 指数图。
|
||||||
|
|
||||||
|
核心能力:
|
||||||
|
- 公式解析:w{nm} 占位符 → 实际波段 2D numpy 数组
|
||||||
|
- 矩阵运算:全影像批量计算,无需逐点循环
|
||||||
|
- 地理信息保持:克隆原始 BSQ 的 GeoTransform / Projection
|
||||||
|
- NoData 处理:运算中产生的 NaN/Inf 统一标记为 -9999
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 内置安全命名空间(公式 eval 白名单)
|
||||||
|
_SAFE_NS: Dict[str, Any] = {
|
||||||
|
'np': np,
|
||||||
|
'nan': np.nan,
|
||||||
|
'inf': np.inf,
|
||||||
|
'pi': np.pi,
|
||||||
|
'e': np.e,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, waterindex_csv_path: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
waterindex_csv_path : str, optional
|
||||||
|
waterindex.csv 路径。
|
||||||
|
若为 None,尝试从默认位置加载:
|
||||||
|
1. src/gui/model/waterindex.csv(开发环境)
|
||||||
|
2. _MEIPASS/src/gui/model/waterindex.csv(打包环境)
|
||||||
|
"""
|
||||||
|
self.csv_path: Optional[str] = None
|
||||||
|
self.formulas: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
if waterindex_csv_path:
|
||||||
|
self.csv_path = waterindex_csv_path
|
||||||
|
else:
|
||||||
|
candidates = [
|
||||||
|
os.path.join(os.path.dirname(__file__), '..', '..', 'gui', 'model', 'waterindex.csv'),
|
||||||
|
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'gui', 'model', 'waterindex.csv'),
|
||||||
|
]
|
||||||
|
for p in candidates:
|
||||||
|
if os.path.isfile(p):
|
||||||
|
self.csv_path = p
|
||||||
|
break
|
||||||
|
|
||||||
|
if self.csv_path:
|
||||||
|
self._parse_csv()
|
||||||
|
else:
|
||||||
|
self.formulas = []
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 公式加载
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _parse_csv(self) -> None:
|
||||||
|
"""解析 waterindex.csv,加载所有公式。"""
|
||||||
|
if not os.path.isfile(self.csv_path):
|
||||||
|
raise FileNotFoundError(f"公式配置文件不存在: {self.csv_path}")
|
||||||
|
|
||||||
|
# ★★★ 防止多次调用时公式翻倍叠加 ★★★
|
||||||
|
self.formulas.clear()
|
||||||
|
|
||||||
|
with open(self.csv_path, 'r', encoding='utf-8-sig') as f:
|
||||||
|
reader = csv.DictReader(f)
|
||||||
|
for row in reader:
|
||||||
|
self.formulas.append(dict(row))
|
||||||
|
|
||||||
|
print(f"[WaterIndexProcessor] 加载 {len(self.formulas)} 条公式 ← {self.csv_path}")
|
||||||
|
|
||||||
|
def reload(self, waterindex_csv_path: str) -> None:
|
||||||
|
"""重新加载公式配置文件。"""
|
||||||
|
self.csv_path = waterindex_csv_path
|
||||||
|
self._parse_csv()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 公式查询
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def list_formulas(self) -> List[Dict[str, Any]]:
|
||||||
|
"""返回所有公式的列表。"""
|
||||||
|
return list(self.formulas)
|
||||||
|
|
||||||
|
def list_formula_names(self) -> List[str]:
|
||||||
|
"""返回所有公式名称列表。"""
|
||||||
|
return [f.get('Formula_Name', '') for f in self.formulas]
|
||||||
|
|
||||||
|
def get_formula(self, name: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""按名称查找单个公式。"""
|
||||||
|
for f in self.formulas:
|
||||||
|
if f.get('Formula_Name', '').strip() == name.strip():
|
||||||
|
return f
|
||||||
|
return None
|
||||||
|
|
||||||
|
def list_categories(self) -> List[str]:
|
||||||
|
"""返回所有公式类别(去重排序)。"""
|
||||||
|
cats = set()
|
||||||
|
for f in self.formulas:
|
||||||
|
c = f.get('Category', '').strip()
|
||||||
|
if c:
|
||||||
|
cats.add(c)
|
||||||
|
return sorted(cats)
|
||||||
|
|
||||||
|
def get_formulas_by_category(self, category: str) -> List[Dict[str, Any]]:
|
||||||
|
"""按类别筛选公式。"""
|
||||||
|
return [f for f in self.formulas
|
||||||
|
if f.get('Category', '').strip().lower() == category.strip().lower()]
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 影像元数据
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_image_metadata(self, bsq_path: str, hdr_path: Optional[str] = None) -> Dict[str, Any]:
|
||||||
|
"""获取影像元数据(GDAL + ENVI HDR 双重保障)。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
bsq_path : str
|
||||||
|
BSQ 影像路径
|
||||||
|
hdr_path : str, optional
|
||||||
|
ENVI HDR 路径(None → 自动构造)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict
|
||||||
|
含 keys: width, height, bands, wavelengths, wavelength_range,
|
||||||
|
geotransform, projection, driver
|
||||||
|
"""
|
||||||
|
meta: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
# 1. GDAL 优先(获取空间信息)
|
||||||
|
try:
|
||||||
|
ds = gdal.Open(bsq_path, gdal.GA_ReadOnly)
|
||||||
|
if ds is not None:
|
||||||
|
meta['width'] = ds.RasterXSize
|
||||||
|
meta['height'] = ds.RasterYSize
|
||||||
|
meta['bands'] = ds.RasterCount
|
||||||
|
meta['driver'] = ds.GetDriver().ShortName
|
||||||
|
gt = ds.GetGeoTransform()
|
||||||
|
proj = ds.GetProjection()
|
||||||
|
if gt and gt != (0, 1, 0, 0, 0, 1):
|
||||||
|
meta['geotransform'] = gt
|
||||||
|
if proj:
|
||||||
|
meta['projection'] = proj
|
||||||
|
ds = None
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 2. HDR 补充波长信息
|
||||||
|
if hdr_path is None:
|
||||||
|
hdr_path = os.path.splitext(bsq_path)[0] + '.hdr'
|
||||||
|
if not os.path.isfile(hdr_path):
|
||||||
|
hdr_path_alt = os.path.splitext(bsq_path)[0] + '.HDR'
|
||||||
|
if os.path.isfile(hdr_path_alt):
|
||||||
|
hdr_path = hdr_path_alt
|
||||||
|
|
||||||
|
if os.path.isfile(hdr_path):
|
||||||
|
wl = self._parse_wavelengths_from_hdr(hdr_path)
|
||||||
|
if wl:
|
||||||
|
meta['wavelengths'] = wl
|
||||||
|
if len(wl) >= 2:
|
||||||
|
meta['wavelength_range'] = f"{wl[0]:.1f}–{wl[-1]:.1f} nm ({len(wl)} 波段)"
|
||||||
|
elif meta.get('bands', 0) > 0:
|
||||||
|
meta['wavelength_range'] = f"{meta['bands']} 波段(波长信息缺失)"
|
||||||
|
|
||||||
|
return meta
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_wavelengths_from_hdr(hdr_path: str) -> Optional[List[float]]:
|
||||||
|
"""从 ENVI .hdr 文件中解析波长列表。"""
|
||||||
|
try:
|
||||||
|
with open(hdr_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# 格式1:wavelength = { 400, 401, ... }
|
||||||
|
m = re.search(r'wavelength\s*=\s*\{([^}]+)\}', content, re.DOTALL)
|
||||||
|
if m:
|
||||||
|
vals = [float(v) for v in re.findall(r'[\d.]+', m.group(1)) if v.strip()]
|
||||||
|
if vals:
|
||||||
|
return vals
|
||||||
|
|
||||||
|
# 格式2:逐行罗列
|
||||||
|
wavelengths: List[float] = []
|
||||||
|
in_wl = False
|
||||||
|
for line in content.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith('wavelength'):
|
||||||
|
in_wl = True
|
||||||
|
continue
|
||||||
|
if in_wl:
|
||||||
|
if line.startswith('{'):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
wavelengths.append(float(line))
|
||||||
|
except ValueError:
|
||||||
|
if '}' in line:
|
||||||
|
in_wl = False
|
||||||
|
return wavelengths if wavelengths else None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 公式解析:w{nm} 占位符 → 实际波段数据
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _find_nearest_band_index(self, target_wv: float,
|
||||||
|
wavelengths: List[float]) -> int:
|
||||||
|
"""找到最接近目标波长的 GDAL 波段索引(1-based)。"""
|
||||||
|
if not wavelengths:
|
||||||
|
raise ValueError("波长列表为空,无法匹配波段")
|
||||||
|
nearest = min(range(len(wavelengths)),
|
||||||
|
key=lambda i: abs(wavelengths[i] - target_wv))
|
||||||
|
return nearest + 1 # GDAL 波段从 1 开始
|
||||||
|
|
||||||
|
def _parse_formula_wavelengths(self, formula: str) -> List[int]:
|
||||||
|
"""从公式字符串中提取所有波长值(去重,int)。"""
|
||||||
|
raw = re.findall(r'[wW](\d+)', formula)
|
||||||
|
seen = set()
|
||||||
|
result: List[int] = []
|
||||||
|
for r in raw:
|
||||||
|
v = int(r)
|
||||||
|
if v not in seen:
|
||||||
|
seen.add(v)
|
||||||
|
result.append(v)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _eval_formula_fast(self, formula: str,
|
||||||
|
band_data: Dict[int, np.ndarray]) -> Optional[np.ndarray]:
|
||||||
|
"""快速公式求值(预处理后直接 eval)。
|
||||||
|
|
||||||
|
band_data: {波长int: 2D 数组}
|
||||||
|
formula 示例: "(w708 - w665) / (w708 + w665)"
|
||||||
|
"""
|
||||||
|
# 预处理:w708 → _B708(避免与 Python 关键字冲突)
|
||||||
|
processed = re.sub(r'[wW](\d+)', r'_B\1', formula)
|
||||||
|
|
||||||
|
# 构建局部变量表:_B708 = band_data[708]
|
||||||
|
local_vars = {f"_B{wv}": arr for wv, arr in band_data.items()}
|
||||||
|
local_vars.update(self._SAFE_NS)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = eval(processed, {"__builtins__": {}}, local_vars)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠ 公式求值失败 [{formula}]: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 单波段读取(带 NoData 处理)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _read_band_as_float(bsq_path: str, band_idx: int) -> np.ndarray:
|
||||||
|
"""读取 BSQ 指定波段(1-based),返回 float64,NaN 替换 NoData。"""
|
||||||
|
ds = gdal.Open(bsq_path, gdal.GA_ReadOnly)
|
||||||
|
if ds is None:
|
||||||
|
raise RuntimeError(f"无法用 GDAL 打开影像: {bsq_path}")
|
||||||
|
|
||||||
|
band = ds.GetRasterBand(band_idx)
|
||||||
|
arr = band.ReadAsArray()
|
||||||
|
nodata = band.GetNoDataValue()
|
||||||
|
ds = None
|
||||||
|
|
||||||
|
arr = arr.astype(np.float64)
|
||||||
|
if nodata is not None:
|
||||||
|
arr = np.where(arr == nodata, np.nan, arr)
|
||||||
|
|
||||||
|
return arr
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 核心处理:逐公式矩阵运算 + GeoTIFF 输出
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def process_bsq(
|
||||||
|
self,
|
||||||
|
bsq_path: str,
|
||||||
|
hdr_path: Optional[str] = None,
|
||||||
|
output_dir: Optional[str] = None,
|
||||||
|
formula_names: Optional[List[str]] = None,
|
||||||
|
water_mask: Optional[np.ndarray] = None,
|
||||||
|
nodata_value: float = -9999.0,
|
||||||
|
progress_callback: Optional[Callable[[str, float], None]] = None,
|
||||||
|
) -> Dict[str, str]:
|
||||||
|
"""逐公式处理 BSQ 影像,输出 GeoTIFF。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
bsq_path : str
|
||||||
|
去耀斑 BSQ 影像路径
|
||||||
|
hdr_path : str, optional
|
||||||
|
ENVI HDR 文件路径(None → 自动构造)
|
||||||
|
output_dir : str, optional
|
||||||
|
输出目录(None → 与 bsq_path 同目录下的 8_WaterIndex_Images/)
|
||||||
|
formula_names : list, optional
|
||||||
|
要处理的公式名列表(None → 处理全部)
|
||||||
|
water_mask : np.ndarray, optional
|
||||||
|
水域掩膜数组(与 BSQ 同形状),掩膜值为 0 表示陆地,
|
||||||
|
将被强制赋值为 nodata_value
|
||||||
|
nodata_value : float
|
||||||
|
NoData 标记值
|
||||||
|
progress_callback : callable, optional
|
||||||
|
回调 (msg: str, pct: float)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict
|
||||||
|
{公式名: 输出 GeoTIFF 路径}
|
||||||
|
"""
|
||||||
|
# ── 自动构造 HDR 路径 ────────────────────────────────────────────
|
||||||
|
if hdr_path is None:
|
||||||
|
hdr_path = os.path.splitext(bsq_path)[0] + '.hdr'
|
||||||
|
if not os.path.isfile(hdr_path):
|
||||||
|
hdr_path_alt = os.path.splitext(bsq_path)[0] + '.HDR'
|
||||||
|
if os.path.isfile(hdr_path_alt):
|
||||||
|
hdr_path = hdr_path_alt
|
||||||
|
|
||||||
|
# ── 自动构造输出目录 ────────────────────────────────────────────
|
||||||
|
if output_dir is None:
|
||||||
|
output_dir = os.path.join(os.path.dirname(bsq_path), '8_WaterIndex_Images')
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
def progress(msg: str, pct: float):
|
||||||
|
if progress_callback:
|
||||||
|
progress_callback(msg, pct)
|
||||||
|
|
||||||
|
# ── 获取影像元数据 ───────────────────────────────────────────────
|
||||||
|
progress("正在打开影像并读取元数据…", 2)
|
||||||
|
meta = self.get_image_metadata(bsq_path, hdr_path)
|
||||||
|
|
||||||
|
width = meta.get('width', 0)
|
||||||
|
height = meta.get('height', 0)
|
||||||
|
n_bands = meta.get('bands', 0)
|
||||||
|
wavelengths = meta.get('wavelengths', [])
|
||||||
|
geotransform = meta.get('geotransform')
|
||||||
|
projection = meta.get('projection')
|
||||||
|
|
||||||
|
if n_bands == 0 or width == 0 or height == 0:
|
||||||
|
raise ValueError(f"影像元数据无效,无法处理: {bsq_path}")
|
||||||
|
|
||||||
|
if not wavelengths:
|
||||||
|
raise ValueError(f"无法从 {hdr_path} 读取波长信息,公式无法解析")
|
||||||
|
|
||||||
|
progress(
|
||||||
|
f"影像: {width}×{height}像素, {n_bands}波段, "
|
||||||
|
f"波长 {wavelengths[0]:.1f}–{wavelengths[-1]:.1f}nm",
|
||||||
|
5
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── 过滤要处理的公式 ──────────────────────────────────────────────
|
||||||
|
if formula_names:
|
||||||
|
formulas_to_run = [
|
||||||
|
f for f in self.formulas
|
||||||
|
if f.get('Formula_Name', '').strip() in formula_names
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
formulas_to_run = list(self.formulas)
|
||||||
|
|
||||||
|
results: Dict[str, str] = {}
|
||||||
|
total = len(formulas_to_run)
|
||||||
|
|
||||||
|
# ── 逐公式处理 ───────────────────────────────────────────────────
|
||||||
|
for i, formula_row in enumerate(formulas_to_run):
|
||||||
|
fname = formula_row.get('Formula_Name', '').strip()
|
||||||
|
fstr = formula_row.get('Formula', '').strip()
|
||||||
|
category = formula_row.get('Category', '').strip()
|
||||||
|
ftype = formula_row.get('Formula_Type', '').strip()
|
||||||
|
|
||||||
|
if not fname or not fstr:
|
||||||
|
continue
|
||||||
|
|
||||||
|
progress(
|
||||||
|
f"[{i + 1}/{total}] {fname} ({category})",
|
||||||
|
5 + 90 * i / total
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1) 提取公式所需的波长列表
|
||||||
|
required_wvs = self._parse_formula_wavelengths(fstr)
|
||||||
|
|
||||||
|
# 2) 按需读取波段数据(相同波长只读一次)
|
||||||
|
band_data: Dict[int, np.ndarray] = {}
|
||||||
|
for wv in required_wvs:
|
||||||
|
if wv not in band_data:
|
||||||
|
band_idx = self._find_nearest_band_index(wv, wavelengths)
|
||||||
|
if not (0 < band_idx <= n_bands):
|
||||||
|
print(f" ⚠ 公式 '{fname}' 引用波段 {band_idx},超出范围 ({n_bands}),跳过")
|
||||||
|
raise ValueError(f"波段 {band_idx} 超出影像范围")
|
||||||
|
band_data[wv] = self._read_band_as_float(bsq_path, band_idx)
|
||||||
|
|
||||||
|
# 3) 矩阵运算
|
||||||
|
index_arr = self._eval_formula_fast(fstr, band_data)
|
||||||
|
if index_arr is None:
|
||||||
|
print(f" ⚠ 公式 '{fname}' 计算失败,跳过")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 4) NoData 处理:NaN / Inf → nodata_value
|
||||||
|
index_arr = np.where(np.isfinite(index_arr), index_arr, nodata_value)
|
||||||
|
|
||||||
|
# 4b) 水域掩膜拦截:陆地像素(mask==0)强制赋 NoData
|
||||||
|
if water_mask is not None:
|
||||||
|
land_pixels = (water_mask == 0)
|
||||||
|
land_count = int(land_pixels.sum())
|
||||||
|
if land_count > 0:
|
||||||
|
index_arr = np.where(land_pixels, nodata_value, index_arr)
|
||||||
|
print(f" 🗺 掩膜处理:陆地像素 {land_count:,} 个已设为 NoData")
|
||||||
|
|
||||||
|
# 5) 输出 GeoTIFF
|
||||||
|
safe_fname = re.sub(r'[^\w\u4e00-\u9fff-]', '_', fname)
|
||||||
|
out_tif = os.path.join(output_dir, f"{safe_fname}.tif")
|
||||||
|
|
||||||
|
self._write_geotiff(
|
||||||
|
out_path=out_tif,
|
||||||
|
data=index_arr,
|
||||||
|
reference_bsq=bsq_path,
|
||||||
|
nodata_value=nodata_value,
|
||||||
|
description=f"{fname}|{category}|{ftype}|{fstr}",
|
||||||
|
)
|
||||||
|
|
||||||
|
results[fname] = out_tif
|
||||||
|
valid = index_arr[index_arr != nodata_value]
|
||||||
|
mean_val = float(np.mean(valid)) if valid.size else np.nan
|
||||||
|
print(f" ✅ {fname} → {out_tif} (mean={mean_val:.4f})")
|
||||||
|
|
||||||
|
except ValueError as ve:
|
||||||
|
print(f" ⏭ 跳过 '{fname}': {ve}")
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ 公式 '{fname}' 失败: {e}\n{traceback.format_exc()}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
progress(f"完成!共输出 {len(results)} / {total} 个指数图", 100)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _write_geotiff(
|
||||||
|
self,
|
||||||
|
out_path: str,
|
||||||
|
data: np.ndarray,
|
||||||
|
reference_bsq: str,
|
||||||
|
nodata_value: float = -9999.0,
|
||||||
|
description: str = "",
|
||||||
|
) -> None:
|
||||||
|
"""将数组写入 GeoTIFF,克隆原始 BSQ 的地理信息。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
out_path : str
|
||||||
|
输出 GeoTIFF 路径
|
||||||
|
data : np.ndarray
|
||||||
|
2D 数据数组(height, width)
|
||||||
|
reference_bsq : str
|
||||||
|
参考 BSQ 影像路径(用于克隆 GeoTransform / Projection)
|
||||||
|
nodata_value : float
|
||||||
|
NoData 标记值
|
||||||
|
description : str
|
||||||
|
GDAL 数据集描述
|
||||||
|
"""
|
||||||
|
height, width = data.shape
|
||||||
|
|
||||||
|
driver = gdal.GetDriverByName('GTiff')
|
||||||
|
if driver is None:
|
||||||
|
raise RuntimeError("GDAL GTiff 驱动不可用")
|
||||||
|
|
||||||
|
out_ds = driver.Create(
|
||||||
|
out_path,
|
||||||
|
width, height,
|
||||||
|
1,
|
||||||
|
gdal.GDT_Float32,
|
||||||
|
options=['COMPRESS=LZW', 'TILED=YES', 'BIGTIFF=IF_SAFER'],
|
||||||
|
)
|
||||||
|
if out_ds is None:
|
||||||
|
raise RuntimeError(f"无法创建 GeoTIFF: {out_path}")
|
||||||
|
|
||||||
|
# 写入数据
|
||||||
|
out_band = out_ds.GetRasterBand(1)
|
||||||
|
out_band.SetNoDataValue(nodata_value)
|
||||||
|
out_band.WriteArray(data)
|
||||||
|
out_band.FlushCache()
|
||||||
|
|
||||||
|
# 写入描述
|
||||||
|
if description:
|
||||||
|
out_band.SetDescription(description)
|
||||||
|
|
||||||
|
# ★★★ 克隆原始 BSQ 的 GeoTransform 和 Projection ★★★
|
||||||
|
ref_ds = gdal.Open(reference_bsq, gdal.GA_ReadOnly)
|
||||||
|
if ref_ds is not None:
|
||||||
|
gt = ref_ds.GetGeoTransform()
|
||||||
|
proj = ref_ds.GetProjection()
|
||||||
|
if gt and gt != (0, 1, 0, 0, 0, 1):
|
||||||
|
out_ds.SetGeoTransform(gt)
|
||||||
|
if proj:
|
||||||
|
out_ds.SetProjection(proj)
|
||||||
|
ref_ds = None
|
||||||
|
|
||||||
|
out_ds = None
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Pipeline 入口(供 PipelineRunner 调用)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def run_inversion(
|
||||||
|
self,
|
||||||
|
deglint_img_path: str,
|
||||||
|
work_dir: str,
|
||||||
|
formula_csv_path: Optional[str] = None,
|
||||||
|
selected_formulas: Optional[List[str]] = None,
|
||||||
|
water_mask_path: Optional[str] = None,
|
||||||
|
nodata_value: float = -9999.0,
|
||||||
|
callback: Optional[Callable] = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> Dict[str, str]:
|
||||||
|
"""Pipeline 入口方法。
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
deglint_img_path : str
|
||||||
|
去耀斑影像 BSQ 路径
|
||||||
|
work_dir : str
|
||||||
|
工作目录
|
||||||
|
formula_csv_path : str, optional
|
||||||
|
waterindex.csv 路径(None → 使用初始化时的路径)
|
||||||
|
selected_formulas : list, optional
|
||||||
|
要处理的公式列表
|
||||||
|
water_mask_path : str, optional
|
||||||
|
水域掩膜路径(如 1_water_mask/water_mask.dat),
|
||||||
|
掩膜中为 0 的像素视为陆地区域,其指数值将被强制设为 NoData。
|
||||||
|
nodata_value : float
|
||||||
|
NoData 标记值,默认 -9999.0
|
||||||
|
callback : callable, optional
|
||||||
|
进度回调
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict
|
||||||
|
{公式名: 输出 GeoTIFF 路径}
|
||||||
|
"""
|
||||||
|
# 重新加载公式(如指定了新路径)
|
||||||
|
if formula_csv_path:
|
||||||
|
self.reload(formula_csv_path)
|
||||||
|
elif not self.formulas:
|
||||||
|
raise RuntimeError("WaterIndexProcessor 未加载公式,请指定 formula_csv_path")
|
||||||
|
|
||||||
|
def notify(msg: str, pct: float):
|
||||||
|
if callback:
|
||||||
|
callback(msg, pct)
|
||||||
|
|
||||||
|
notify("开始水色指数反演", 0)
|
||||||
|
|
||||||
|
bsq_path = deglint_img_path
|
||||||
|
hdr_path = os.path.splitext(bsq_path)[0] + '.hdr'
|
||||||
|
if not os.path.isfile(hdr_path):
|
||||||
|
hdr_path_alt = os.path.splitext(bsq_path)[0] + '.HDR'
|
||||||
|
if os.path.isfile(hdr_path_alt):
|
||||||
|
hdr_path = hdr_path_alt
|
||||||
|
|
||||||
|
output_dir = os.path.join(work_dir, "8_WaterIndex_Images")
|
||||||
|
|
||||||
|
# ── 加载水域掩膜(可选)───────────────────────────────────────
|
||||||
|
water_mask: Optional[np.ndarray] = None
|
||||||
|
if water_mask_path:
|
||||||
|
if os.path.isfile(water_mask_path):
|
||||||
|
try:
|
||||||
|
import rasterio
|
||||||
|
with rasterio.open(water_mask_path) as msrc:
|
||||||
|
water_mask = msrc.read(1)
|
||||||
|
print(f"[run_inversion] 水域掩膜已加载: {water_mask_path},"
|
||||||
|
f"形状={water_mask.shape},"
|
||||||
|
f"陆地区域(0)={int((water_mask == 0).sum())},"
|
||||||
|
f"水区域(>0)={int((water_mask > 0).sum())}")
|
||||||
|
except Exception as mask_err:
|
||||||
|
print(f"[run_inversion] ⚠ 掩膜加载失败,跳过掩膜处理: {mask_err}")
|
||||||
|
water_mask = None
|
||||||
|
else:
|
||||||
|
print(f"[run_inversion] ⚠ 水域掩膜文件不存在: {water_mask_path},跳过掩膜处理")
|
||||||
|
|
||||||
|
notify("水色指数处理中…", 20)
|
||||||
|
|
||||||
|
results = self.process_bsq(
|
||||||
|
bsq_path=bsq_path,
|
||||||
|
hdr_path=hdr_path,
|
||||||
|
output_dir=output_dir,
|
||||||
|
formula_names=selected_formulas,
|
||||||
|
water_mask=water_mask,
|
||||||
|
nodata_value=nodata_value,
|
||||||
|
progress_callback=lambda m, p: notify(m, 20 + 70 * p / 100),
|
||||||
|
)
|
||||||
|
|
||||||
|
notify("水色指数反演完成", 100)
|
||||||
|
return results
|
||||||
34
src/utils/generate_pure_water_csv.py
Normal file
34
src/utils/generate_pure_water_csv.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
生成纯水 IOPs(吸收与散射系数)插值表
|
||||||
|
参考 Pope & Fry (1997) 吸收系数 + Morel & Luukko (1974) 后向散射系数
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def generate_csv():
|
||||||
|
anchor_points = {
|
||||||
|
400: [0.017, 0.0076], 440: [0.015, 0.0049], 500: [0.026, 0.0029],
|
||||||
|
555: [0.060, 0.0019], 600: [0.245, 0.0014], 650: [0.340, 0.0010],
|
||||||
|
709: [0.735, 0.0007], 750: [2.470, 0.0005], 800: [2.070, 0.0004]
|
||||||
|
}
|
||||||
|
wl_anchors = list(anchor_points.keys())
|
||||||
|
aw_anchors = [v[0] for v in anchor_points.values()]
|
||||||
|
bbw_anchors = [v[1] for v in anchor_points.values()]
|
||||||
|
|
||||||
|
wavelengths = np.arange(400, 801, 1)
|
||||||
|
aw_interp = np.interp(wavelengths, wl_anchors, aw_anchors)
|
||||||
|
bbw_interp = np.interp(wavelengths, wl_anchors, bbw_anchors)
|
||||||
|
|
||||||
|
df = pd.DataFrame({'Wavelength': wavelengths, 'aw': aw_interp, 'bbw': bbw_interp})
|
||||||
|
save_path = os.path.join(os.path.dirname(__file__), 'pure_water_iops.csv')
|
||||||
|
df.to_csv(save_path, index=False)
|
||||||
|
print(f"纯水 IOPs 表已生成: {save_path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
generate_csv()
|
||||||
402
src/utils/pure_water_iops.csv
Normal file
402
src/utils/pure_water_iops.csv
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
Wavelength,aw,bbw
|
||||||
|
400,0.017,0.0076
|
||||||
|
401,0.01695,0.0075325
|
||||||
|
402,0.016900000000000002,0.007465
|
||||||
|
403,0.01685,0.0073974999999999996
|
||||||
|
404,0.016800000000000002,0.00733
|
||||||
|
405,0.01675,0.0072625
|
||||||
|
406,0.0167,0.007195
|
||||||
|
407,0.01665,0.0071275
|
||||||
|
408,0.0166,0.00706
|
||||||
|
409,0.016550000000000002,0.0069925000000000005
|
||||||
|
410,0.0165,0.006925
|
||||||
|
411,0.01645,0.0068575
|
||||||
|
412,0.0164,0.00679
|
||||||
|
413,0.01635,0.0067225
|
||||||
|
414,0.016300000000000002,0.006655
|
||||||
|
415,0.01625,0.0065875
|
||||||
|
416,0.0162,0.00652
|
||||||
|
417,0.01615,0.0064525
|
||||||
|
418,0.0161,0.006385
|
||||||
|
419,0.016050000000000002,0.0063175
|
||||||
|
420,0.016,0.00625
|
||||||
|
421,0.01595,0.0061825000000000005
|
||||||
|
422,0.0159,0.006115
|
||||||
|
423,0.01585,0.0060475
|
||||||
|
424,0.0158,0.00598
|
||||||
|
425,0.01575,0.0059125
|
||||||
|
426,0.0157,0.0058449999999999995
|
||||||
|
427,0.01565,0.0057775
|
||||||
|
428,0.0156,0.00571
|
||||||
|
429,0.01555,0.0056425
|
||||||
|
430,0.0155,0.005575
|
||||||
|
431,0.01545,0.0055075
|
||||||
|
432,0.0154,0.00544
|
||||||
|
433,0.015349999999999999,0.0053725000000000005
|
||||||
|
434,0.0153,0.005305
|
||||||
|
435,0.01525,0.0052375
|
||||||
|
436,0.0152,0.00517
|
||||||
|
437,0.01515,0.005102499999999999
|
||||||
|
438,0.015099999999999999,0.0050349999999999995
|
||||||
|
439,0.01505,0.0049675
|
||||||
|
440,0.015,0.0049
|
||||||
|
441,0.015183333333333333,0.004866666666666667
|
||||||
|
442,0.015366666666666666,0.004833333333333334
|
||||||
|
443,0.01555,0.0048
|
||||||
|
444,0.015733333333333332,0.0047666666666666664
|
||||||
|
445,0.015916666666666666,0.004733333333333333
|
||||||
|
446,0.0161,0.0047
|
||||||
|
447,0.016283333333333334,0.004666666666666666
|
||||||
|
448,0.016466666666666668,0.004633333333333333
|
||||||
|
449,0.016649999999999998,0.0046
|
||||||
|
450,0.016833333333333332,0.004566666666666667
|
||||||
|
451,0.017016666666666666,0.004533333333333333
|
||||||
|
452,0.0172,0.0045
|
||||||
|
453,0.017383333333333334,0.0044666666666666665
|
||||||
|
454,0.017566666666666668,0.004433333333333333
|
||||||
|
455,0.01775,0.004399999999999999
|
||||||
|
456,0.017933333333333332,0.004366666666666666
|
||||||
|
457,0.018116666666666666,0.004333333333333333
|
||||||
|
458,0.0183,0.0043
|
||||||
|
459,0.018483333333333334,0.004266666666666667
|
||||||
|
460,0.018666666666666665,0.004233333333333333
|
||||||
|
461,0.01885,0.0042
|
||||||
|
462,0.019033333333333333,0.004166666666666667
|
||||||
|
463,0.019216666666666667,0.0041333333333333335
|
||||||
|
464,0.0194,0.0040999999999999995
|
||||||
|
465,0.019583333333333335,0.004066666666666666
|
||||||
|
466,0.019766666666666665,0.004033333333333333
|
||||||
|
467,0.01995,0.004
|
||||||
|
468,0.020133333333333333,0.003966666666666667
|
||||||
|
469,0.020316666666666667,0.003933333333333333
|
||||||
|
470,0.020499999999999997,0.0039
|
||||||
|
471,0.02068333333333333,0.0038666666666666667
|
||||||
|
472,0.020866666666666665,0.003833333333333333
|
||||||
|
473,0.02105,0.0037999999999999996
|
||||||
|
474,0.021233333333333333,0.0037666666666666664
|
||||||
|
475,0.021416666666666667,0.0037333333333333333
|
||||||
|
476,0.0216,0.0036999999999999997
|
||||||
|
477,0.02178333333333333,0.003666666666666666
|
||||||
|
478,0.021966666666666666,0.003633333333333333
|
||||||
|
479,0.02215,0.0036
|
||||||
|
480,0.022333333333333334,0.0035666666666666663
|
||||||
|
481,0.022516666666666667,0.003533333333333333
|
||||||
|
482,0.022699999999999998,0.0034999999999999996
|
||||||
|
483,0.022883333333333332,0.0034666666666666665
|
||||||
|
484,0.023066666666666666,0.0034333333333333334
|
||||||
|
485,0.02325,0.0034
|
||||||
|
486,0.023433333333333334,0.0033666666666666662
|
||||||
|
487,0.023616666666666668,0.003333333333333333
|
||||||
|
488,0.0238,0.0033
|
||||||
|
489,0.023983333333333332,0.0032666666666666664
|
||||||
|
490,0.024166666666666666,0.003233333333333333
|
||||||
|
491,0.02435,0.0031999999999999997
|
||||||
|
492,0.02453333333333333,0.0031666666666666666
|
||||||
|
493,0.024716666666666665,0.003133333333333333
|
||||||
|
494,0.0249,0.0030999999999999995
|
||||||
|
495,0.025083333333333332,0.0030666666666666663
|
||||||
|
496,0.025266666666666666,0.003033333333333333
|
||||||
|
497,0.02545,0.003
|
||||||
|
498,0.025633333333333334,0.0029666666666666665
|
||||||
|
499,0.02581666666666667,0.002933333333333333
|
||||||
|
500,0.026,0.0029
|
||||||
|
501,0.02661818181818182,0.0028818181818181816
|
||||||
|
502,0.027236363636363635,0.0028636363636363633
|
||||||
|
503,0.027854545454545455,0.002845454545454545
|
||||||
|
504,0.02847272727272727,0.002827272727272727
|
||||||
|
505,0.02909090909090909,0.002809090909090909
|
||||||
|
506,0.029709090909090907,0.002790909090909091
|
||||||
|
507,0.030327272727272727,0.0027727272727272726
|
||||||
|
508,0.030945454545454543,0.0027545454545454544
|
||||||
|
509,0.03156363636363636,0.002736363636363636
|
||||||
|
510,0.03218181818181818,0.002718181818181818
|
||||||
|
511,0.032799999999999996,0.0026999999999999997
|
||||||
|
512,0.03341818181818182,0.0026818181818181815
|
||||||
|
513,0.034036363636363635,0.0026636363636363637
|
||||||
|
514,0.03465454545454545,0.0026454545454545455
|
||||||
|
515,0.035272727272727275,0.0026272727272727272
|
||||||
|
516,0.03589090909090909,0.002609090909090909
|
||||||
|
517,0.03650909090909091,0.0025909090909090908
|
||||||
|
518,0.037127272727272724,0.0025727272727272725
|
||||||
|
519,0.03774545454545454,0.0025545454545454543
|
||||||
|
520,0.038363636363636364,0.002536363636363636
|
||||||
|
521,0.03898181818181818,0.002518181818181818
|
||||||
|
522,0.039599999999999996,0.0025
|
||||||
|
523,0.04021818181818182,0.002481818181818182
|
||||||
|
524,0.040836363636363636,0.0024636363636363636
|
||||||
|
525,0.04145454545454545,0.0024454545454545454
|
||||||
|
526,0.042072727272727276,0.002427272727272727
|
||||||
|
527,0.042690909090909085,0.002409090909090909
|
||||||
|
528,0.04330909090909091,0.0023909090909090907
|
||||||
|
529,0.043927272727272725,0.002372727272727273
|
||||||
|
530,0.04454545454545454,0.0023545454545454546
|
||||||
|
531,0.045163636363636364,0.0023363636363636364
|
||||||
|
532,0.04578181818181818,0.002318181818181818
|
||||||
|
533,0.0464,0.0023
|
||||||
|
534,0.04701818181818182,0.0022818181818181817
|
||||||
|
535,0.047636363636363636,0.0022636363636363635
|
||||||
|
536,0.04825454545454545,0.0022454545454545453
|
||||||
|
537,0.048872727272727276,0.002227272727272727
|
||||||
|
538,0.049490909090909085,0.002209090909090909
|
||||||
|
539,0.05010909090909091,0.0021909090909090906
|
||||||
|
540,0.050727272727272725,0.002172727272727273
|
||||||
|
541,0.05134545454545454,0.0021545454545454546
|
||||||
|
542,0.051963636363636365,0.0021363636363636363
|
||||||
|
543,0.05258181818181818,0.002118181818181818
|
||||||
|
544,0.0532,0.0021
|
||||||
|
545,0.05381818181818182,0.0020818181818181816
|
||||||
|
546,0.05443636363636363,0.002063636363636364
|
||||||
|
547,0.05505454545454545,0.0020454545454545456
|
||||||
|
548,0.055672727272727277,0.0020272727272727274
|
||||||
|
549,0.056290909090909086,0.002009090909090909
|
||||||
|
550,0.05690909090909091,0.001990909090909091
|
||||||
|
551,0.05752727272727273,0.0019727272727272727
|
||||||
|
552,0.05814545454545454,0.0019545454545454545
|
||||||
|
553,0.058763636363636365,0.0019363636363636362
|
||||||
|
554,0.059381818181818175,0.0019181818181818182
|
||||||
|
555,0.06,0.0019
|
||||||
|
556,0.0641111111111111,0.001888888888888889
|
||||||
|
557,0.06822222222222223,0.0018777777777777777
|
||||||
|
558,0.07233333333333333,0.0018666666666666666
|
||||||
|
559,0.07644444444444444,0.0018555555555555556
|
||||||
|
560,0.08055555555555555,0.0018444444444444443
|
||||||
|
561,0.08466666666666667,0.0018333333333333333
|
||||||
|
562,0.08877777777777777,0.0018222222222222223
|
||||||
|
563,0.09288888888888888,0.001811111111111111
|
||||||
|
564,0.097,0.0018
|
||||||
|
565,0.10111111111111111,0.001788888888888889
|
||||||
|
566,0.10522222222222222,0.0017777777777777779
|
||||||
|
567,0.10933333333333334,0.0017666666666666666
|
||||||
|
568,0.11344444444444444,0.0017555555555555556
|
||||||
|
569,0.11755555555555555,0.0017444444444444445
|
||||||
|
570,0.12166666666666667,0.0017333333333333333
|
||||||
|
571,0.12577777777777777,0.0017222222222222222
|
||||||
|
572,0.1298888888888889,0.0017111111111111112
|
||||||
|
573,0.134,0.0017
|
||||||
|
574,0.13811111111111113,0.0016888888888888889
|
||||||
|
575,0.14222222222222222,0.0016777777777777778
|
||||||
|
576,0.14633333333333334,0.0016666666666666666
|
||||||
|
577,0.15044444444444444,0.0016555555555555555
|
||||||
|
578,0.15455555555555556,0.0016444444444444445
|
||||||
|
579,0.15866666666666668,0.0016333333333333334
|
||||||
|
580,0.1627777777777778,0.0016222222222222222
|
||||||
|
581,0.1668888888888889,0.0016111111111111111
|
||||||
|
582,0.17099999999999999,0.0015999999999999999
|
||||||
|
583,0.1751111111111111,0.0015888888888888888
|
||||||
|
584,0.17922222222222223,0.0015777777777777778
|
||||||
|
585,0.18333333333333335,0.0015666666666666667
|
||||||
|
586,0.18744444444444444,0.0015555555555555555
|
||||||
|
587,0.19155555555555556,0.0015444444444444444
|
||||||
|
588,0.19566666666666668,0.0015333333333333334
|
||||||
|
589,0.19977777777777778,0.0015222222222222221
|
||||||
|
590,0.2038888888888889,0.001511111111111111
|
||||||
|
591,0.20800000000000002,0.0015
|
||||||
|
592,0.2121111111111111,0.001488888888888889
|
||||||
|
593,0.21622222222222223,0.0014777777777777777
|
||||||
|
594,0.22033333333333335,0.0014666666666666667
|
||||||
|
595,0.22444444444444445,0.0014555555555555554
|
||||||
|
596,0.22855555555555557,0.0014444444444444444
|
||||||
|
597,0.2326666666666667,0.0014333333333333333
|
||||||
|
598,0.23677777777777778,0.0014222222222222223
|
||||||
|
599,0.2408888888888889,0.001411111111111111
|
||||||
|
600,0.245,0.0014
|
||||||
|
601,0.2469,0.001392
|
||||||
|
602,0.2488,0.001384
|
||||||
|
603,0.2507,0.001376
|
||||||
|
604,0.2526,0.001368
|
||||||
|
605,0.2545,0.00136
|
||||||
|
606,0.2564,0.001352
|
||||||
|
607,0.2583,0.001344
|
||||||
|
608,0.2602,0.001336
|
||||||
|
609,0.2621,0.001328
|
||||||
|
610,0.264,0.00132
|
||||||
|
611,0.2659,0.001312
|
||||||
|
612,0.2678,0.001304
|
||||||
|
613,0.2697,0.001296
|
||||||
|
614,0.2716,0.001288
|
||||||
|
615,0.2735,0.0012799999999999999
|
||||||
|
616,0.2754,0.001272
|
||||||
|
617,0.2773,0.001264
|
||||||
|
618,0.2792,0.001256
|
||||||
|
619,0.2811,0.001248
|
||||||
|
620,0.28300000000000003,0.00124
|
||||||
|
621,0.2849,0.001232
|
||||||
|
622,0.2868,0.001224
|
||||||
|
623,0.2887,0.001216
|
||||||
|
624,0.2906,0.0012079999999999999
|
||||||
|
625,0.2925,0.0012000000000000001
|
||||||
|
626,0.2944,0.001192
|
||||||
|
627,0.2963,0.001184
|
||||||
|
628,0.2982,0.001176
|
||||||
|
629,0.30010000000000003,0.001168
|
||||||
|
630,0.30200000000000005,0.00116
|
||||||
|
631,0.3039,0.001152
|
||||||
|
632,0.3058,0.001144
|
||||||
|
633,0.30770000000000003,0.001136
|
||||||
|
634,0.3096,0.001128
|
||||||
|
635,0.3115,0.00112
|
||||||
|
636,0.3134,0.001112
|
||||||
|
637,0.3153,0.001104
|
||||||
|
638,0.31720000000000004,0.001096
|
||||||
|
639,0.31910000000000005,0.001088
|
||||||
|
640,0.321,0.00108
|
||||||
|
641,0.3229,0.001072
|
||||||
|
642,0.32480000000000003,0.001064
|
||||||
|
643,0.3267,0.001056
|
||||||
|
644,0.3286,0.0010479999999999999
|
||||||
|
645,0.3305,0.0010400000000000001
|
||||||
|
646,0.33240000000000003,0.001032
|
||||||
|
647,0.33430000000000004,0.001024
|
||||||
|
648,0.33620000000000005,0.001016
|
||||||
|
649,0.3381,0.001008
|
||||||
|
650,0.34,0.001
|
||||||
|
651,0.3466949152542373,0.0009949152542372882
|
||||||
|
652,0.3533898305084746,0.0009898305084745764
|
||||||
|
653,0.3600847457627119,0.0009847457627118643
|
||||||
|
654,0.3667796610169492,0.0009796610169491525
|
||||||
|
655,0.37347457627118646,0.0009745762711864407
|
||||||
|
656,0.38016949152542373,0.0009694915254237288
|
||||||
|
657,0.38686440677966105,0.000964406779661017
|
||||||
|
658,0.3935593220338983,0.0009593220338983051
|
||||||
|
659,0.4002542372881356,0.0009542372881355932
|
||||||
|
660,0.4069491525423729,0.0009491525423728814
|
||||||
|
661,0.41364406779661017,0.0009440677966101695
|
||||||
|
662,0.4203389830508475,0.0009389830508474577
|
||||||
|
663,0.42703389830508476,0.0009338983050847457
|
||||||
|
664,0.433728813559322,0.0009288135593220339
|
||||||
|
665,0.44042372881355935,0.0009237288135593221
|
||||||
|
666,0.4471186440677966,0.0009186440677966102
|
||||||
|
667,0.45381355932203393,0.0009135593220338983
|
||||||
|
668,0.4605084745762712,0.0009084745762711864
|
||||||
|
669,0.46720338983050846,0.0009033898305084746
|
||||||
|
670,0.4738983050847458,0.0008983050847457628
|
||||||
|
671,0.48059322033898305,0.0008932203389830508
|
||||||
|
672,0.4872881355932204,0.000888135593220339
|
||||||
|
673,0.49398305084745764,0.0008830508474576271
|
||||||
|
674,0.5006779661016949,0.0008779661016949153
|
||||||
|
675,0.5073728813559322,0.0008728813559322035
|
||||||
|
676,0.5140677966101694,0.0008677966101694915
|
||||||
|
677,0.5207627118644068,0.0008627118644067796
|
||||||
|
678,0.5274576271186441,0.0008576271186440678
|
||||||
|
679,0.5341525423728813,0.000852542372881356
|
||||||
|
680,0.5408474576271186,0.000847457627118644
|
||||||
|
681,0.547542372881356,0.0008423728813559322
|
||||||
|
682,0.5542372881355933,0.0008372881355932203
|
||||||
|
683,0.5609322033898305,0.0008322033898305085
|
||||||
|
684,0.5676271186440678,0.0008271186440677967
|
||||||
|
685,0.574322033898305,0.0008220338983050847
|
||||||
|
686,0.5810169491525423,0.0008169491525423729
|
||||||
|
687,0.5877118644067797,0.000811864406779661
|
||||||
|
688,0.594406779661017,0.0008067796610169492
|
||||||
|
689,0.6011016949152542,0.0008016949152542373
|
||||||
|
690,0.6077966101694916,0.0007966101694915254
|
||||||
|
691,0.6144915254237289,0.0007915254237288136
|
||||||
|
692,0.6211864406779661,0.0007864406779661017
|
||||||
|
693,0.6278813559322034,0.0007813559322033899
|
||||||
|
694,0.6345762711864407,0.000776271186440678
|
||||||
|
695,0.6412711864406779,0.0007711864406779661
|
||||||
|
696,0.6479661016949152,0.0007661016949152542
|
||||||
|
697,0.6546610169491525,0.0007610169491525424
|
||||||
|
698,0.6613559322033898,0.0007559322033898305
|
||||||
|
699,0.6680508474576271,0.0007508474576271187
|
||||||
|
700,0.6747457627118645,0.0007457627118644068
|
||||||
|
701,0.6814406779661017,0.0007406779661016949
|
||||||
|
702,0.688135593220339,0.000735593220338983
|
||||||
|
703,0.6948305084745763,0.0007305084745762712
|
||||||
|
704,0.7015254237288135,0.0007254237288135593
|
||||||
|
705,0.7082203389830508,0.0007203389830508475
|
||||||
|
706,0.7149152542372881,0.0007152542372881356
|
||||||
|
707,0.7216101694915253,0.0007101694915254237
|
||||||
|
708,0.7283050847457627,0.0007050847457627119
|
||||||
|
709,0.735,0.0007
|
||||||
|
710,0.7773170731707317,0.0006951219512195122
|
||||||
|
711,0.8196341463414634,0.0006902439024390243
|
||||||
|
712,0.8619512195121951,0.0006853658536585366
|
||||||
|
713,0.9042682926829269,0.0006804878048780488
|
||||||
|
714,0.9465853658536586,0.000675609756097561
|
||||||
|
715,0.9889024390243903,0.0006707317073170731
|
||||||
|
716,1.031219512195122,0.0006658536585365854
|
||||||
|
717,1.0735365853658538,0.0006609756097560976
|
||||||
|
718,1.1158536585365855,0.0006560975609756097
|
||||||
|
719,1.158170731707317,0.0006512195121951219
|
||||||
|
720,1.200487804878049,0.0006463414634146342
|
||||||
|
721,1.2428048780487806,0.0006414634146341463
|
||||||
|
722,1.2851219512195122,0.0006365853658536585
|
||||||
|
723,1.327439024390244,0.0006317073170731707
|
||||||
|
724,1.3697560975609757,0.000626829268292683
|
||||||
|
725,1.4120731707317074,0.0006219512195121951
|
||||||
|
726,1.4543902439024392,0.0006170731707317073
|
||||||
|
727,1.496707317073171,0.0006121951219512195
|
||||||
|
728,1.5390243902439025,0.0006073170731707317
|
||||||
|
729,1.5813414634146343,0.0006024390243902439
|
||||||
|
730,1.6236585365853662,0.0005975609756097561
|
||||||
|
731,1.6659756097560976,0.0005926829268292683
|
||||||
|
732,1.7082926829268295,0.0005878048780487805
|
||||||
|
733,1.7506097560975613,0.0005829268292682927
|
||||||
|
734,1.7929268292682927,0.0005780487804878049
|
||||||
|
735,1.8352439024390246,0.000573170731707317
|
||||||
|
736,1.8775609756097564,0.0005682926829268292
|
||||||
|
737,1.9198780487804878,0.0005634146341463415
|
||||||
|
738,1.9621951219512197,0.0005585365853658537
|
||||||
|
739,2.0045121951219516,0.0005536585365853658
|
||||||
|
740,2.0468292682926834,0.0005487804878048781
|
||||||
|
741,2.089146341463415,0.0005439024390243903
|
||||||
|
742,2.1314634146341467,0.0005390243902439024
|
||||||
|
743,2.1737804878048785,0.0005341463414634146
|
||||||
|
744,2.21609756097561,0.0005292682926829268
|
||||||
|
745,2.258414634146342,0.0005243902439024391
|
||||||
|
746,2.3007317073170737,0.0005195121951219512
|
||||||
|
747,2.343048780487805,0.0005146341463414634
|
||||||
|
748,2.385365853658537,0.0005097560975609757
|
||||||
|
749,2.427682926829269,0.0005048780487804878
|
||||||
|
750,2.47,0.0005
|
||||||
|
751,2.462,0.000498
|
||||||
|
752,2.454,0.000496
|
||||||
|
753,2.446,0.000494
|
||||||
|
754,2.438,0.000492
|
||||||
|
755,2.43,0.00049
|
||||||
|
756,2.422,0.000488
|
||||||
|
757,2.414,0.000486
|
||||||
|
758,2.406,0.000484
|
||||||
|
759,2.398,0.000482
|
||||||
|
760,2.39,0.00048
|
||||||
|
761,2.382,0.000478
|
||||||
|
762,2.374,0.000476
|
||||||
|
763,2.366,0.00047400000000000003
|
||||||
|
764,2.358,0.00047200000000000003
|
||||||
|
765,2.35,0.00047000000000000004
|
||||||
|
766,2.342,0.000468
|
||||||
|
767,2.334,0.000466
|
||||||
|
768,2.326,0.000464
|
||||||
|
769,2.318,0.000462
|
||||||
|
770,2.31,0.00046
|
||||||
|
771,2.302,0.000458
|
||||||
|
772,2.294,0.000456
|
||||||
|
773,2.286,0.00045400000000000003
|
||||||
|
774,2.278,0.00045200000000000004
|
||||||
|
775,2.27,0.00045
|
||||||
|
776,2.262,0.000448
|
||||||
|
777,2.254,0.000446
|
||||||
|
778,2.246,0.000444
|
||||||
|
779,2.238,0.000442
|
||||||
|
780,2.23,0.00044
|
||||||
|
781,2.222,0.000438
|
||||||
|
782,2.214,0.000436
|
||||||
|
783,2.206,0.00043400000000000003
|
||||||
|
784,2.198,0.000432
|
||||||
|
785,2.19,0.00043000000000000004
|
||||||
|
786,2.182,0.000428
|
||||||
|
787,2.174,0.000426
|
||||||
|
788,2.166,0.000424
|
||||||
|
789,2.158,0.000422
|
||||||
|
790,2.15,0.00042
|
||||||
|
791,2.142,0.000418
|
||||||
|
792,2.134,0.00041600000000000003
|
||||||
|
793,2.126,0.00041400000000000003
|
||||||
|
794,2.118,0.00041200000000000004
|
||||||
|
795,2.11,0.00041
|
||||||
|
796,2.102,0.000408
|
||||||
|
797,2.094,0.000406
|
||||||
|
798,2.086,0.000404
|
||||||
|
799,2.078,0.000402
|
||||||
|
800,2.07,0.0004
|
||||||
|
114
src/utils/water_owt_config.py
Normal file
114
src/utils/water_owt_config.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
水域光学特性配置库 (Water Optical Type Configuration)
|
||||||
|
|
||||||
|
基于 QAA-v5 / QAA-v6 通用参数,为中国典型内陆水体配置参考波段和算法参数。
|
||||||
|
参考:
|
||||||
|
- Lee et al. (2002) QAA-v4
|
||||||
|
- Lee et al. (2010) QAA-v5
|
||||||
|
- Lee et al. (2014) QAA-v6
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
LAKE_CONFIGS: Dict[str, Dict] = {
|
||||||
|
# 太湖:藻型富营养化水体,参考波长 709nm
|
||||||
|
"太湖 (Lake Taihu)": {
|
||||||
|
"description": "太湖 — 藻型富营养化水体,参考波长 709nm",
|
||||||
|
"lambda_0": 709,
|
||||||
|
"reference_band": 709,
|
||||||
|
"qaa_version": "QAA-v6",
|
||||||
|
"S": 0.015,
|
||||||
|
"gamma": 1.0,
|
||||||
|
"case": "bloom_dominant",
|
||||||
|
"notes": "藻类主导水体,b_bp 偏高,叶绿素峰在 700-710nm 区间",
|
||||||
|
},
|
||||||
|
# 滇池:高浊度藻型水体,参考波长 710nm
|
||||||
|
"滇池 (Lake Dianchi)": {
|
||||||
|
"description": "滇池 — 高浊度藻型水体,参考波长 710nm",
|
||||||
|
"lambda_0": 710,
|
||||||
|
"reference_band": 710,
|
||||||
|
"qaa_version": "QAA-v5",
|
||||||
|
"S": 0.020,
|
||||||
|
"gamma": 1.0,
|
||||||
|
"case": "turbid_algal",
|
||||||
|
"notes": "高浊度 + 藻华共存,散射贡献显著",
|
||||||
|
},
|
||||||
|
# 青海湖:寡营养深水湖泊,参考波长 555nm(蓝绿波段)
|
||||||
|
"青海湖 (Lake Qinghai)": {
|
||||||
|
"description": "青海湖 — 寡营养深水湖泊,参考波长 555nm",
|
||||||
|
"lambda_0": 555,
|
||||||
|
"reference_band": 555,
|
||||||
|
"qaa_version": "QAA-v6",
|
||||||
|
"S": 0.006,
|
||||||
|
"gamma": 0.65,
|
||||||
|
"case": "oligotrophic_clear",
|
||||||
|
"notes": "清澈寡营养水体,CDOM 极低,参考 555nm(蓝绿峰)",
|
||||||
|
},
|
||||||
|
# 巢湖:富营养化藻型水体,参考波长 709nm
|
||||||
|
"巢湖 (Lake Chaohu)": {
|
||||||
|
"description": "巢湖 — 富营养化藻型水体,参考波长 709nm",
|
||||||
|
"lambda_0": 709,
|
||||||
|
"reference_band": 709,
|
||||||
|
"qaa_version": "QAA-v5",
|
||||||
|
"S": 0.018,
|
||||||
|
"gamma": 1.0,
|
||||||
|
"case": "bloom_dominant",
|
||||||
|
"notes": "太湖类型水体,蓝藻水华频发",
|
||||||
|
},
|
||||||
|
# 长江中下游典型水体:中等浊度,参考波长 665nm
|
||||||
|
"长江中下游 (Mid-Lower Yangtze)": {
|
||||||
|
"description": "长江中下游 — 中等浊度混合型水体,参考波长 665nm",
|
||||||
|
"lambda_0": 665,
|
||||||
|
"reference_band": 665,
|
||||||
|
"qaa_version": "QAA-v5",
|
||||||
|
"S": 0.012,
|
||||||
|
"gamma": 0.80,
|
||||||
|
"case": "turbid_mixed",
|
||||||
|
"notes": "悬浮物浓度中等,CDOM 与藻类混合贡献",
|
||||||
|
},
|
||||||
|
# 黄河:浑浊高沙河流,参考波长 650nm
|
||||||
|
"黄河 (Yellow River)": {
|
||||||
|
"description": "黄河 — 浑浊高沙河流型水体,参考波长 650nm",
|
||||||
|
"lambda_0": 650,
|
||||||
|
"reference_band": 650,
|
||||||
|
"qaa_version": "QAA-v5",
|
||||||
|
"S": 0.025,
|
||||||
|
"gamma": 1.0,
|
||||||
|
"case": "highly_turbid",
|
||||||
|
"notes": "悬浮泥沙主导,散射系数极高,参考 650nm 避免强吸收干扰",
|
||||||
|
},
|
||||||
|
# 通用水体(默认参数,用于无特定配置时)
|
||||||
|
"通用水体 (Generic)": {
|
||||||
|
"description": "通用水体 — 默认 QAA 参数,参考波长 555nm",
|
||||||
|
"lambda_0": 555,
|
||||||
|
"reference_band": 555,
|
||||||
|
"qaa_version": "QAA-v6",
|
||||||
|
"S": 0.013,
|
||||||
|
"gamma": 0.75,
|
||||||
|
"case": "generic",
|
||||||
|
"notes": "通用参数,适用于大多数内陆水体初步分析",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_lake_config(lake_name: str) -> Optional[Dict]:
|
||||||
|
"""根据水域名称获取配置字典,未找到返回 None"""
|
||||||
|
return LAKE_CONFIGS.get(lake_name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_lake_names() -> List[str]:
|
||||||
|
"""返回所有已配置的水域名称列表(按哈希表键序)"""
|
||||||
|
return list(LAKE_CONFIGS.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_lake() -> str:
|
||||||
|
"""返回默认水域名称"""
|
||||||
|
return "通用水体 (Generic)"
|
||||||
|
|
||||||
|
|
||||||
|
def get_lambda_0(lake_name: str) -> int:
|
||||||
|
"""快捷方法:获取指定水域的参考波长"""
|
||||||
|
cfg = get_lake_config(lake_name)
|
||||||
|
return cfg["lambda_0"] if cfg else 555
|
||||||
Reference in New Issue
Block a user