feat(qaa): 新增 QAA 算法模块 src/core/algorithms/qaa/
This commit is contained in:
7
src/core/algorithms/qaa/__init__.py
Normal file
7
src/core/algorithms/qaa/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
QAA 准解析反演算法模块
|
||||
"""
|
||||
from src.core.algorithms.qaa.qaas_baseline import QAABaselineSolver
|
||||
|
||||
__all__ = ['QAABaselineSolver']
|
||||
345
src/core/algorithms/qaa/qaas_baseline.py
Normal file
345
src/core/algorithms/qaa/qaas_baseline.py
Normal file
@ -0,0 +1,345 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
QAA 准解析算法基线求解器 (QAABaselineSolver)
|
||||
|
||||
实现 QAA-v5 / QAA-v6 核心步骤:
|
||||
1. Rrs(λ) → r_rs(λ)(水面以下遥感反射率转换)
|
||||
2. 计算中间变量 u(λ)(固有光学性质比值)
|
||||
3. λ₀ 锚点查表获取纯水吸收 aw(λ₀) 和后向散射 bbw(λ₀)
|
||||
4. 估算全波段 b_b(λ)(后向散射系数)
|
||||
5. 推导全波段 a(λ)(总吸收系数)
|
||||
|
||||
参考:
|
||||
- Lee, Z.P. et al. (2002) JGR-Oceans, 107(C4), 9-1~9-18 (QAA-v4)
|
||||
- Lee, Z.P. et al. (2010) Applied Optics, 49(4), 617-623 (QAA-v5)
|
||||
- Lee, Z.P. et al. (2014) Applied Optics, 53(4), 598-611 (QAA-v6)
|
||||
"""
|
||||
|
||||
import os
|
||||
import warnings
|
||||
from typing import Optional, Union, Tuple
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class QAABaselineSolver:
|
||||
"""
|
||||
QAA 准解析算法基线求解器。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pure_water_csv : str, optional
|
||||
纯水 IOPs 表路径,默认使用 src/utils/pure_water_iops.csv。
|
||||
qaa_version : str, default "QAA-v6"
|
||||
算法版本,支持 "QAA-v5" 或 "QAA-v6"。
|
||||
|
||||
Attributes
|
||||
----------
|
||||
iops_df : pd.DataFrame
|
||||
纯水 IOPs 表,含 Wavelength / aw / bbw 三列。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
pure_water_csv: Optional[str] = None,
|
||||
qaa_version: str = "QAA-v6"
|
||||
):
|
||||
if pure_water_csv is None:
|
||||
project_root = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'utils')
|
||||
)
|
||||
pure_water_csv = os.path.join(project_root, 'pure_water_iops.csv')
|
||||
|
||||
if not os.path.exists(pure_water_csv):
|
||||
raise FileNotFoundError(f"纯水 IOPs 表不存在: {pure_water_csv}")
|
||||
|
||||
self.iops_df = pd.read_csv(pure_water_csv)
|
||||
self.qaa_version = qaa_version
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 核心 QAA 步骤
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _rrs_to_rrs_subsurface(rrs: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
将水面遥感反射率 Rrs 转换为水面以下遥感反射率 r_rs。
|
||||
|
||||
转换公式(Lee et al. 1999):
|
||||
r_rs = Rrs / (0.52 + 1.7 * Rrs)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rrs : np.ndarray
|
||||
水面遥感反射率 Rrs,形状 (N,) 或 (N, n_bands)。
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
水面以下遥感反射率 r_rs。
|
||||
"""
|
||||
rrs = np.asarray(rrs, dtype=np.float64)
|
||||
denom = 0.52 + 1.7 * rrs
|
||||
with np.errstate(divide='ignore', invalid='ignore'):
|
||||
result = rrs / denom
|
||||
result[~np.isfinite(result)] = np.nan
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _compute_u(rrs_subsurface: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
计算中间变量 u = b_b / (a + b_b)。
|
||||
|
||||
QAA-v5/v6 经验关系(Lee et al. 2002):
|
||||
u = r_rs / (0.5 * r_rs + sqrt(0.25 * r_rs^2 + 0.1 * r_rs))
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rrs_subsurface : np.ndarray
|
||||
水面以下遥感反射率 r_rs。
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
u 值,范围 [0, 1)。
|
||||
"""
|
||||
rs = np.asarray(rrs_subsurface, dtype=np.float64)
|
||||
with np.errstate(divide='ignore', invalid='ignore'):
|
||||
result = rs / (0.5 * rs + np.sqrt(0.25 * rs ** 2 + 0.1 * rs))
|
||||
result[~np.isfinite(result)] = np.nan
|
||||
return result
|
||||
|
||||
def _get_pure_water_iops(self, wavelength: Union[int, float]) -> Tuple[float, float]:
|
||||
"""
|
||||
根据波长从纯水 IOPs 表中插值获取 aw 和 bbw。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
wavelength : float
|
||||
波长(nm),范围应在 400-800nm 内。
|
||||
|
||||
Returns
|
||||
-------
|
||||
(aw, bbw) : tuple
|
||||
纯水吸收系数 (m^-1) 和后向散射系数 (m^-1)。
|
||||
"""
|
||||
df = self.iops_df
|
||||
wl_arr = df['Wavelength'].values
|
||||
aw_arr = df['aw'].values
|
||||
bbw_arr = df['bbw'].values
|
||||
|
||||
aw = float(np.interp(wavelength, wl_arr, aw_arr))
|
||||
bbw = float(np.interp(wavelength, wl_arr, bbw_arr))
|
||||
return aw, bbw
|
||||
|
||||
@staticmethod
|
||||
def _compute_bb(
|
||||
u: np.ndarray,
|
||||
bbw_0: float,
|
||||
wavelength: np.ndarray,
|
||||
lambda_0: int
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
估算全波段后向散射系数 b_b(λ)。
|
||||
|
||||
经验光谱形状(Lee et al. 2002, QAA-v4):
|
||||
b_b(λ) = b_bw(λ₀) * (λ₀ / λ)^S
|
||||
|
||||
其中 S 为经验光谱斜率参数(QAA-v5 中默认 0.5,
|
||||
QAA-v6 中随 λ₀ 自适应调整)。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
u : np.ndarray
|
||||
中间变量 u。
|
||||
bbw_0 : float
|
||||
λ₀ 处的纯水后向散射系数。
|
||||
wavelength : np.ndarray
|
||||
全波段波长数组。
|
||||
lambda_0 : int
|
||||
参考波长(锚点)。
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
全波段后向散射系数 b_b。
|
||||
"""
|
||||
S = 0.5 if lambda_0 < 600 else 0.0
|
||||
wavelength = np.asarray(wavelength, dtype=np.float64)
|
||||
ratio = (float(lambda_0) / wavelength) ** S
|
||||
bb = u * bbw_0 / (1.0 - u) * ratio
|
||||
bb = np.maximum(bb, 0.0)
|
||||
return bb
|
||||
|
||||
@staticmethod
|
||||
def _compute_a(
|
||||
u: np.ndarray,
|
||||
aw_0: float,
|
||||
bbw_0: float,
|
||||
wavelength: np.ndarray,
|
||||
lambda_0: int
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
推导全波段总吸收系数 a(λ)。
|
||||
|
||||
由 u = b_b / (a + b_b) 推导:
|
||||
a = b_b * (1 - u) / u
|
||||
|
||||
Parameters
|
||||
----------
|
||||
u : np.ndarray
|
||||
中间变量 u。
|
||||
aw_0 : float
|
||||
λ₀ 处的纯水吸收系数。
|
||||
bbw_0 : float
|
||||
λ₀ 处的纯水后向散射系数。
|
||||
wavelength : np.ndarray
|
||||
全波段波长数组。
|
||||
lambda_0 : int
|
||||
参考波长(锚点)。
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
全波段总吸收系数 a。
|
||||
"""
|
||||
S = 0.5 if lambda_0 < 600 else 0.0
|
||||
wavelength = np.asarray(wavelength, dtype=np.float64)
|
||||
ratio = (float(lambda_0) / wavelength) ** S
|
||||
bbw = bbw_0 * ratio
|
||||
with np.errstate(divide='ignore', invalid='ignore'):
|
||||
a = bbw * (1.0 - u) / u + aw_0
|
||||
a[~np.isfinite(a)] = np.nan
|
||||
return a
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 主入口
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def run_inversion(
|
||||
self,
|
||||
wavelengths: np.ndarray,
|
||||
Rrs_spectrum: np.ndarray,
|
||||
lambda_0: int
|
||||
) -> dict:
|
||||
"""
|
||||
执行 QAA 核心反演。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
wavelengths : np.ndarray
|
||||
光谱波长数组(nm),形状 (n_bands,) 或 (n_samples, n_bands)。
|
||||
Rrs_spectrum : np.ndarray
|
||||
水面遥感反射率光谱数据,形状 (n_bands,) 或 (n_samples, n_bands)。
|
||||
若为 2D,每行为一个样本的光谱。
|
||||
lambda_0 : int
|
||||
参考波长(锚点),用于查表获取纯水 IOPs。
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
包含以下键的字典:
|
||||
- wavelengths : 波长数组
|
||||
- Rrs : 输入 Rrs
|
||||
- r_rs_subsurface : 水下遥感反射率
|
||||
- u : 中间变量
|
||||
- a_lambda : 总吸收系数 a(λ)
|
||||
- bb_lambda : 后向散射系数 b_b(λ)
|
||||
- aw : λ₀ 处纯水吸收
|
||||
- bbw : λ₀ 处纯水后向散射
|
||||
"""
|
||||
wavelengths = np.asarray(wavelengths, dtype=np.float64)
|
||||
Rrs_spectrum = np.asarray(Rrs_spectrum, dtype=np.float64)
|
||||
|
||||
if Rrs_spectrum.ndim == 1:
|
||||
Rrs_spectrum = Rrs_spectrum[np.newaxis, :]
|
||||
|
||||
aw_0, bbw_0 = self._get_pure_water_iops(lambda_0)
|
||||
|
||||
results = []
|
||||
for row in Rrs_spectrum:
|
||||
rrs_sub = self._rrs_to_rrs_subsurface(row)
|
||||
u = self._compute_u(rrs_sub)
|
||||
bb = self._compute_bb(u, bbw_0, wavelengths, lambda_0)
|
||||
a = self._compute_a(u, aw_0, bbw_0, wavelengths, lambda_0)
|
||||
results.append({
|
||||
'wavelengths': wavelengths,
|
||||
'Rrs': row,
|
||||
'r_rs_subsurface': rrs_sub,
|
||||
'u': u,
|
||||
'a_lambda': a,
|
||||
'bb_lambda': bb,
|
||||
'aw_0': aw_0,
|
||||
'bbw_0': bbw_0,
|
||||
})
|
||||
|
||||
if len(results) == 1:
|
||||
return results[0]
|
||||
return results
|
||||
|
||||
def invert_to_csv(
|
||||
self,
|
||||
wavelengths: np.ndarray,
|
||||
Rrs_spectrum: np.ndarray,
|
||||
lambda_0: int,
|
||||
output_csv: str,
|
||||
wavelength_col: str = "Wavelength",
|
||||
sample_ids: Optional[list] = None
|
||||
) -> str:
|
||||
"""
|
||||
执行反演并将结果保存为 CSV 文件。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
wavelengths : np.ndarray
|
||||
波长数组(n_bands,)。
|
||||
Rrs_spectrum : np.ndarray
|
||||
光谱数据,形状 (n_bands,) 或 (n_samples, n_bands)。
|
||||
lambda_0 : int
|
||||
参考波长。
|
||||
output_csv : str
|
||||
输出 CSV 文件路径。
|
||||
wavelength_col : str
|
||||
输出 CSV 中波长列的列名前缀。
|
||||
sample_ids : list, optional
|
||||
样本 ID 列表(若为 None,使用 row_0, row_1, ...)。
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
输出文件路径。
|
||||
"""
|
||||
wavelengths = np.asarray(wavelengths, dtype=np.float64)
|
||||
Rrs_spectrum = np.asarray(Rrs_spectrum, dtype=np.float64)
|
||||
|
||||
if Rrs_spectrum.ndim == 1:
|
||||
Rrs_spectrum = Rrs_spectrum[np.newaxis, :]
|
||||
|
||||
n_samples = Rrs_spectrum.shape[0]
|
||||
if sample_ids is None:
|
||||
sample_ids = [f"sample_{i}" for i in range(n_samples)]
|
||||
|
||||
aw_0, bbw_0 = self._get_pure_water_iops(lambda_0)
|
||||
|
||||
rows_out = []
|
||||
for i, row in enumerate(Rrs_spectrum):
|
||||
rrs_sub = self._rrs_to_rrs_subsurface(row)
|
||||
u = self._compute_u(rrs_sub)
|
||||
bb = self._compute_bb(u, bbw_0, wavelengths, lambda_0)
|
||||
a = self._compute_a(u, aw_0, bbw_0, wavelengths, lambda_0)
|
||||
for j, wl in enumerate(wavelengths):
|
||||
rows_out.append({
|
||||
'sample_id': sample_ids[i],
|
||||
'Wavelength': wl,
|
||||
'Rrs': row[j],
|
||||
'r_rs': rrs_sub[j],
|
||||
'u': u[j],
|
||||
'a_lambda': a[j],
|
||||
'bb_lambda': bb[j],
|
||||
})
|
||||
|
||||
df = pd.DataFrame(rows_out)
|
||||
os.makedirs(os.path.dirname(output_csv) or '.', exist_ok=True)
|
||||
df.to_csv(output_csv, index=False, float_format='%.8f')
|
||||
return output_csv
|
||||
Reference in New Issue
Block a user