Commit Graph

295 Commits

Author SHA1 Message Date
a13b4be6d4 feat: formula report AI analysis per distribution map with configurable limit in UI panel 2026-07-09 15:03:39 +08:00
683cf77da5 fix: restore generic sections and AI analysis in formula report 2026-07-09 15:00:25 +08:00
a3e83e846a fix: remove leftover glint_analysis reference causing NameError 2026-07-09 14:52:50 +08:00
a572b52239 fix: formula report reads distribution maps from 12_visualization/distribution_maps/ instead of 11_Thematic_Map 2026-07-09 14:49:36 +08:00
5fe71917a6 fix: hyperspectral images section - skip missing instead of showing placeholder text, fix flight path rglob scope 2026-07-09 14:47:44 +08:00
f99fa63f0e fix: remove duplicate hyperspectral images and conflicting generic sections from formula report 2026-07-09 14:45:53 +08:00
8a7273dd57 fix: 公式报告进度步数用实际脉冲数而非原始数据量 (87→30)
旧: total_steps = 4 + 63 + 20 = 87 (但大部分步骤不发脉冲)
新: total_steps = 4 + 63//10 + 20 = 30 (与 _next 调用次数一致)
2026-07-09 13:27:47 +08:00
dd04ee323c fix: ML 报告进度增加 on_progress 回调 + 两条报告逻辑审查确认
ML 报告: 参数循环中加入 on_progress 回调,
  终端显示 ML报告 参数 3/13: Chlorophyll

审查结论:
  公式报告 (_generate_formula_report):
    数据源: 10_WaterIndex_CSV + 11_Thematic_Map + 1_water_mask
    不含 ML 内容 (无 scatter/boxplot/heatmap/R²/RMSE)
    进度: 数据驱动 (N_csv + N_maps + 4 固定)

  ML 报告 (_generate_ml_report):
    数据源: 12_visualization + 5_Data_Cleaning + 9_Concentration
    完整 ML 流程 (统计表→热力图→浓度→逐参数图表→总结)
    进度: 图片数 + 参数数 + 2
2026-07-09 13:26:09 +08:00
836c322b10 fix: 公式报告进度数据驱动 — 按实际 CSV/专题图数量计算总步数
旧: total_steps=7 硬编码, 看不到实际数据处理进度
新: 启动时扫描 10_WaterIndex_CSV 和 11_Thematic_Map,
  总步数 = 4(固定章节) + N_csv + N_maps,
  每处理 10% CSV 和每张专题图均更新进度,
  终端显示 统计 7/63 / 专题图 3/20 等实时信息
2026-07-09 13:21:32 +08:00
b88bab57ee fix: 公式报告 4 项修复 — 通用章节+AI接口+图片路径+标题数字
修复一: _generate_formula_report 封面后恢复
  _add_company_description_page/doc
  _add_data_acquisition_section/doc
  _add_data_processing_section/doc

修复二: _call_minimax_text / _call_minimax_vision
  URL 自动补齐 /chat/completions 后缀

修复三: _call_minimax_vision
  MIME 类型动态检测 (.png→image/png, 其他→image/jpeg)

修复四: _add_hyperspectral_images_section
  航线图搜索: 多路径 + rglob 递归查找
  去掉所有硬编码编号 (3.1/图3-2/3-3/3-4/3-5/3-6)
2026-07-09 13:12:14 +08:00
fc6036efb7 fix: _add_hyperspectral_images_section 去掉多余的 start_figure_num 参数 2026-07-09 11:53:23 +08:00
43d56e74d9 fix: _add_cover_page 参数对齐 + 确认 _generate_formula_report 无 ML 混入 2026-07-09 11:42:32 +08:00
650e505ac4 feat: 报告生成模块双轨制重构 — 公式模式 + ML 模式独立分流
**UI (step13_report_panel.py):**
  - 新增 QComboBox 报告模式选择:
    机器学习(ml) / 水色指数公式(formula)
  - WorkerThread 传递 report_mode 到 generate_report

