Commit Graph

131 Commits

Author SHA1 Message Date
f97527598d feat: 各步骤产物改为原子化落盘,复用前校验 .done 完成标记
跳过条件从 Path.exists() 改为 is_file_complete(),产物成功落盘后统一补 mark_file_complete()。涉及 step1 水域掩膜、step3 去耀斑、SHP 栅格化缓存三处快路径。

step3 去耀斑新增清理分支:无 .done 标记的旧文件一律视为半成品,先删除再重跑(Kutser/Goodman/Hedley/SUGAR 四种方法一致)。

模型落盘改为 atomic_filepath:modeling_batch 批量建模与 automl_trainer 的 AutoML 结果均先写 .__wip 再原子替换,避免中断产生半个 joblib 文件。
2026-09-15 10:34:34 +08:00
8f416c728b fix: 推理端反射率量级统管收敛为_align_reflectance_scale单一入口
- 新增 _align_reflectance_scale():光谱列 >10 则 /10000 收敛到 0~1,并清洗 NaN/负值
- inference_pipeline / batch_inference / batch_inference_multi_data 三处散装 /10000 魔法逻辑统一收敛
(注:该文件另含先前本地未提交改动,一并入库)
2026-09-08 17:31:51 +08:00
4d49753cca fix: 实测站坐标改osr重投影+GDAL逆仿射,提取光谱写CSV前统一0~1量纲
- geo_to_pixel 改用 gdal.InvGeoTransform 全仿射(含旋转项);坐标列正则收紧,只认经纬度,剔除 pixel/utm 误判
- get_spectral_in_coor 主流程由错误 UTM 数学公式改为 osr.CoordinateTransformation(EPSG源->影像原生CRS) 重投影
- 提取光谱列写 CSV 前做 >10 则 /10000 量级收敛(不动实测值列)
2026-09-08 17:26:18 +08:00
04f9a647d8 fix: 推理端自适应反射率缩放 + Pipeline 列精确对齐
1. 自适应反射率量级缩放 (inference_pipeline/batch_inference/batch_inference_multi_data):
   - 检测光谱列 max > 10 时自动 ÷10000 统一到 0-1 区间
   - 三个推理入口全量同步保护

2. Pipeline 列精确对齐 (preprocess_spectra 两处):
   - 旧: not in 盲目排除多余列 → 140个光谱列被当多余移除
   - 新: 遍历 train_cols,精确字符串匹配 → math.isclose 浮点近似
        匹配列重命名为训练列名,缺失列补零
        最终 spectra[train_cols] 严格按训练顺序输出
   - 兼容路径同步修复

3. 误导性日志修正:
   - 改前: '正在应用预处理方法: D1' → 让用户以为推理端在手动做 D1
   - 改后: '[模型信息] 训练预处理方法: D1 — 由 Pipeline 内部自动执行'
2026-08-04 09:10:26 +08:00
f801b481fe fix: SVR+MNF 训练管线三个致命 Bug 修复
1. SVR y 尺度修复 (modeling_batch.py):
   - TransformedTargetRegressor 包裹 SVR,自动标准化 y 后训练
   - 超参前缀 model__ → model__regressor__ (穿透包裹层)
   - _find_svr_in_pipeline 兼容新的 TransformedTargetRegressor 嵌套

2. MNF 噪声估计修复 (spectral_Preprocessing.py):
   - 旧: noise = X[1:] - X[:-1] (假设空间连续,对打乱数据错误)
   - 新: noise = X - savgol_filter(X) (SG 滤波残差提取纯光谱噪声)
   - 附带波段数检查和异常兜底

3. DualStream_MNF 补标准 (spectral_Preprocessing.py):
   - FeatureUnion 外再包 Pipeline + StandardScaler
   - MNF 主成分(±10) 和 Physical 指数(0.001) 统一量纲后再送 SVR