**后端 (report_word.py):**
  - generate_report(report_mode='ml'/'formula') 参数路由
  - _generate_formula_report(): 水色指数专属结构
    1.项目背景 → 2.影像预处理 → 3.公式列表 →
    4.指数统计(10_WaterIndex_CSV) → 5.分布专题图(11_Thematic_Map)
    绝不包含模型训练/R²/RMSE 等 ML 内容
  - _generate_ml_report(): 原有 ML 逻辑完整保留
  - _analyze_statistics(): 全部改用 .get() 防御式读取
    兼容 参数/Parameter/name, 点位数/数量/count,
    最小值/min, 最大值/max, 平均值/mean, 标准差/std
2026-07-09 11:33:50 +08:00
692a88c7bb fix: heatmap_path 提前声明避免水色指数模式下 UnboundLocalError 2026-07-09 11:22:50 +08:00
1740425152 fix: 报告水色指数模式 stats_data 补全缺失的统计字段
水色指数模式 stats_data 只复制了 参数/点位数,
  缺少 最小值/最大值/平均值/标准差 →
  _analyze_statistics 访问时 KeyError: '最小值'
2026-07-09 11:17:54 +08:00
303f202547 fix: read_csv_data 含量列动态识别 — 排除所有坐标列名
问题: CSV 格式 [proj_x, proj_y, longitude, latitude, BGA_Am09KBBI],
  旧逻辑 coord_cols={proj_x,proj_y} 只排除识别的坐标列,
  longitude 被错误提取为含量列 → Kriging 用坐标值插值

修复: 扩展排除集合包含全部已知坐标列名:
  proj_x/proj_y, longitude/latitude, x/y, lon/lat,
  x_coord/y_coord, pixel_x/pixel_y, geometry, uncertainty
  兜底取最后一列
2026-07-09 09:56:03 +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
1d051c9fec feat: 报告生成器自动检测水色指数模式 — 支持非 ML 管线
问题: 报告模板仅适配 ML 管线 (steps 5-9+13),
  用户跑 1,2,3,4,10,11,12 (水色指数公式管线) 时大量显示
  [图片未找到]。

修复: 自动模式检测 + 分支逻辑

  1. _detect_pipeline_mode(): 检测 scatter_with_confidence 文件
     不存在 → 水色指数模式

  2. _get_available_image_types(): 扫描实际存在的图片类型,
     只报告确实生成的内容

  3. 统计表格: ML→5_Data_Cleaning, 水色指数→10_WaterIndex_CSV
     水色指数模式遍历所有公式 CSV 生成统计汇总表

  4. 相关性热力图: 水色指数模式跳过并注明原因

  5. 分布图: 利用已有 fallback 从 11_Thematic_Map 读取
2026-07-09 09:31:58 +08:00
d18da41505 fix: step13 统一使用 12_visualization 目录名
问题: step13_service.py 检查 14_visualization 但 report_word.py
  内部写死使用 12_visualization, 目录名不一致导致新服务报
  '可视化目录不存在'。

修复: step13_service.py 全部 14_visualization → 12_visualization
2026-07-09 09:25:14 +08:00
f496b28c8c fix: 回退 setWidgetResizable(False) 恢复图像显示 + 保留滚动条策略修复
setWidgetResizable(False)+QSizePolicy.Ignored 导致 QLabel
  无有效 sizeHint, 图像不显示。

回退为 setWidgetResizable(True), 保留显式 ScrollBarAsNeeded
  策略 (原默认可能为 AlwaysOff 导致滚动条异常)。
2026-07-08 17:55:34 +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
f996353b37 fix: 克里金矩阵鲁棒性 — 去重重叠点 + nugget 防奇异化
问题: 4326 点仍崩溃回退 IDW,原因两个:
  1. 采样点中存在空间完全重叠点 → 协方差矩阵行列式为 0
  2. 坐标范围出现负数 (X=-793275) → 经/纬度传反,空间距离扭曲

修复:
  1. read_csv_data: drop_duplicates(subset=['proj_x','proj_y'])
     坐标 <0.01m 的重叠点只保留首个,防止矩阵奇异化
  2. OrdinaryKriging (2处): nugget=1e-6
     微小固有方差强制打破矩阵奇异性,病态矩阵仍可求逆
2026-07-08 16:29:33 +08:00
7921e21750 fix: step4 采样点过密 — 提高自适应下限 + 默认关闭自适应模式
问题: min_interval=10 导致每个水面产生数千个密集采样点 (8242点)

修复:
  1. sampling.py: min_interval 默认值 10→50 (提高 5×)
     两个入口函数同步修改 (chunked + full)
  2. step4_sampling_panel.py: 自适应采样复选框默认 False
     用户设置的固定间隔 (interval=100px) 真正生效
2026-07-08 16:03:42 +08:00
197f58c187 fix: step12 图像预览横向滚动条被挤出视口底部
根因: QScrollArea.setWidgetResizable(True) 强制 QLabel 填充视口,
  与缩放后 QLabel 的 pixmap 固有尺寸冲突,滚动条布局异常。

修复:
  1. setWidgetResizable(False) — QLabel 保持 pixmap 自然尺寸
  2. QScrollArea SizePolicy → Expanding×Expanding (受父布局约束)
  3. ScrollBarPolicy → AsNeeded (显式设置)
  4. QLabel SizePolicy → Ignored (按内容自适应大小)

缩放行为:
  - 放大超出视口 → 自动出现滚动条
  - 缩小/适应窗口 → QLabel 自动缩至 pixmap 尺寸, 无滚动条
2026-07-08 15:24:44 +08:00
757741c6a9 fix: 掩膜改用 rasterio.features.rasterize 全分辨率 C 级光栅化
问题: 降采样+np.kron 暴力放大的旧逻辑导致出图边缘出现
  14×14 像素马赛克锯齿。

修复: 彻底删除 _MASK_TARGET_POINTS/_mask_step/shapely.prepared/
  np.kron 升采样链,替换为 rasterio C 底层光栅化:
  - from_bounds 精确计算仿射矩阵
  - rasterize(shapes, out_shape, transform) C 级瞬间盖章
  - flipud 处理 Y 轴对齐
  - 输出 uint8 bool 掩膜, 无降采样无锯齿
2026-07-08 15:02:30 +08:00
e0c6b63446 perf: shapely.prepared.prep 加速掩膜空间查询 — 22亿次测试秒级完成
问题: mask_gdf.within(boundary_gdf.unary_union) 对 5.3万点 × 4.1万
  复杂多边形的暴力相交测试,计算量高达 22 亿次,单核假死。

修复: 使用 shapely.prepared.prep 预编译几何体:
  - union_poly = boundary_gdf.unary_union (执行一次)
  - prepared_poly = prep(union_poly) (预编译为 C 级空间索引)
  - [prepared_poly.contains(Point(x,y)) for ...] (加速 100×+)

  同时省去 GeoDataFrame 构造开销,直接用 numpy mask_pts
  迭代生成 Point 对象并查询。
2026-07-08 14:35:11 +08:00
b8f0625bd9 fix: 掩膜降采样计算 — 杜绝 1m 分辨率下 10.5M Point 对象 OOM/假死
真凶定位: prepare_shared_context 第 2982-2988 行
  grid_xx.ravel() → 10.5M 坐标点
  [Point(x,y) for ...] → 创建 10.5M 个 shapely Point (~2 GB)
  within(boundary.unary_union) → 10.5M 次几何测试 O(N×M)
  → 此处假死/卡死/内存爆炸,Kriging 从未开始执行

修复: 掩膜降采样到 ~50K 点 (约 100m 等效分辨率) 计算,
  结果通过 np.kron 升采样回原始分辨率。
  掩膜是布尔值,精度损失可忽略;
  np.kron 的 C 向量化升采样 <1 秒。