2026-08-04 09:10:18 +08:00
e3a08e0ad0 feat: 推理端光谱覆盖率智能预警
- 新增 _check_spectral_coverage() 静态方法
- 比较预测波长 vs 训练波长的起止范围
- 左端缺口 >15nm → 黄色警告:蓝端/紫外特征丢失
- 右端缺口 >15nm → 黄色警告:近红外特征丢失
- 在 _preprocess_dual_stream 和 preprocess_spectra 两处调用
- 使用 ANSI \033[93m 黄色高亮,不中断程序运行
2026-07-30 13:12:19 +08:00
f0b9f178da feat: 光谱黄金区间过滤 — 训练波长统一收缩到 400-1000nm
- 新增 WAVELENGTH_RANGE = (400.0, 1000.0) 类常量
- 新增 _is_wavelength_in_range() 范围感知波长列判断
- _extract_train_wavelengths() 自动过滤超出区间的波长
- _extract_feature_columns / train_single_model / pre-scan 三处调用点
  统一切换为范围感知判断
- 效果: 308 波段 → 300 波段 (裁掉 <400nm 的 4 个 + >1000nm 的 4 个)
- 推理端通过 metadata train_wavelengths 自动继承过滤后的网格
2026-07-30 13:12:14 +08:00
b19cb427b9 feat: MNF 诊断系统 + 全局对齐两遍扫描法
【MNFTransformer 增强】(spectral_Preprocessing.py)
  - fit() 存储 eigvals_w_/eigvals_ratio_/eigvals_cumsum_ 实例属性
  - _print_band_selection() 精简 3 行控制台输出 (总成分/选中数/Top3 贡献率)
  - get_diagnostics_dict() 返回结构化诊断字典供 JSON 导出

【模型保存增强】(modeling_batch.py)
  - save_model: joblib.dump 前注入 metadata['mnf_diagnostics']
  - save_model: 并行导出 *_mnf_diag.json 独立诊断文件
  - 部署 npz 新增 mnf_eigvals/_ratio/_cumsum 三个特征值数组

【批量训练报告】(modeling_batch.py)
  - _generate_mnf_report() 生成 batch_mnf_eigenvalue_report.md
  - 按目标列+模型分块,每个选中成分按特征值降序列出具体数值
  - 容错:无 MNF 模型时标注警告,不崩溃

【MNF 全局对齐 — 两遍扫描法】(modeling_batch.py)
  - 第一遍预扫描: 对每个目标 fit MNFTransformer(0.95),记录所需成分数
  - 取全局最大值 global_max_mnf_components (兜底 50)
  - 第二遍正式训练: 所有 Pipeline 统一使用固定整型成分数
  - 仅当启用 DualStream_MNF 时触发,非 MNF 场景透明跳过
  - get_preprocessing_transformer 新增 mnf_n_components 参数透传链
2026-07-30 12:43:49 +08:00
314d947d34 fix: 推理端三项修复 — 光谱断崖 + 全零拦截 + MNF 加载诊断
【修复 1】np.interp 边缘断崖 (3 处)
  - _preprocess_dual_stream + preprocess_spectra 主/兼容重采样路径
  - left/right 从 np.nan 改为 y_vals[0]/y_vals[-1] 恒定外推
  - 彻底杜绝 NaN→0.0 造成的光谱曲线两端零值断崖

【修复 2】全零光谱像素 NaN 拦截
  - 新增 _mask_zero_spectra_pixels() 静态方法
  - 检测 NDWI 掩膜外的全零陆地像素,强制预测值为 NaN
  - 覆盖 inference_pipeline / batch_inference / batch_inference_multi_data
  - Kriging/IDW 插值天然忽略 NaN,制图时陆地直接留白

【新增】MNF 诊断信息在模型加载时自动输出
  - _print_mnf_info_if_available() 递归查找 Pipeline 中的 MNFTransformer
  - load_best_model / load_specific_model 加载后自动打印精简 3 行诊断
2026-07-30 12:43:29 +08:00
88dd1ae1d8 revert: 回退 StandardScaler — 跨传感器场景下双重归一化导致推理坍缩
- 删除 Pipeline 中 scaler(StandardScaler) 步骤
- 删除 save_model 中 scaler_mean/scaler_scale 导出
- 删除 StandardScaler import
- 恢复: imputer → FeatureUnion → cleaner → SVR
- MNF 白化本身就是尺度对齐,无需额外标准化
2026-07-29 15:54:05 +08:00
67900c7aa6 fix: 补充 StandardScaler 导入 2026-07-29 15:46:14 +08:00
3edb73b652 fix: DualStream Pipeline 三项修复 — StandardScaler + 部署导出 + 推理拆弹
任务1: Pipeline 插入 StandardScaler
  imputer → FeatureUnion → cleaner → scaler(StandardScaler) → SVR
  MNF(≈1e4量级)与物理指数(≈1e0量级)统一尺度后入SVR

任务2: .npz 导出 scaler_mean / scaler_scale
  C++端可复现 (X - mean) / scale

任务3: 推理端 train_wavelengths 缺失时直接抛异常
  不再静默裸退308列导致 SimpleImputer 维度不匹配崩溃
2026-07-29 15:41:25 +08:00
6849ff6877 feat: 新增 Physical_Only 预处理分支 — 纯物理特征+标准化,与 MNF A/B 对比
spectral_Preprocessing.py:
- 新增 Physical_Only 分支: Pipeline[PhysicalFeatureExtractor + StandardScaler]
- 纯物理特征流: 20个指数 → StandardScaler → SVR
- DualStream_MNF 逻辑完全不变

modeling_batch.py:
- preprocessing_methods 新增 Physical_Only
- 训练时 Physical_Only 也只取纯光谱50列
- 波长列表传递覆盖 Physical_Only

GUI:
- 两个面板同步新增「纯物理特征 (指数+标准化)」选项
2026-07-29 14:32:59 +08:00
f3150f7807 refactor: 抽取 _preprocess_dual_stream 独立方法
- preprocess_spectra 中 DualStream_MNF 分支从 25 行内联代码
  变为 1 行方法调用: self._preprocess_dual_stream(spectra, metadata)
- 新方法含完整文档、fallback 路径、重采样逻辑
- 与旧 WQI 补齐/linspace 路径完全解耦
2026-07-29 13:23:13 +08:00
f4a927386b refactor: DualStream 纯光谱输入 — 消除 WQI 冗余双向断层
训练端:
- DualStream_MNF 时只取纯光谱列(50列)传给 Pipeline
- PhysicalFeatureExtractor + MNFTransformer 都只收纯光谱
- 不再从 CSV 预读 WQI 列混入输入

推理端:
- 只做 308→50 光谱重采样,不补 WQI
- pipeline.predict() 自动完成 Physical 指数计算 + MNF 降维
- 删除 30+ 行 WQI 补齐代码

训练/推理完全对称: 纯光谱入 → FeatureUnion → SVR 出
2026-07-29 13:15:10 +08:00
3414f0207a fix: DualStream_MNF 推理重建113列后交给 Pipeline.predict() 完整处理
- 之前手动调 imputer/preproc.transform 后返回, predict() 又套一层
  Pipeline → SimpleImputer 收到44列不匹配113列
- 改为重建113列训练特征集,返回后让 model.predict() 完整走
  Pipeline: imputer→preproc→cleaner→SVR,与训练完全一致
2026-07-29 10:58:26 +08:00
c9374a489b fix: DualStream_MNF 推理先重采样308→训练网格再进Pipeline
- 推理数据 308 波段 vs 训练时 113 列(50光谱+63WQI)维度不同
- SimpleImputer fit在113列上不接受308列输入
- 修复: 先 np.interp 重采样到训练波长网格, 再 WQI 补齐,
  然后喂入 Pipeline 的 imputer+preproc+cleaner 最终进 SVR
2026-07-29 10:53:58 +08:00
8b3955fd7a fix: 推理端 DualStream_MNF 直通 Pipeline — 不再走旧重采样+WQI路径
- 训练时 Pipeline 保存了完整 MNFTransformer(mean_/W_mnf_)
  和 PhysicalFeatureExtractor(wavelengths/_feat_cols_)
- 推理端新增快速通道:检测到 DualStream_MNF 时,直接调用
  Pipeline 的 imputer.transform + preproc.transform,复用训练时
  fit 好的变换矩阵,无需手动光谱重采样和 WQI 补齐
- 旧模型(非 DualStream_MNF)保持原有逻辑不变
2026-07-29 10:51:01 +08:00
40f12b569e fix: 部署导出 _feat_cols_ 解包适配新4元组(name,ftype,ia,ib) 2026-07-29 10:42:17 +08:00
58bad4081e feat: PhysicalFeature 扩展到全部63个水色指数 + MNF成分数自动确定
PhysicalFeatureExtractor:
- 废弃硬编码3指数(NDVI/NDWI/AlgaeRatio),改为调用
  WaterQualityIndexCalculator.calculate_many() 计算全部63个公式
- 涵盖 BGA(25)/Chl(16)/Turb(10)/TSM(3) 等完整指数集

MNFTransformer:
- n_components 支持 float(0~1): 根据累积方差比自动确定成分数
  n_components=0.95 → 保留95%方差的成分
- fit 后保存 n_components_ (实际成分数) 供部署导出使用
- Wp 显式按特征值降序排列
2026-07-29 10:37:09 +08:00
495857913d refactor: RandomizedSearchCV → GridSearchCV — SVR 超参全量穷举
- SVR 参数网格仅 216 种组合(4×6×3×3),全量搜索 ~2s 完成
- 之前 n_iter=10 只抽样 4.6%,靠运气撞最优参数
- 删除 n_iter 和 random_state 参数(GridSearchCV 不需要)
- 保留 RandomizedSearchCV import 供其他模型(参数空间大的)使用
2026-07-29 10:28:17 +08:00
6aaaea5abd fix: inf→NaN 清洗兼容 ndarray(spxy/ks 返回 ndarray 无 .replace) 2026-07-29 10:22:47 +08:00
80189a9f44 fix: 补充 BaseEstimator/TransformerMixin 导入 2026-07-29 10:20:57 +08:00
12866f12b0 fix: Pipeline 安全网 — preproc 与 model 间插入 _SafeFiniteTransformer
- 新增 _SafeFiniteTransformer: np.nan_to_num + clip(±1e15)
- 放在 Pipeline preproc→model 之间,无论 MNF 数值溢出还是
  PhysicalFeatureExtractor 除零,SVR 收不到 inf/NaN
- X_train/X_test 入口先 replace(inf→NaN),SimpleImputer 接力填充
2026-07-29 10:20:00 +08:00
09f8d89b56 fix: PhysicalFeatureExtractor 修复 ndarray 输入时波长丢失问题
- SimpleImputer 把 DataFrame → ndarray,PhysicalFeatureExtractor.fit()
  收不到列名导致 ValueError: 必须在 __init__ 中提供 wavelengths
- get_preprocessing_transformer 新增 wavelengths 可选参数
- train_single_model 在 DualStream_MNF 时提取 X_raw 波长列表传入工厂
- PhysicalFeatureExtractor 已有 ndarray + wavelengths 参数的处理分支
2026-07-29 10:11:01 +08:00
4347988ab2 feat: 训练框架适配 DualStream_MNF + 部署矩阵 .npz 导出
modeling_batch.py:
- preprocessing_methods 新增 DualStream_MNF
- save_model 新增 C++/Rust 部署导出逻辑:
  自动检测 Pipeline 中的 MNFTransformer + SVR,
  提取 mean_/W_mnf_/dual_coef_/support_vectors_/
  intercept_/gamma/kernel 等纯量矩阵,
  保存为 *_deploy.npz 文件
- 新增 _find_mnf_in_pipeline / _find_svr_in_pipeline /
  _find_physical_extractor_in_pipeline 辅助方法,
  支持 FeatureUnion 嵌套递归查找
2026-07-29 10:02:37 +08:00
80e46c8476 fix: 单步模式下自动发现耀斑掩膜文件
- 单步运行时 PipelineContext 不保留 step2 的 glint_mask_path,
  导致采样时无法剔除耀斑区域
- 新增自动发现:兜底扫描 2_Glint_Detection/severe_glint_area.dat
- 补充缺失的 import os
2026-07-28 17:25:41 +08:00
241b138bb5 fix: 异常值裁剪 P1/P99 → P2/P98,避免边界垃圾值污染
- P1-P99 对 pH/DO 等参数不够保守:边界像素模型预测 pH=-22
  导致 P1=-2.38,裁剪后仍保留物理上不可能的值
- 改为 P2-P98(4% 裁剪),IQR 8.3-8.5 的主体数据完全不受影响
- 两处同步:预测端 _clip_outliers + 插值端 _perform_interpolation
2026-07-28 17:09:42 +08:00
1efe10adb6 revert: 回退克里金多线程并发,恢复串行处理
并发实测无提速(32GB 内存下单个克里金已用 5-6GB,两个并发
竞争内存带宽导致实际耗时相同),回退为简单串行循环。
2026-07-28 16:53:15 +08:00
e8a81008fa feat: 专题图克里金插值支持多线程并发
- ThreadPoolExecutor 替代串行 for 循环,默认 2 线程并发
- 克里金内部 numpy/scipy 运算释放 GIL,线程并行有效
- 通过 kriging_workers 配置项控制并发数(默认 2,设为 1 回退串行)
- 主线程预先设置 matplotlib Agg 后端,避免多线程竞争
- 保留串行路径作为 fallback(单 CSV 或 workers=1 时)
2026-07-28 16:41:41 +08:00
3c7e735342 feat: 预测结果百分位裁剪 — 消除极端异常值拉爆专题图色阶
- 新增 _clip_outliers(P1-P99) 方法,预测完成后自动裁剪
- 水体边界零值区域导致模型外推极端值(-86~7026),
  裁剪后克里金插值色阶不再被拉爆,正常空间细节可见
- 无异常值时不触发裁剪(零开销)
2026-07-28 16:38:07 +08:00
b6fa07925a fix: 新模型路径推理时缺失 WQI 特征导致 Pipeline 维度不匹配
- 新模型路径(有 train_wavelengths)重采样后仅产出 50 列光谱,
  未计算模型训练时包含的 WQI 指数特征(期望 113 列)
- 旧模型路径(else 分支)有此逻辑但被隔离,新路径无法受益
- 将 WQI 特征补全逻辑提至 if/else 之后统一执行,
  通过 n_features_in_ 属性自动检测特征缺口并补齐
2026-07-28 15:31:49 +08:00
89b67fbd34 fix: ML Pipeline 缺失值填充 + 推理端外部模型 dict 兼容
- modeling_batch: Pipeline 首步新增 SimpleImputer(median) 填充 NaN
- inference_batch: 外部模型支持完整 dict(含 metadata/train_wavelengths),
  兼容旧版裸 Pipeline 对象
- step9_ml_predict_panel: 模型加载保留完整 dict 而非仅 model 对象,
  确保推理端可从 train_wavelengths 做光谱重采样
2026-07-28 14:59:11 +08:00
02592cc181 fix: 光谱提取 NaN/Inf 防护 + 边界掩膜支持
- 逐波段读取路径:防止 GDAL ReadAsArray 返回 NaN/Inf 污染光谱
- 批量读取路径:astype(float64) 后统一过滤非法值
- 两路径均支持 boundary_mask:水体之外光谱置零
2026-07-28 14:59:07 +08:00
46c5e51772 fix: BIP→BSQ 转换数据丢失修复 + 只读文件处理
- 延迟删除原始文件:先验证临时 BSQ 文件存在且非空,再删原始数据
- 异常处理不再删除临时 BSQ:保留完好副本供手动恢复
- 新增 _make_writable() 移除只读属性,避免 Windows PermissionError
- 清理孤儿 .aux.xml 临时文件
- 路径变量提前声明,保证异常处理块可访问
2026-07-28 14:59:03 +08:00
c16cddf384 feat: 推理端自动波长回填+linspace子采样(旧模型兼容) 2026-07-27 18:08:37 +08:00
92e8c90370 feat: ML模型波长记忆 + 跨传感器光谱重采样 + 数据加载规范化
训练端 (modeling_batch.py):
- _extract_train_wavelengths() 从列名提取训练波长写入 metadata
- load_data_batch/load_data_single 改为基于列名语义智能提取特征
- 废除 feature_start_column 硬编码位置索引

推理端 (inference_batch.py):
- preprocess_spectra() 分支A: train_wavelengths存在→np.interp精确重采样
- preprocess_spectra() 分支B: 无波长元数据→解析列名→np.interp到400-800nm标准网格
- 废除暴力截断和零值填充
2026-07-27 14:52:30 +08:00
99aeab3076 refactor: 中间层消除硬编码波段号+CRS动态探测
- GlintRemovalStep/handler/service 全部改为传递波长参数而非波段索引
- ContentMapper 移除 EPSG:32651 硬编码, 新增 _ensure_crs() + _probe_crs_from_file()
- _prepare_shared_context 在 read_csv_data 前先探测边界文件 CRS
- GUI QSpinBox→QDoubleSpinBox,默认值改为波长(nm),placeholder 提示自动探测
- 消除 38/36/49/47/25/37/65/91 等所有魔法数字
2026-07-27 14:52:23 +08:00
f6d61693e3 refactor: 耀斑去除算法从波段索引驱动改造为波长驱动
- Kutser/Hedley/Goodman 的 __init__ 参数从硬编码波段号改为目标波长(nm)
- 内部通过 find_band_number() 从 HDR 元数据动态解析波段索引
- Goodman 消除 P0 不一致: 25/37 vs 65/91 统一为 641.93/751.49nm
- 彻底解除传感器耦合,任何传感器均可自动适配
2026-07-27 14:52:15 +08:00
b609a64811 fix: 多项稳定性修复与打包优化
- PyInstaller: runtime hook 预加载 osgeo DLL 避免 Qt5 符号冲突;
  spec 纳入 osgeo .py 文件 + 运行时 DLL 依赖 (ffi/lzma/bz2/expat/sqlite3);
  移除 DLL 双副本防止 segfault
- GDAL 环境: 新增 _MEIPASS/osgeo/data/gdal 路径搜索,改善打包后 GDAL_DATA 检测
- 预览生成器: 掩膜与底图同分辨率时跳过 Warp 加速读取
- 专题图: ConvexHull 坐标中心化修复 UTM 大坐标精度退化;
  边界采样兜底防止外扩点为空;
  TIFF 已含 NaN 掩膜时跳过矢量擦除;
  形态学闭运算填充掩膜小孔洞
- NDWI: int16→float32 防止减法溢出
- 面板注册表: 步骤模块分组重构 (模块一/二/三/四重划分)
2026-07-26 20:35:50 +08:00
0bd54d5fdd feat(step3): auto BIP→BSQ conversion with metadata preservation
- _ensure_bsq(): 检测 BIP 格式并自动调用 gdal.Translate 转换为 BSQ
- 临时文件原地替换原名,所有下游步骤零感知
- 保留原始波长/波段名称等元数据到新 HDR
- 空间不足时跳过转换,回退 BIP-chunked 模式运行
2026-07-24 14:37:00 +08:00
2c637596eb perf(step3): Kutser BIP-chunked mode + auto interleave detection
- 新增 run_fast(): 自动检测 BIP/BSQ 格式,BIP 按行块读立方体(文件只读一遍)
- BSQ 模式逐波段顺序读取,均比原版块外波段内随机 IO 快 10-100 倍
- get_corrected_bands() 默认走 run_fast(),原版 run() 保留为低内存兼容
- 每 10 波段打印进度 + 预计剩余时间
2026-07-24 14:36:56 +08:00
beed27b1f0 perf(step11): IDW auto-switch for large grids + GDAL fast rasterize
- 网格 >500K 点自动跳过 Kriging 切 IDW(千万级点从数小时降至数秒)
- n_closest_points 50→20,Block 每块打印进度
- GDAL ReprojectImage 直接重采样栅格掩膜,避免 58K 多边形 rasterize 卡死
- 自动扫描工作目录 .dat 栅格,不依赖 handler 传参
- 特殊 Unicode 字符替换为 ASCII 兼容
2026-07-24 14:36:51 +08:00
9fa60e2995 fix: CRS 比较改用 GDAL osr.IsSame() 语义比对, 消除 WKT 格式差异误报
问题: 两组不同数据的 .hdr 文件由不同软件生成,
  同一 UTM 投影的 WKT 字符串格式不同 (PROJCS 命名/参数精度),
  原 validate_projections 用字符串比较会误报 CRS 不一致。

修复: 改用 gdal.osr.SpatialReference.IsSame() 语义比对:
  - 同一定义 → 通过 (即使 WKT 格式不同)
  - 真正不同 → 报错
  - osr 解析失败 → 回退字符串比较
2026-07-09 09:37:50 +08:00
830589c108 fix: 智能坐标列检测 — proj_x/proj_y 优先 + 双列名输出
问题: CSV 中 UTM 米坐标被命名为 longitude/latitude,
  列名与实际内容不符, 容易引发 CRS 配置错误。

修复:

1. map.py read_csv_data: 优先级智能列检测
   proj_x/proj_y → longitude/latitude → lon/lat → x_coord/y_coord
   → 终极回退按位置[0][1]。新旧文件全部兼容。

2. csv_processor.py: 输出双列名
   proj_x + proj_y (新标准, 优先)
   longitude + latitude (旧标准, 兼容)
   值完全相同, 新旧代码都能正确读取
2026-07-08 16:50:20 +08:00
3f023cffd4 fix: step11 移除 ProcessPoolExecutor 改用顺序生成避免 Windows spawn 死锁
问题: ProcessPoolExecutor 在 Windows spawn 模式下,每个 worker
  需重新导入 __main__ (water_quality_gui_v2.py) 及全部依赖
  (PyQt5, GDAL, rasterio...),启动极慢且极易因环境差异死锁。

修复: 改为顺序 for 循环,每个 CSV 直接在当前 WorkerThread 调用
  _process_one_map。每张图生成前发进度通知,完全透明。

时间预估: 16 块 × ~25s/块 ≈ 6-8 分钟/张,63 张 ≈ 6-8 小时。
  用户可随时看到进度,心跳线程持续保活防止超时误杀。
2026-07-08 14:18:40 +08:00
29a5aaa9ab fix: Goodman 全局裁剪反射率 ≥0,消除非水体像素的负值
问题: 光谱查看时发现起始波段和部分后续波段反射率为负值。
  物理上反射率不应为负。

根因: 之前的 np.maximum(R[water], 0) 仅裁剪了水体像素。
  非水体像素保留原始值,而大气校正在短波边缘波段
  (375-400nm) 和长波末端 (950-1000nm) 因低 SNR 常过校正
  产生负反射率。

修复: 在每个处理路径末尾添加全局 np.maximum(R, 0, out=R):
  - 行段跳跃模式: 水体校正后 + 全局裁剪
  - 全图+掩膜模式: 水体校正后 + 全局裁剪 (新增)
  - 无掩膜模式: 全局裁剪 (原有,不变)
  确保输出文件所有像素反射率 ≥ 0
2026-07-07 17:21:52 +08:00
c6e42c3d2f perf: Goodman 水体像素原地校正 + 行段跳跃大幅提速大尺度影像
核心优化 (消除 ~95% 的无效计算):

1. 原地校正 (in-place on water pixels only):
   旧: corrected = R - R_750 + A + B*diff (全图 86M 像素)
        np.where(water, corrected, R) (再分配 329MB)
   新: R[water] = R[water] - R_750[water] + A + B*diff[water]
        (仅水体像素, 零额外分配, 无 np.where)
   效果: 水体占 5% 时, 浮点运算减少 20×, 中间数组消除

2. 行段跳跃 (water row ranges):
   预计算含水行段, 纯陆地行直接跳过不做任何计算
   水域 < 50% 时自动启用 (_find_water_row_ranges)

3. SIMD 友好路径 (无掩膜时):
   np.subtract/add(..., out=R) 替代表达式
   避免临时中间数组, 利用 NumPy SIMD 向量化

预期效果 (6522×13215×150, 水体 10%):
  每波段耗时: ~62s → 估计 ~25-35s (计算部分加速 ~20×)
  IO 仍然占主导 (~15-20s 读+写 329MB)
2026-07-07 14:18:37 +08:00
d920863a0c fix: Goodman 流式处理 — 逐波段写入磁盘杜绝 OOM
问题: 6522×13215×150 大影像处理到第 142 波段时崩溃
  'Unable to allocate 329. MiB for an array'
  根因: _get_corrected_bands_gdal 将全部 150 个波段累积在
  corrected_bands 列表中 (≈46 GB),内存耗尽。

修复 (Goodman.py):
- _get_corrected_bands_gdal(): 新增 out_dataset 参数,
  流式模式下每处理完一个波段立即 WriteArray→FlushCache→del
- 新增 _get_corrected_bands_streaming(): 创建输出文件后
  调用流式处理,内存峰值 ≈ 3 波段 (NIR×2 + 当前) ≈ 1 GB
- get_corrected_bands(): output_path 已设置时自动走流式模式
- 原 _get_corrected_bands_numpy() 和 _gdal_mem() 的
  output_path=None 路径保持向后兼容

修复 (glint_removal_step.py):
- corrected_bands 为 None 时跳过 _save_bands_as_image
  (流式模式已直接写入磁盘)

性能微优化:
- corrected = R - R_750 → += self.A → += self.B*diff (原地)
- del R_640 提早释放
- WriteArray + FlushCache 确保数据及时落盘
2026-07-07 14:10:47 +08:00
2b5c77131e fix: BIP 格式兼容 — 鲁棒 HDR 查找 + find_band_number GDAL 回退
问题: 用户导入 3ref.bip 文件,step2 报 FileNotFoundError: 3ref.hdr 不存在。

根本原因: get_hdr_file_path() 只用 os.path.splitext()[0]+.hdr,
对于 3ref.bip 只查找 3ref.hdr,不兼容 3ref.bip.hdr 等其他命名规范。

修复内容:

**util.py (核心):**
- get_hdr_file_path(): 改为多候选路径查找(按优先级):
  3ref.hdr → 3ref.bip.hdr → 3ref.HDR → 3ref.bip.HDR
- find_band_number(): 三级回退 —
  1) ENVI .hdr 文件 → spectral 解析
  2) GDAL 元数据域 (ENVI/wavelength, WAVELENGTH_1..N)
  3) 线性估算 (假设 400-1000nm 或 400-2500nm)
- 新增 _read_wavelengths_from_gdal() 辅助函数

**同模式修复 (4 处):**
- get_spectral.py: get_hdr_file_path() 多候选
- get_spectral-test.py: 同上
- waterindex_inversion/__init__.py: 两处 hdr 构造均改为多候选
- sampling.py: 波长读取的 hdr 查找改为多候选
2026-07-07 10:32:02 +08:00