2026-07-08 14:22:34 +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
9512cab807 fix: 移除 _local_kriging 内部 multiprocessing.Pool 避免 Windows 嵌套 spawn 死锁
根因: step11 用 ProcessPoolExecutor 派发 CSV 到子进程,
  子进程内 ContentMapper.process_data → _local_kriging 又创建
  multiprocessing.Pool。Windows spawn 模式下嵌套 Pool 死锁。

修复: _local_kriging 内部改为顺序执行 16 个块。
  每块 ~12s (650K 网格点 × 50 近邻), 总计 ~3 分钟, 完全可接受。
  step11 层的 ProcessPoolExecutor 仍提供 CSV 级并行。
2026-07-08 14:09:54 +08:00
4469d6462d fix: 修复 _local_krige_block 单块调用时参数未 unpack 的 bug 2026-07-08 14:01:29 +08:00
237fcba647 perf: 自适应分块 + 40% 重叠缓冲 + n_closest=50 优化局部克里金
用户需求: 500m 固定分块(50块)容易碎片化, 稀疏区域易受异常值污染

修复 (3项):

1. 自适应块大小: 根据 extent 自动切分为 ~4×4 块(16块),
   纵横比极端时自动在长边增加 1 块。
   每块面积足够大, 囊括更多采样点特征。

2. 40% 重叠缓冲区: 采样点搜索范围为块长宽的 1.4×,
   相邻块交界处使用极度重叠的采样点 → 消除拼缝断层。
   网格点(grid_x/y)仍使用严格不重叠块范围 → 无冗余计算。

3. n_closest_points=50: 50 个近邻稀释极端异常值影响,
   比之前 15 个更稳健, 且协方差矩阵 50×50 仍远小于全局 8242×8242。
2026-07-08 14:00:59 +08:00
7fbc613ded fix: 局部 Kriging 网格范围使用缓冲区导致 10x 冗余计算卡死
问题: sub_grid_x/y 使用了缓冲区范围 (block+500m) 而非块范围,
  导致每个 500m 块的网格点从 ~457x460 膨胀到 ~1500x1500 (9x),
  50 个块合计需计算 1.12 亿次 kriging, 任务构建阶段就卡死。

修复: sub_grid_x/y 改为仅使用块自身范围,
  缓冲区仅用于筛选局部采样点。
  每个网格点只计算一次, 总计算量 = 实际网格点数 (10.5M)。
2026-07-08 13:14:40 +08:00
bdac7873f4 fix: 修复 ContentMapper 类体被模块级函数截断的严重 bug
问题: 批量生成专题图时报 'ContentMapper' object has no attribute 'process_data'

根因: _local_krige_block_worker (0空格缩进,模块级) 被错误地插入在
  ContentMapper 类体中间 (line 819), 导致类定义在此处终止。
  之后 19 个方法 (_idw_interpolation, read_csv_data, create_content_map,
  visualize_raster, prepare_shared_context, process_data, process_batch...)
  全部脱离类变成模块级函数。
  AST 验证: ContentMapper 仅剩 9 个方法。

修复: 将 _local_krige_block_worker 移至文件末尾 (模块级正确位置),
  ContentMapper 恢复为 28 个方法的完整类。
2026-07-08 13:02:03 +08:00
8f03dcb10b perf: Local Kriging 500m spatial window + multiprocessing
Local Kriging: grid split into 500m blocks, each block uses only
sample points within block+buffer zone. Covariance matrix shrinks
from 8242x8242 to local nxn. 10M+ grid cells now finish in minutes.

Complexity:
  Global: O(G * P * logP * K^3) = hours for large grids
  Local:  O(sum(block*grid * block*points * 15^3)) = minutes
2026-07-08 12:03:46 +08:00
e0b9257b56 perf: Kriging 多进程分块 + C 后端自动检测 — 1m 分辨率千层级网格加速
问题: 用户需要 1m 分辨率 (2285×4600 = 1051万网格点),
  单进程 loop 后端需要数小时。

修复 (三层加速):

1. C 后端自动检测 (_detect_kriging_backend):
   - 检测 pykrige 是否有编译的 C 扩展 (conda-forge 默认有)
   - 可用时 C 后端比 loop 快 50-100×
   - 不可用时回退 loop + 多进程补偿

2. 多进程分块 (_krige_chunked):
   - 网格 >100 万点时自动启用
   - 沿 Y 轴将网格拆分为 N 个 stripe (N=CPU核数,上限8)
   - 每个 worker 独立运行 OrdinaryKriging.execute()
   - 结果沿 Y 轴 vstack 拼接

3. 进度可见:
   - 每个 worker 启动时打印 '[Krige Worker] 分块 X/N'
   - 完成后立即打印完成消息
   - 主进程打印拼接结果维度

预期性能 (8核, C后端, 1051万网格点):
  单进程 loop: ~3-5 小时
  8进程 + C: ~1-3 分钟 (loop后端 ~15-30 分钟)
2026-07-08 11:34:14 +08:00
212772caf4 fix: Kriging 大网格自动拦截 — 防止百万级网格点导致无限卡死
问题: 用户将 step11 分辨率设为 1m,生成 2285×4600 = 1051万
  网格点。pykrige OrdinaryKriging.execute() 在此规模上需要数小时,
  且期间无任何进度输出,看起来像程序卡死。

根因: 原代码对网格规模无上限约束。

修复 (create_interpolation_grid):
  - 推荐上限: 500K 网格点 (~30-120s)
  - 绝对上限: 2000K 网格点 (超出直接抛错,建议提升分辨率)
  - 超推荐上限时自动按比例提升分辨率: 2285×4600 → ~505×1016
  - 打印明确的网格点数统计和 ETA 提示
  - 网格自动缩减后日志: '调整后网格: N×M = X 个网格点'
2026-07-08 11:28:25 +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
796d36c093 fix: 修复 TIF 缺失 CRS 时 fallback 到 WGS84 导致掩膜擦除全部像元的 bug
问题: step11 日志反复出现:
  '栅格 TIF 缺失坐标系,默认按 WGS84 渲染'
  '擦除后有效像元: 0/15300'

根因: step10 生成的 GeoTIFF 缺少 CRS (可能是 PROJ 异常时生成的)。
  visualize_raster 中当 crs_obj 为 None 时无条件回退到 EPSG:4326 (WGS84),
  但 transform 实际是 UTM 米坐标 (e.g. x≈470000, y≈2200000)。
  UTM 度坐标的掩膜被 reproject 到 WGS84 后,
  与仍在 UTM 米坐标的 transform 彻底错位 → geometry_mask 全部为 False
  → 0 个有效像元。

修复: 当 crs_obj 为 None 且 boundary 有投影 CRS 时,
  检查 transform 的 X 坐标量级: 若 >180 则必定是投影坐标 (UTM 等),
  直接使用 boundary 的 CRS 而非 WGS84 回退。
  仅当坐标量级 <180 时才回退到 WGS84 (真经纬度)。
2026-07-07 16:51:33 +08:00
6b6e593fa5 fix: 修复 pip wheel GDAL 的 proj.db 未被找到的 bug
根因: pip 安装的 GDAL ≥3.5 把 proj.db 放在
  {site-packages}/osgeo/data/proj/proj.db
而之前的代码将其加入 prefixes 列表后,搜索循环只查找
  {prefix}/Library/share/proj/ 子目录,
导致 site-packages 中的 v6+ proj.db 被漏掉。

修复:
  1. 新增独立搜索步骤:直接扫描 site-packages 下
     osgeo/data/proj/ 和 osgeo/proj_data/ 目录
  2. 找到 v6+ 版本后直接设为 proj_lib,
     不再经过前缀子目录搜索(优先级最高)
  3. 同时扫描 site-packages/fiona/proj_data/,
     找到旧版时打印明确路径警告,
     确认 PROJ_DATA 已覆盖该旧版
  4. 仅 site-packages 未找到时回退到前缀搜索
2026-07-07 16:47:02 +08:00
721370f3e5 fix: 新增 PROJ_DATA 环境变量 + 屏蔽 Fiona 内部旧版 PROJ 数据库
问题: visualize_raster 阶段 fiona 库内部捆绑了老旧的 PROJ 数据库
  (VERSION.MINOR=2),导致读取掩膜时投影错乱 — 底图 51N 被误解析为
  49N,矢量掩膜物理擦除时把所有水体当成陆地,有效像元变成 0/15300。

修复:
  1. 新增 os.environ['PROJ_DATA'] = proj_lib
     Fiona ≥1.4 / Rasterio ≥1.4 / GDAL ≥3.5 / PROJ ≥8 优先读取此变量

  2. PROJ_NETWORK = OFF — 禁用 PROJ 网络下载,避免意外行为

  3. 打印明确日志确认全进程 PROJ 来源统一

效果: 所有空间组件(GDAL / PROJ / Fiona / Rasterio / pyproj)
  强制使用同一套 conda 环境下的 proj.db (v6+),彻底杜绝 fiona 内部
  捆绑的 v2 旧版干扰。
2026-07-07 16:40:51 +08:00
da773b139f fix: 彻底解决 PROJ 版本冲突 — 深度搜索 conda env + pip wheel 中的 proj.db
问题: step11 所有内容报错:
  'C:\ITRES\ATK\app\GDAL\projlib\proj.db contains
   DATABASE.LAYOUT.VERSION.MINOR = 2 whereas a number >= 6 is expected'
原因: 系统有旧版 GDAL (C:\ITRES\ATK\app\GDAL), 其 proj.db 为 v2,
      GDAL 内部搜索先找到旧版 → PROJ 坐标转换全部失败

增强的搜索策略 (由简到深):
  1. Python 可执行文件位置回推 prefix (最可靠)
  2. sys.path 中 site-packages 回推所有 conda/env 前缀
  3. CONDA_PREFIX + sys.prefix + CONDA_ROOT
  4. PATH 中推断 conda 安装路径
  5. 硬编码已知路径
  6. 扫描 conda 根下的 envs/*/ 所有环境
  7. pip wheel 路径: {site-packages}/osgeo/data/proj/

新增 proj.db 版本检测:
  - _check_proj_db_version(): 读取 SQLite 头,
    通过文件大小判定 v6+ (>3MB) 或 v2 (<3MB)
  - 仅选择 v6+ 的 proj.db 设置 PROJ_LIB

新增递归搜索:
  - _find_proj_in_prefix(): 在 prefix 下深度搜索 Library/share/proj/
    限制深度 6 层避免全盘扫描

找不到时打印明确的修复建议:
  '请手动设置: set PROJ_LIB=<conda环境>\Library\share\proj'
2026-07-07 16:23:38 +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
d6057c3d70 fix: 大尺度影像处理超时 — 心跳线程 + 日志复位看门狗 + 延长至1小时
问题: 用户处理 6522x13215x150 大影像,step3 Goodman 逐波段
C/NumPy 运算耗时超过 10 分钟,期间无 Qt 信号发出,看门狗误判
为假死并强制终止。

修复 (三层防护):

1. WorkerThread 心跳线程:
   - 新增独立 daemon 线程,每 30s 发射 log_message 信号
   - PyQt 信号线程安全,跨线程 emit 自动排队到主线程
   - 确保长时间 C 运算期间看门狗持续收到保活信号
   - run() finally 中自动停止心跳

2. 日志消息也复位看门狗:
   _on_log_message() 现在与 _on_progress_update() 一样更新
   _last_progress_time —— 任何来自 Worker 的通讯都代表存活

3. 超时阈值 600s → 3600s (1小时):
   大尺度影像处理 1 小时足够覆盖极端场景
2026-07-07 11:31:22 +08:00
a03798418f fix: 增强 PROJ/GDAL 环境检测 — 多级回退搜索策略
问题: sys.prefix 指向 test_env,该环境不含 GDAL/PROJ 数据文件,
导致 _find_and_set_gdal_env() 找不到 proj.db。

修复: 按优先级扩展搜索路径:
  1. CONDA_PREFIX 环境变量(当前激活的 Conda 环境)
  2. sys.prefix(Python 安装前缀)
  3. CONDA_ROOT / MAMBA_ROOT_PREFIX
  4. 从 PATH 推断 conda 安装根目录
  5. 硬编码回退(WQ_GUI 路径 + 常见目录 ProgramData)
每个前缀尝试 Windows (Library/share/...) 和 Linux (share/...) 子目录。
找不到时打印已搜索前缀数量供用户诊断。
2026-07-07 10:38:16 +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
1f6c81158b fix: 动态查找 PROJ/GDAL 路径 + Warp 1 像素浮点舍入容错
**water_quality_gui_v2.py:**
- 废弃硬编码 conda_env 路径
- 新增 _find_and_set_gdal_env(): 基于 sys.prefix 自动推断
  Library/share/proj 和 Library/share/gdal,验证 proj.db 存在后
  强设 PROJ_LIB 和 GDAL_DATA 环境变量
- 兼容 Windows (Library/share/...) 和 Linux/Mac (share/...) 路径

**preview_generator.py _warp_mask_to_image():**
- 尺寸验证从严格相等改为 ±2 像素容差
- 新增 _snap_array_to_shape(): warp 输出与 target_shape
  相差 ≤2px 时自动 slice(截断)或 pad(填充),不再回退到盲读模式
- 解决坐标系转换浮点舍入导致的 1048x522 vs 1047x521 问题
2026-07-07 10:01:31 +08:00
78818bbc50 fix: 在 water_quality_gui_v2.py 顶部强制绑定 Conda 环境的 GDAL/PROJ 路径,防止系统环境变量干扰 2026-07-07 09:55:53 +08:00
826f110894 fix: 防御性编程重构 — 栅格空间对齐 + NoData 处理 + 高危代码加固
**核心修复 (preview_generator.py):**
- 废弃 _align_mask_to_image() (numpy crop/pad 在地理空间上错误)
- 新增 _warp_mask_to_image(): 使用 gdal.Warp 将掩膜重采样到与底图
  完全一致的像素网格,处理旋转/偏移/投影差异
- 新增 _normalize_mask(nodata_value): 正确的掩膜值域自劢归一间 (0/1 vs 0/255)
- 修复 alpha = mask_data/255.0 → mask_data (掩膜是二值 0/1, 不是 0/255)
- 面积计算 valid_pixels 使用 warp 前数据排除 nodata 背景

**新增防御工具模块:**
- spatial_validator.py: SpatialAlignmentError 自定义异常 +
  validate_two_rasters() / validate_spatial_alignment() 强制空间一致性检查 +
  has_rotation() / get_pixel_resolution() 诊断工具
- nodata_handler.py: read_band_safe() / read_bands_safe() 自动 NoData→nan +
  read_band_masked() 返回 MaskedArray + create_valid_mask()

**P0 高危代码集成 (3处):**
- sampling.py: 耀斑掩膜与水体掩膜 bool 运算前验证 numpy shape 一致
- find_severe_glint_area.py: 栅格 mask 读取后验证 dims+GT+projection 对齐
- waterindex_inversion/__init__.py: mask 与 BSQ 维度验证, 不一致时优雅降级

**旋转影像兼容性修复:**
- extract_water_area.py: pixel_size = sqrt(gt[1]^2+gt[2]^2) 使用勾股定理
  正确计算旋转影像的像素分辨率
2026-07-07 09:10:40 +08:00
c46f78e69d fix: 补齐缺失的 handler 文件 + IDW 插值退化检测 + V1 代码归档 2026-07-06 15:57:29 +08:00