Initial commit of WQ_GUI

This commit is contained in:
2026-04-08 15:25:08 +08:00
commit 91e36407ae
302 changed files with 40872 additions and 0 deletions

157
.gitignore vendored Normal file
View File

@ -0,0 +1,157 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Project specific
# Build directories
V1/
V2/
dist/
build/
# Data files (keep structure but ignore large files)
data/input/*
data/output/*
data/models/*
!data/input/.gitkeep
!data/output/.gitkeep
!data/models/.gitkeep
# Log files
*.log
logs/
# Temporary files
*.tmp
*.temp
temp/
tmp/
# Backup files
*.bak
*.backup
*~

186
IMAGE_CONFIG.md Normal file
View File

@ -0,0 +1,186 @@
# Logo和横幅图像配置说明
## 功能概述
本更新实现了以下需求:
1. **Logo在菜单栏左侧** - 和菜单栏在**同一行**
2. **菜单栏在最右侧** - 使用弹性空间布局
3. **横幅图片撑满整个区域** - 自适应窗口宽度
## 布局说明
```
┌─────────────────────────────────────┐
│ [Logo] 文件 工具 帮助 │ ← 同一行Logo在左菜单在右
├─────────────────────────────────────┤
│ [软件名称横幅图 - 撑满宽度] │ ← 横幅撑满整个区域
├─────────────────────────────────────┤
│ │
│ 主要内容区 │
│ │
```
## 配置步骤
### 1. 准备图像文件
将你的图像文件放在项目目录中。建议的目录结构:
```
fengzhuang-ui2/
├── assets/ # 推荐:创建资源目录
│ ├── logo.png # 公司logo (建议高度30像素)
│ └── banner.png # 软件名称横幅 (建议高度70像素)
├── src/
│ └── gui/
│ └── water_quality_gui.py
└── ...
```
### 2. 修改图像路径
打开 `src/gui/water_quality_gui.py` 文件,找到以下两处代码:
#### Logo路径修改create_logo_bar 方法中第3928行左右
```python
# 修改前:
logo_path = "path/to/your/logo.png"
# 修改后(使用相对路径):
logo_path = "assets/logo.png"
# 或使用绝对路径:
logo_path = r"E:\your\full\path\to\logo.png"
```
#### 横幅路径修改create_banner_widget 方法中第3978行左右
```python
# 修改前:
banner_path = "path/to/your/banner.png"
# 修改后(使用相对路径):
banner_path = "assets/banner.png"
# 或使用绝对路径:
banner_path = r"E:\your\full\path\to\banner.png"
```
### 3. 图像规格建议
| 名称 | 建议尺寸 | 格式 | 说明 |
|-----|--------|------|------|
| Logo | 高度30像素 | PNG/JPG | 放在顶部Logo栏中自动按高度缩放保持宽高比 |
| 横幅 | 高度70像素 | PNG/JPG | 占据横幅区域,自动按高度缩放保持宽高比 |
### 4. 图像加载失败处理
如果图像文件未找到或加载失败,系统会自动显示占位符:
- **Logo占位符**: 显示"Logo"文本,背景为浅灰色
- **横幅占位符**: 显示"软件名称横幅"文本背景为蓝色字体为24号加粗
### 5. 自适应缩放说明
为了避免图像拉伸,代码使用了 `scaledToHeight()` 方法:
- Logo按高度30像素缩放自动计算宽度保持原始宽高比
- 横幅按高度70像素缩放自动计算宽度保持原始宽高比
这样可以确保无论原始图像大小如何,都能自然地显示而不会出现拉伸变形。
## 常见问题
### Q: 如何使用项目内的图像资源?
**A**: 在项目中创建 `assets``resources` 文件夹,并使用相对路径:
```python
# 假设项目结构:
# fengzhuang-ui2/
# ├── assets/
# │ ├── logo.png
# │ └── banner.png
# └── src/gui/water_quality_gui.py
# 在 water_quality_gui.py 中第3928行
logo_path = "assets/logo.png"
# 在 water_quality_gui.py 中第3978行
banner_path = "assets/banner.png"
```
### Q: Logo或横幅大小不合适
**A**: 修改以下代码调整显示大小:
```python
# 在 create_logo_bar() 方法中调整Logo大小
logo_label.setFixedSize(60, 40) # 改为 60×40
# 在 create_banner_widget() 方法中调整横幅大小
banner_label.setMaximumHeight(100) # 改为100像素高
banner_label.setMinimumHeight(80) # 改为最小80像素高
# 调整缩放高度
scaled_pixmap = logo_pixmap.scaledToHeight(35, Qt.SmoothTransformation) # 改为35
scaled_pixmap = banner_pixmap.scaledToHeight(85, Qt.SmoothTransformation) # 改为85
```
### Q: 如何隐藏Logo或横幅
**A**: 在 `init_ui()` 方法中注释掉相应的创建方法:
```python
# 在 init_ui() 中
# self.create_logo_bar() # 注释此行隐藏Logo
# self.create_banner_widget() # 注释此行隐藏横幅
```
### Q: Logo显示位置不对
**A**: Logo栏是作为独立的工具栏添加在菜单栏下方不是在菜单栏内。当前的布局顺序是
1. 菜单栏 (最上方)
2. Logo栏 (菜单栏下方)
3. 横幅区域 (Logo栏下方)
4. 主内容区域 (最下方)
### Q: 图像在高分辨率屏幕上看起来模糊?
**A**: 使用 `Qt.SmoothTransformation` 可以改善图像质量。如果仍然不够清晰,可以准备高分辨率的原始图像。
## 代码位置
- **Logo栏创建**: `create_logo_bar()` 方法 (第3902行)
- **横幅区域创建**: `create_banner_widget()` 方法 (第3950行)
- **主UI初始化**: `init_ui()` 方法 (第3821行)
## 支持的图像格式
- PNG (推荐,支持透明背景)
- JPG/JPEG
- BMP
- GIF
- TIFF
## 样式调整
如需修改样式(背景色、边框等),编辑以下位置的 `setStyleSheet()` 调用:
```python
# Logo样式第3907-3916行
logo_toolbar.setStyleSheet("""...""")
# 占位符样式第3931行
logo_label.setStyleSheet("...")
# 横幅占位符样式第3959-3965行
banner_label.setStyleSheet("""...""")
```
## 更新日期
2026-03-27
## 备注
所有的图像路径都可以根据你的实际项目结构灵活调整。建议将图像文件与代码一起版本控制,以确保项目的可维护性。

152
README-conda.md Normal file
View File

@ -0,0 +1,152 @@
# 水质参数反演分析系统 - Conda环境安装指南
## 📋 概述
本项目提供完整的Conda环境配置支持一键安装所有依赖包。
## 🚀 快速开始
### 方法1: 使用环境配置文件 (推荐)
```bash
# 1. 克隆或下载项目
# 2. 进入项目目录
cd fengzhuang
# 3. 创建Conda环境 (自动安装所有依赖)
conda env create -f environment.yml
# 4. 激活环境
conda activate water_quality_analysis
# 5. 运行程序
python src/gui/water_quality_gui.py
```
### 方法2: 使用批处理脚本 (Windows)
```cmd
# 双击运行或在命令行执行
scripts\setup_conda_env.bat
```
### 方法3: 手动安装
```bash
# 创建环境
conda create -n water_quality_analysis python=3.8
# 激活环境
conda activate water_quality_analysis
# 安装依赖包
conda install -c conda-forge --file requirements-conda.txt
```
## 📦 依赖包说明
### 核心依赖
- **Python 3.8+**: 运行环境
- **PyQt5**: GUI界面框架
- **NumPy, SciPy, Pandas**: 科学计算基础库
- **Scikit-learn**: 机器学习算法
- **XGBoost, LightGBM**: 梯度提升算法
### 地理空间处理
- **GDAL**: 地理数据处理
- **Rasterio**: 栅格数据处理
- **GeoPandas**: 地理数据分析
- **Shapely**: 几何运算
- **PyProj**: 坐标系转换
### 图像和可视化
- **OpenCV**: 计算机视觉
- **Pillow**: 图像处理
- **Matplotlib, Seaborn**: 数据可视化
- **Spectral**: 光谱数据处理
### 工具库
- **Joblib**: 并行计算
- **PyWavelets**: 小波变换
- **TQDM**: 进度条
- **PyYAML**: 配置处理
## 🔧 环境管理
### 更新环境
```bash
# 更新所有包到最新版本
conda env update -f environment.yml
```
### 删除环境
```bash
# 停用环境
conda deactivate
# 删除环境
conda env remove -n water_quality_analysis
```
### 导出环境
```bash
# 导出当前环境配置
conda env export > environment_export.yml
```
## 🐛 故障排除
### 常见问题
1. **Conda命令找不到**
- 确保已安装Miniconda或Anaconda
- 重启命令行窗口
2. **包安装失败**
- 检查网络连接
- 尝试更换conda源: `conda config --add channels conda-forge`
3. **环境激活失败**
- Windows: 使用 `conda activate water_quality_analysis` (非 `activate`)
- Linux/Mac: 确保conda已正确初始化
4. **PyQt5显示问题**
- Linux: 安装系统依赖 `sudo apt-get install qt5-default`
- Mac: 确保XQuartz已安装
### 验证安装
运行以下Python代码验证安装:
```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import sklearn
import PyQt5
import gdal
import rasterio
import geopandas
print("所有依赖包安装成功!")
```
## 📚 相关链接
- [Conda官方文档](https://docs.conda.io/)
- [Miniconda下载](https://docs.conda.io/en/latest/miniconda.html)
- [Anaconda下载](https://www.anaconda.com/products/distribution)
## 📞 技术支持
如遇问题,请检查:
1. Conda版本是否为最新
2. Python版本是否符合要求
3. 系统是否满足硬件要求

215
README.md Normal file
View File

@ -0,0 +1,215 @@
# 水质参数反演分析系统 (Water Quality Inversion Analysis System)
[![Python Version](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Build Status](https://img.shields.io/badge/build-passing-green.svg)]()
基于遥感影像处理和机器学习技术的水质监测专业软件系统,集成了完整的水域识别、耀斑处理、光谱提取、模型训练和预测分析流程。
## 🚀 主要特性
- **多算法耀斑去除** - 支持Goodman、Kutser、Hedley、SUGAR等多种去耀斑算法
- **智能水域识别** - 基于NDWI阈值分割和Shapefile掩膜的自动水域提取
- **机器学习建模** - 支持多种机器学习算法随机森林、XGBoost、神经网络等
- **非经验统计回归** - 基于物理原理的叶绿素a、总氮、总磷等参数反演
- **高精度空间插值** - 距离扩散插值生成平滑的水质分布图
- **可视化分析** - 丰富的图表展示和空间分布可视化
- **用户友好界面** - 基于PyQt5的图形化操作界面
## 📋 系统要求
### 硬件要求
- **处理器**: Intel Core i5 或同等性能以上
- **内存**: 8GB RAM推荐16GB
- **存储**: 至少10GB可用空间
- **显卡**: 支持OpenGL 3.0以上
### 软件要求
- **操作系统**: Windows 10/11, Linux, macOS
- **Python版本**: 3.8+
- **必要依赖**: GDAL, NumPy, Pandas, Scikit-learn, PyQt5等
## 🛠️ 安装
### 方式1从源码安装
```bash
# 克隆仓库
git clone https://github.com/waterquality/water-quality-inversion.git
cd water-quality-inversion
# 创建虚拟环境
python -m venv venv
source venv/bin/activate # Linux/macOS
# 或
venv\Scripts\activate # Windows
# 安装依赖
pip install -r requirements.txt
# 安装包
pip install -e .
```
### 方式2使用pip安装
```bash
pip install water-quality-inversion
```
## 🎯 快速开始
### 图形界面模式
```bash
water-quality-gui
```
### 命令行模式
```bash
water-quality-pipeline --config config.yaml
```
### Python API
```python
from water_quality_inversion import WaterQualityInversionPipeline
# 创建流水线实例
pipeline = WaterQualityInversionPipeline()
# 运行完整分析流程
pipeline.run()
```
## 📖 使用指南
### 基本工作流程
1. **步骤1: 水域掩膜生成**
- 支持Shapefile文件或NDWI自动提取
- 生成水域范围的栅格掩膜
2. **步骤2: 耀斑区域检测**
- 支持Otsu、Z-score、百分位数等多种检测方法
- 生成耀斑区域掩膜
3. **步骤3: 耀斑去除**
- Goodman、Kutser、Hedley、SUGAR四种算法
- 支持多种插值修复方法
4. **步骤4: 数据预处理**
- CSV数据清洗和异常值检测
- 数据标准化和特征工程
5. **步骤5: 光谱提取**
- 基于采样点的光谱特征提取
- 支持多种采样半径和统计计算
6. **步骤5.5: 水质指数计算**
- 基于光谱特征计算水质指数
- 支持自定义公式和18种水质参数
7. **步骤6: 机器学习建模**
- 支持18种机器学习算法
- 11种光谱预处理方法
- 3种数据划分策略
8. **步骤6.5: 非经验统计回归**
- 6种水质参数的非经验模型
- 基于物理原理的参数反演
9. **步骤6.75: 自定义回归分析**
- 完全自定义的回归分析
- 探索性数据分析工具
10. **步骤7: 采样点生成**
- 规则网格采样点生成
- 智能边界处理
11. **步骤8/8.5/8.75: 参数预测**
- 机器学习预测
- 非经验模型预测
- 自定义回归预测
12. **步骤9: 分布图生成**
- 空间插值和栅格化
- 多格式输出GeoTIFF, PNG, PDF
## 🏗️ 项目结构
```
water-quality-inversion/
├── src/ # 源代码目录
│ ├── core/ # 核心算法模块
│ │ ├── glint_removal/ # 耀斑去除算法
│ │ ├── modeling/ # 建模算法
│ │ └── prediction/ # 预测算法
│ ├── preprocessing/ # 数据预处理模块
│ ├── postprocessing/ # 后处理模块
│ ├── visualization/ # 可视化模块
│ ├── utils/ # 工具函数
│ └── gui/ # GUI界面
├── data/ # 数据目录
│ ├── input/ # 输入数据
│ ├── output/ # 输出结果
│ └── models/ # 模型文件
├── docs/ # 文档目录
├── scripts/ # 构建和部署脚本
├── tests/ # 测试目录
├── requirements.txt # 依赖文件
├── setup.py # 安装配置
├── pyproject.toml # 项目配置
└── README.md # 项目说明
```
## 🤝 贡献
欢迎贡献代码!请查看 [CONTRIBUTING.md](CONTRIBUTING.md) 了解详细信息。
### 开发环境设置
```bash
# 安装开发依赖
pip install -e ".[dev]"
# 运行测试
pytest
# 代码格式化
black src/
isort src/
# 类型检查
mypy src/
```
## 📄 许可证
本项目基于 MIT 许可证开源 - 查看 [LICENSE](LICENSE) 文件了解详情。
## 📚 引用
如果您在研究中使用了本系统,请引用:
```bibtex
@software{water_quality_inversion,
title = {Water Quality Inversion Analysis System},
author = {Water Quality Research Team},
url = {https://github.com/waterquality/water-quality-inversion},
version = {1.0.0},
year = {2025}
}
```
## 📞 联系我们
- **项目主页**: https://github.com/waterquality/water-quality-inversion
- **问题反馈**: https://github.com/waterquality/water-quality-inversion/issues
- **邮箱**: support@waterquality.com
## 🙏 致谢
感谢所有为本项目做出贡献的开发者们!
---
**水质参数反演分析系统** - 让水质监测更智能、更精准!

120
README_SAMPLING_MAP.md Normal file
View File

@ -0,0 +1,120 @@
# 采样点地图功能使用说明
## 功能概述
本系统新增了采样点地图生成功能,可以在高光谱假彩色影像上标注采样点位置,并添加专业的地图要素。
## 主要功能
### 1. SamplingPointMap 类 (`src/postprocessing/point_map.py`)
**核心功能:**
- 读取高光谱影像并生成假彩色RGB图像
- 读取CSV文件中的采样点坐标前两列为**纬度、经度**
- 在影像上标注红色采样点
- 添加**指北针**、**比例尺**和**图例**
- 支持地理坐标转换
### 2. Visualization Reports 集成 (`src/postprocessing/visualization_reports.py`)
**新增方法:**
- `generate_sampling_point_map()`:生成采样点地图
- `generate_all_visualizations()`:生成所有可视化结果
### 3. GUI 集成 (`src/gui/water_quality_gui.py`)
**可视化分析页面新增:**
- 复选框:"生成采样点地图"
- 按钮:"📍 生成采样点地图"
- 按钮:"👁️ 查看采样点地图"
## 使用方法
### 1. 通过GUI使用
1. 打开**可视化分析**页面
2. 勾选"生成采样点地图"
3. 点击"📍 生成采样点地图"按钮
4. 系统会自动:
- 查找高光谱影像文件(.dat, .bsq, .tif等
- 查找 `4_processed_data` 文件夹中的CSV文件
- 生成带采样点的地图
- 保存至 `9_visualization/sampling_maps/` 目录
### 2. 编程调用
```python
from src.postprocessing.point_map import SamplingPointMap
from src.postprocessing.visualization_reports import WaterQualityVisualization
# 方法1直接使用SamplingPointMap
map_generator = SamplingPointMap(output_dir="./point_maps")
map_path = map_generator.create_sampling_point_map(
hyperspectral_path="path/to/hyperspectral.dat",
csv_path="path/to/sampling_points.csv",
point_color='red',
point_size=100,
show_north_arrow=True,
show_scale_bar=True,
show_legend=True
)
# 方法2通过VisualizationReports
viz = WaterQualityVisualization(output_dir="./9_visualization")
map_path = viz.generate_sampling_point_map(
hyperspectral_path="path/to/hyperspectral.dat",
csv_path="path/to/sampling_points.csv"
)
# 方法3生成所有可视化
results = viz.generate_all_visualizations(work_dir="./work_dir")
```
## CSV文件格式要求
CSV文件必须满足以下格式
- **前两列**分别为**纬度**和**经度**
- 使用**逗号分隔**
- 必须包含有效的数值
**示例:**
```csv
latitude,longitude,parameter1,parameter2
31.2345,121.4567,25.5,3.2
31.2350,121.4570,26.1,3.5
31.2360,121.4580,24.8,2.9
```
## 输出目录结构
```
work_dir/
├── 1_water_mask/
│ └── hsi_preview.png # 高光谱预览图
├── 4_processed_data/
│ └── processed_data.csv # 处理后的数据
├── 9_visualization/
│ ├── glint_deglint_previews/ # 掩膜和耀斑缩略图
│ └── sampling_maps/ # 采样点地图
│ └── hyperspectral_sampling_map.png
└── ...
```
## 地图要素
- **红色圆点**:采样点位置
- **指北针**:指示北方
- **比例尺**:显示实际距离
- **图例**:说明采样点数量
- **标题**:清晰的地图标题
## 依赖库
- GDAL (地理坐标转换)
- matplotlib (绘图)
- pandas (CSV处理)
- numpy (数值计算)
---
**注意**确保工作目录中包含高光谱影像文件和处理后的CSV文件。

10
check_env.bat Normal file
View File

@ -0,0 +1,10 @@
@echo off
echo 检查insect conda环境...
if exist "%USERPROFILE%\.conda\envs\insect\python.exe" (
echo insect环境存在
echo Python路径: %USERPROFILE%\.conda\envs\insect\python.exe
"%USERPROFILE%\.conda\envs\insect\python.exe" --version
) else (
echo insect环境不存在
)
pause

BIN
data/icons/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
data/icons/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
data/icons/11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

BIN
data/icons/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
data/icons/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
data/icons/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
data/icons/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
data/icons/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
data/icons/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
data/icons/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
data/icons/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
data/icons/IRIS.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

BIN
data/icons/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
data/icons/table.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 MiB

BIN
data/icons/word/fenmian.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 KiB

BIN
data/icons/word/lica.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 KiB

0
data/input/.gitkeep Normal file
View File

0
data/models/.gitkeep Normal file
View File

0
data/output/.gitkeep Normal file
View File

BIN
data/sub/png/watermask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

46
data/sub/waterindex.csv Normal file
View File

@ -0,0 +1,46 @@
Formula_Name,Category,Formula,Reference
BGA_Am09KBBI,Phycocyanin (BGA_PC),(w686 - w658) / (w686 + w658),"Amin, R.; Zhou, J.; Gilerson, A.; Gross, B.; Moshary, F.; Ahmed, S.; Novel optical techniques for detecting and classifying toxic dinoflagellate Karenia brevis blooms using satellite imagery, Optics Express, 2009, 17, 11, 1-13."
BGA_Be162B643sub629,Phycocyanin (BGA_PC),w644 - w629,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538."
BGA_Be162B700sub601,Phycocyanin (BGA_PC),w700 - w601,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539."
BGA_Be162BsubPhy,Phycocyanin (BGA_PC),w715 - w615,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 540."
BGA_Be16FLHBlueRedNIR,Phycocyanin (BGA_PC),w658 - (w857 + (w458 - w857)),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538."
BGA_Be16FLHGreenRedNIR,Phycocyanin (BGA_PC),w658 - (w857 + (w558 - w857)),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539."
BGA_Be16FLHVioletRedNIR,Phycocyanin (BGA_PC),w658 - (w857 + (w444 - w857)),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538."
BGA_Be16MPI,Phycocyanin (BGA_PC),(w615 - w601) - (w644 - w601),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539."
BGA_Be16NDPhyI,Phycocyanin (BGA_PC),(w700 - w622) / (w700 + w622),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 540."
BGA_Be16NDPhyI644over615,Phycocyanin (BGA_PC),(w644 - w615) / (w644 + w615),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 541."
BGA_Be16NDPhyI644over629,Phycocyanin (BGA_PC),(w644 - w629) / (w644 + w629),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 542."
BGA_Be16Phy2BDA644over629,Phycocyanin (BGA_PC),w644 / w629,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 545."
BGA_Da052BDA,Phycocyanin (BGA_PC),w714 / w672,"Wynne, T. T., Stumpf, R. P., Tomlinson, M. C., Warner, R. A., Tester, P. A., Dyble, J.; Relating spectral shape to cyanobacterial blooms in the Laurentian Great Lakes. Int. J. Remote Sens., 2008, 29, 3665-3672."
BGA_Go04MCI,Phycocyanin (BGA_PC),w709 - w681 - (w753 - w681),"Gower, J.F.R.; Brown,L.; Borstad, G.A.; Observation of chlorophyll fluorescence in west coast waters of Canada using the MODIS satellite sensor. Can. J. Remote Sens., 2004, 30 (1), 17闁?5."
BGA_HU103BDA,Phycocyanin (BGA_PC),(((1 / w615) - (1 / w600)) - w725),"Hunter, P.D.; Tyler, A.N.; Willby, N.J.; Gilvear, D.J.; The spatial dynamics of vertical migration by Microcystis aeruginosa in a eutrophic shallow lake: A case study using high spatial resolution time-series airborne remote sensing. Limn. Oceanogr. 2008, 53, 2391-2406"
BGA_Ku15PhyCI,Phycocyanin (BGA_PC),(-1 * (W681 - W665 - (W709 - W665))),"Kudela, R.M., Palacios, S.L., Austerberry, D.C., Accorsi, E.K., Guild, L.S.; Application of hyperspectral remote sensing to cyanobacterial blooms in inland waters, Torres-Perez, J., 2015, Remote Sens. Environ., 2015, 167, 1-10."
BGA_Ku15SLH,Phycocyanin (BGA_PC),(w715 - w658) + (w715 - w658),"Kudela, R.M., Palacios, S.L., Austerberry, D.C., Accorsi, E.K., Guild, L.S.; Application of hyperspectral remote sensing to cyanobacterial blooms in inland waters, Torres-Perez, J., 2015, Remote Sens. Environ., 2015, 167, 1-11."
BGA_MI092BDA,Phycocyanin (BGA_PC),w700 / w600,"Mishra, S.; Mishra, D.R.; Schluchter, W. M., A novel algorithm for predicting PC concentrations in cyanobacteria: A proximal hyperspectral remote sensing approach. Remote Sens., 2009, 1, 758闁?75."
BGA_MM092BDA,Phycocyanin (BGA_PC),w724 / w600,"Mishra, S.; Mishra, D.R.; Schluchter, W. M., A novel algorithm for predicting PC concentrations in cyanobacteria: A proximal hyperspectral remote sensing approach. Remote Sens., 2009, 1, 758闁?76."
BGA_MM12NDCIalt,Phycocyanin (BGA_PC),(w700 - w658) / (w700 + w658),"Mishra, S.; Mishra, D.R.; A novel remote sensing algorithm to quantify phycocyanin in cyanobacterial algal blooms, Env. Res. Lett., 2014, 9 (11), DOI:10.1088/1748-9326/9/11/114003"
BGA_MM143BDAopt,Phycocyanin (BGA_PC),((1 / w629) - (1 / w659)) * w724,"Mishra, S.; Mishra, D.R.; A novel remote sensing algorithm to quantify phycocyanin in cyanobacterial algal blooms, Env. Res. Lett., 2014, 9 (11), DOI:10.1088/1748-9326/9/11/114004"
BGA_SI052BDA,Phycocyanin (BGA_PC),w709 / w620,"Simis, S. G. H.; Peters, S.W. M.; Gons, H. J.; Remote sensing of the cyanobacteria pigment phycocyanin in turbid inland water. Limn. Oceanogr., 2005, 50, 237闁?45"
BGA_SM122BDA,Phycocyanin (BGA_PC),w709 / w600,"Mishra, S. Remote sensing of cyanobacteria in turbid productive waters, PhD Dissertation. Mississippi State University, USA. 2012."
BGA_SY002BDA,Phycocyanin (BGA_PC),w650 / w625,"Schalles, J.; Yacobi, Y. Remote detection and seasonal patterns of phycocyanin, carotenoid and chlorophyll-a pigments in eutrophic waters. Archiv fur Hydrobiologie, Special Issues Advances in Limnology, 2000, 55,153闁?68"
BGA_Wy08CI,Phycocyanin (BGA_PC),(-1 * (W686 - W672 - (W715 - W672))),"Wynne, T. T., Stumpf, R. P., Tomlinson, M. C., Warner, R. A., Tester, P. A., Dyble, J.; Relating spectral shape to cyanobacterial blooms in the Laurentian Great Lakes. Int. J. Remote Sens., 2008, 29, 3665-3672."
Chl_Al10SABI,chlorophyll_a,(w857 - w644) / (w458 + w529),"Alawadi, F. Detection of surface algal blooms using the newly developed algorithm surface algal bloom index (SABI). Proc. SPIE 2010, 7825."
Chl_Am092Bsub,chlorophyll_a,w681 - w665,"Amin, R.; Zhou, J.; Gilerson, A.; Gross, B.; Moshary, F.; Ahmed, S. Novel optical techniques for detecting and classifying toxic dinoflagellate Karenia brevis blooms using satellite imagery. Opt. Express 2009, 17, 9126闁?144."
Chl_Be16FLHblue,chlorophyll_a,w529 - (w644 + (w458 - w644)),"Beck, R.A. and 22 others; Comparison of satellite reflectance algorithms for estimating chlorophyll-a in a temperate reservoir using coincident hyperspectral aircraft imagery and dense coincident surface observations, Remote Sens. Environ., 2016, 178, 15-30."
Chl_Be16FLHviolet,chlorophyll_a,w529 - (w644 + (w429 - w644)),"Beck, R.A. and 22 others; Comparison of satellite reflectance algorithms for estimating chlorophyll-a in a temperate reservoir using coincident hyperspectral aircraft imagery and dense coincident surface observations, Remote Sens. Environ., 2016, 178, 15-30."
Chl_Be16NDTIblue,chlorophyll_a,(w658 - w458) / (w658 + w458),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 543."
Chl_Be16NDTIviolet,chlorophyll_a,(w658 - w444) / (w658 + w444),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 544."
Chl_De933BDA,chlorophyll_a,w600 - w648 - w625,"Dekker, A.; Detection of the optical water quality parameters for eutrophic waters by high resolution remote sensing, Ph.D. thesis, 1993, Free University, Amsterdam."
Chl_Gi033BDA,chlorophyll_a,((1 / w672) - (1 / w715)) * w757,"Gitelson, A.A.; U. Gritz, and M. N. Merzlyak.; Relationships between leaf chlorophyll content and spectral reflectance and algorithms for non-destructive chlorophyll assessment in higher plant leaves. J. Plant Phys. 2003, 160, 271-282."
Chl_Kn07KIVU,chlorophyll_a,(w458 - w644) / w529,"Kneubuhler, M.; Frank T.; Kellenberger, T.W; Pasche N.; Schmid M.; Mapping chlorophyll-a in Lake Kivu with remote sensing methods. 2007, Proceedings of the Envisat Symposium 2007, Montreux, Switzerland 23闁?7 April 2007 (ESA SP-636, July 2007)."
Chl_MM12NDCI,chlorophyll_a,(w715 - w686) / (w715 + w686),"Mishra, S.; and Mishra, D.R. Normalized difference chlorophyll index: A novel model for remote estimation of chlorophyll-a concentration in turbid productive waters, Remote Sens. Environ., 2012, 117, 394-406"
Chl_Zh10FLH,chlorophyll_a,w686 - (w715 + (w672 - w751)),"Zhao, D.Z.; Xing, X.G.; Liu, Y.G.; Yang, J.H.; Wang, L. The relation of chlorophyll-a concentration with the reflectance peak near 700 nm in algae-dominated waters and sensitivity of fluorescence algorithms for detecting algal bloom. Int. J. Remote Sens. 2010, 31, 39-48"
Turb_Be16GreenPlusRedBothOverViolet,Turbidity,(w558 + w658) / w444,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538"
Turb_Be16RedOverViolet,Turbidity,w658 / w444,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539"
Turb_Bow06RedOverGreen,Turbidity,w658 / w558,"Bowers, D. G., and C. E. Binding. 2006. 闁炽儲缈籬e Optical Properties of Mineral Suspended Particles: A Review and Synthesis.闁?Estuarine Coastal and Shelf Science 67 (1闁?): 219闁?30. doi:10.1016/j.ecss.2005.11.010"
Turb_Chip09NIROverGreen,Turbidity,w857 / w558,"Chipman, J. W.; Olmanson, L.G.; Gitelson, A.A.; Remote sensing methods for lake management: A guide for resource managers and decision-makers. 2009."
Turb_Dox02NIRoverRed,Turbidity,w857 / w658,"Doxaran, D., Froidefond, J.-M.; Castaing, P. ; A reflectance band ratio used to estimate suspended matter concentrations in sediment-dominated coastal waters, Remote Sens., 2002, 23, 5079-5085"
Turb_Frohn09GreenPlusRedBothOverBlue,Turbidity,(w558 + w658) / w458,"Frohn, R. C., & Autrey, B. C. (2009). Water quality assessment in the Ohio River using new indices for turbidity and chlorophyll-a with Landsat-7 Imagery. Draft Internal Report, US Environmental Protection Agency."
Turb_Harr92NIR,Turbidity,w857,"Schiebe F.R., Harrington J.A., Ritchie J.C. Remote-Sensing of Suspended Sediments闁炽儲鏁刪e Lake Chicot, Arkansas Project. Int. J. Remote Sens. 1992;13:1487闁?509"
Turb_Lath91RedOverBlue,Turbidity,w658 / w458,"Lathrop, R. G., Jr., T. M. Lillesand, and B. S. Yandell, 1991. Testing the utility of simple multi-date Thematic Mapper calibration algorithms for monitoring turbid inland waters. International Journal of Remote Sensing"
Turb_Moore80Red,Turbidity,w658,"Moore, G.K., Satellite remote sensing of water turbidity, Hydrological Sciences, 1980, 25, 4, 407-422"
1 Formula_Name Category Formula Reference
2 BGA_Am09KBBI Phycocyanin (BGA_PC) (w686 - w658) / (w686 + w658) Amin, R.; Zhou, J.; Gilerson, A.; Gross, B.; Moshary, F.; Ahmed, S.; Novel optical techniques for detecting and classifying toxic dinoflagellate Karenia brevis blooms using satellite imagery, Optics Express, 2009, 17, 11, 1-13.
3 BGA_Be162B643sub629 Phycocyanin (BGA_PC) w644 - w629 Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538.
4 BGA_Be162B700sub601 Phycocyanin (BGA_PC) w700 - w601 Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539.
5 BGA_Be162BsubPhy Phycocyanin (BGA_PC) w715 - w615 Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 540.
6 BGA_Be16FLHBlueRedNIR Phycocyanin (BGA_PC) w658 - (w857 + (w458 - w857)) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538.
7 BGA_Be16FLHGreenRedNIR Phycocyanin (BGA_PC) w658 - (w857 + (w558 - w857)) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539.
8 BGA_Be16FLHVioletRedNIR Phycocyanin (BGA_PC) w658 - (w857 + (w444 - w857)) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538.
9 BGA_Be16MPI Phycocyanin (BGA_PC) (w615 - w601) - (w644 - w601) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539.
10 BGA_Be16NDPhyI Phycocyanin (BGA_PC) (w700 - w622) / (w700 + w622) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 540.
11 BGA_Be16NDPhyI644over615 Phycocyanin (BGA_PC) (w644 - w615) / (w644 + w615) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 541.
12 BGA_Be16NDPhyI644over629 Phycocyanin (BGA_PC) (w644 - w629) / (w644 + w629) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 542.
13 BGA_Be16Phy2BDA644over629 Phycocyanin (BGA_PC) w644 / w629 Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 545.
14 BGA_Da052BDA Phycocyanin (BGA_PC) w714 / w672 Wynne, T. T., Stumpf, R. P., Tomlinson, M. C., Warner, R. A., Tester, P. A., Dyble, J.; Relating spectral shape to cyanobacterial blooms in the Laurentian Great Lakes. Int. J. Remote Sens., 2008, 29, 3665-3672.
15 BGA_Go04MCI Phycocyanin (BGA_PC) w709 - w681 - (w753 - w681) Gower, J.F.R.; Brown,L.; Borstad, G.A.; Observation of chlorophyll fluorescence in west coast waters of Canada using the MODIS satellite sensor. Can. J. Remote Sens., 2004, 30 (1), 17闁?5.
16 BGA_HU103BDA Phycocyanin (BGA_PC) (((1 / w615) - (1 / w600)) - w725) Hunter, P.D.; Tyler, A.N.; Willby, N.J.; Gilvear, D.J.; The spatial dynamics of vertical migration by Microcystis aeruginosa in a eutrophic shallow lake: A case study using high spatial resolution time-series airborne remote sensing. Limn. Oceanogr. 2008, 53, 2391-2406
17 BGA_Ku15PhyCI Phycocyanin (BGA_PC) (-1 * (W681 - W665 - (W709 - W665))) Kudela, R.M., Palacios, S.L., Austerberry, D.C., Accorsi, E.K., Guild, L.S.; Application of hyperspectral remote sensing to cyanobacterial blooms in inland waters, Torres-Perez, J., 2015, Remote Sens. Environ., 2015, 167, 1-10.
18 BGA_Ku15SLH Phycocyanin (BGA_PC) (w715 - w658) + (w715 - w658) Kudela, R.M., Palacios, S.L., Austerberry, D.C., Accorsi, E.K., Guild, L.S.; Application of hyperspectral remote sensing to cyanobacterial blooms in inland waters, Torres-Perez, J., 2015, Remote Sens. Environ., 2015, 167, 1-11.
19 BGA_MI092BDA Phycocyanin (BGA_PC) w700 / w600 Mishra, S.; Mishra, D.R.; Schluchter, W. M., A novel algorithm for predicting PC concentrations in cyanobacteria: A proximal hyperspectral remote sensing approach. Remote Sens., 2009, 1, 758闁?75.
20 BGA_MM092BDA Phycocyanin (BGA_PC) w724 / w600 Mishra, S.; Mishra, D.R.; Schluchter, W. M., A novel algorithm for predicting PC concentrations in cyanobacteria: A proximal hyperspectral remote sensing approach. Remote Sens., 2009, 1, 758闁?76.
21 BGA_MM12NDCIalt Phycocyanin (BGA_PC) (w700 - w658) / (w700 + w658) Mishra, S.; Mishra, D.R.; A novel remote sensing algorithm to quantify phycocyanin in cyanobacterial algal blooms, Env. Res. Lett., 2014, 9 (11), DOI:10.1088/1748-9326/9/11/114003
22 BGA_MM143BDAopt Phycocyanin (BGA_PC) ((1 / w629) - (1 / w659)) * w724 Mishra, S.; Mishra, D.R.; A novel remote sensing algorithm to quantify phycocyanin in cyanobacterial algal blooms, Env. Res. Lett., 2014, 9 (11), DOI:10.1088/1748-9326/9/11/114004
23 BGA_SI052BDA Phycocyanin (BGA_PC) w709 / w620 Simis, S. G. H.; Peters, S.W. M.; Gons, H. J.; Remote sensing of the cyanobacteria pigment phycocyanin in turbid inland water. Limn. Oceanogr., 2005, 50, 237闁?45
24 BGA_SM122BDA Phycocyanin (BGA_PC) w709 / w600 Mishra, S. Remote sensing of cyanobacteria in turbid productive waters, PhD Dissertation. Mississippi State University, USA. 2012.
25 BGA_SY002BDA Phycocyanin (BGA_PC) w650 / w625 Schalles, J.; Yacobi, Y. Remote detection and seasonal patterns of phycocyanin, carotenoid and chlorophyll-a pigments in eutrophic waters. Archiv fur Hydrobiologie, Special Issues Advances in Limnology, 2000, 55,153闁?68
26 BGA_Wy08CI Phycocyanin (BGA_PC) (-1 * (W686 - W672 - (W715 - W672))) Wynne, T. T., Stumpf, R. P., Tomlinson, M. C., Warner, R. A., Tester, P. A., Dyble, J.; Relating spectral shape to cyanobacterial blooms in the Laurentian Great Lakes. Int. J. Remote Sens., 2008, 29, 3665-3672.
27 Chl_Al10SABI chlorophyll_a (w857 - w644) / (w458 + w529) Alawadi, F. Detection of surface algal blooms using the newly developed algorithm surface algal bloom index (SABI). Proc. SPIE 2010, 7825.
28 Chl_Am092Bsub chlorophyll_a w681 - w665 Amin, R.; Zhou, J.; Gilerson, A.; Gross, B.; Moshary, F.; Ahmed, S. Novel optical techniques for detecting and classifying toxic dinoflagellate Karenia brevis blooms using satellite imagery. Opt. Express 2009, 17, 9126闁?144.
29 Chl_Be16FLHblue chlorophyll_a w529 - (w644 + (w458 - w644)) Beck, R.A. and 22 others; Comparison of satellite reflectance algorithms for estimating chlorophyll-a in a temperate reservoir using coincident hyperspectral aircraft imagery and dense coincident surface observations, Remote Sens. Environ., 2016, 178, 15-30.
30 Chl_Be16FLHviolet chlorophyll_a w529 - (w644 + (w429 - w644)) Beck, R.A. and 22 others; Comparison of satellite reflectance algorithms for estimating chlorophyll-a in a temperate reservoir using coincident hyperspectral aircraft imagery and dense coincident surface observations, Remote Sens. Environ., 2016, 178, 15-30.
31 Chl_Be16NDTIblue chlorophyll_a (w658 - w458) / (w658 + w458) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 543.
32 Chl_Be16NDTIviolet chlorophyll_a (w658 - w444) / (w658 + w444) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 544.
33 Chl_De933BDA chlorophyll_a w600 - w648 - w625 Dekker, A.; Detection of the optical water quality parameters for eutrophic waters by high resolution remote sensing, Ph.D. thesis, 1993, Free University, Amsterdam.
34 Chl_Gi033BDA chlorophyll_a ((1 / w672) - (1 / w715)) * w757 Gitelson, A.A.; U. Gritz, and M. N. Merzlyak.; Relationships between leaf chlorophyll content and spectral reflectance and algorithms for non-destructive chlorophyll assessment in higher plant leaves. J. Plant Phys. 2003, 160, 271-282.
35 Chl_Kn07KIVU chlorophyll_a (w458 - w644) / w529 Kneubuhler, M.; Frank T.; Kellenberger, T.W; Pasche N.; Schmid M.; Mapping chlorophyll-a in Lake Kivu with remote sensing methods. 2007, Proceedings of the Envisat Symposium 2007, Montreux, Switzerland 23闁?7 April 2007 (ESA SP-636, July 2007).
36 Chl_MM12NDCI chlorophyll_a (w715 - w686) / (w715 + w686) Mishra, S.; and Mishra, D.R. Normalized difference chlorophyll index: A novel model for remote estimation of chlorophyll-a concentration in turbid productive waters, Remote Sens. Environ., 2012, 117, 394-406
37 Chl_Zh10FLH chlorophyll_a w686 - (w715 + (w672 - w751)) Zhao, D.Z.; Xing, X.G.; Liu, Y.G.; Yang, J.H.; Wang, L. The relation of chlorophyll-a concentration with the reflectance peak near 700 nm in algae-dominated waters and sensitivity of fluorescence algorithms for detecting algal bloom. Int. J. Remote Sens. 2010, 31, 39-48
38 Turb_Be16GreenPlusRedBothOverViolet Turbidity (w558 + w658) / w444 Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538
39 Turb_Be16RedOverViolet Turbidity w658 / w444 Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539
40 Turb_Bow06RedOverGreen Turbidity w658 / w558 Bowers, D. G., and C. E. Binding. 2006. 闁炽儲缈籬e Optical Properties of Mineral Suspended Particles: A Review and Synthesis.闁?Estuarine Coastal and Shelf Science 67 (1闁?): 219闁?30. doi:10.1016/j.ecss.2005.11.010
41 Turb_Chip09NIROverGreen Turbidity w857 / w558 Chipman, J. W.; Olmanson, L.G.; Gitelson, A.A.; Remote sensing methods for lake management: A guide for resource managers and decision-makers. 2009.
42 Turb_Dox02NIRoverRed Turbidity w857 / w658 Doxaran, D., Froidefond, J.-M.; Castaing, P. ; A reflectance band ratio used to estimate suspended matter concentrations in sediment-dominated coastal waters, Remote Sens., 2002, 23, 5079-5085
43 Turb_Frohn09GreenPlusRedBothOverBlue Turbidity (w558 + w658) / w458 Frohn, R. C., & Autrey, B. C. (2009). Water quality assessment in the Ohio River using new indices for turbidity and chlorophyll-a with Landsat-7 Imagery. Draft Internal Report, US Environmental Protection Agency.
44 Turb_Harr92NIR Turbidity w857 Schiebe F.R., Harrington J.A., Ritchie J.C. Remote-Sensing of Suspended Sediments闁炽儲鏁刪e Lake Chicot, Arkansas Project. Int. J. Remote Sens. 1992;13:1487闁?509
45 Turb_Lath91RedOverBlue Turbidity w658 / w458 Lathrop, R. G., Jr., T. M. Lillesand, and B. S. Yandell, 1991. Testing the utility of simple multi-date Thematic Mapper calibration algorithms for monitoring turbid inland waters. International Journal of Remote Sensing
46 Turb_Moore80Red Turbidity w658 Moore, G.K., Satellite remote sensing of water turbidity, Hydrological Sciences, 1980, 25, 4, 407-422

BIN
data/sub/waterindex.xlsx Normal file

Binary file not shown.

View File

@ -0,0 +1,46 @@
Formula_Name,Category,Formula,Reference
BGA_Am09KBBI,Phycocyanin (BGA_PC),(w686 - w658) / (w686 + w658),"Amin, R.; Zhou, J.; Gilerson, A.; Gross, B.; Moshary, F.; Ahmed, S.; Novel optical techniques for detecting and classifying toxic dinoflagellate Karenia brevis blooms using satellite imagery, Optics Express, 2009, 17, 11, 1-13."
BGA_Be162B643sub629,Phycocyanin (BGA_PC),w644 - w629,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538."
BGA_Be162B700sub601,Phycocyanin (BGA_PC),w700 - w601,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539."
BGA_Be162BsubPhy,Phycocyanin (BGA_PC),w715 - w615,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 540."
BGA_Be16FLHBlueRedNIR,Phycocyanin (BGA_PC),w658 - (w857 + (w458 - w857)),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538."
BGA_Be16FLHGreenRedNIR,Phycocyanin (BGA_PC),w658 - (w857 + (w558 - w857)),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539."
BGA_Be16FLHVioletRedNIR,Phycocyanin (BGA_PC),w658 - (w857 + (w444 - w857)),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538."
BGA_Be16MPI,Phycocyanin (BGA_PC),(w615 - w601) - (w644 - w601),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539."
BGA_Be16NDPhyI,Phycocyanin (BGA_PC),(w700 - w622) / (w700 + w622),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 540."
BGA_Be16NDPhyI644over615,Phycocyanin (BGA_PC),(w644 - w615) / (w644 + w615),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 541."
BGA_Be16NDPhyI644over629,Phycocyanin (BGA_PC),(w644 - w629) / (w644 + w629),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 542."
BGA_Be16Phy2BDA644over629,Phycocyanin (BGA_PC),w644 / w629,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 545."
BGA_Da052BDA,Phycocyanin (BGA_PC),w714 / w672,"Wynne, T. T., Stumpf, R. P., Tomlinson, M. C., Warner, R. A., Tester, P. A., Dyble, J.; Relating spectral shape to cyanobacterial blooms in the Laurentian Great Lakes. Int. J. Remote Sens., 2008, 29, 3665-3672."
BGA_Go04MCI,Phycocyanin (BGA_PC),w709 - w681 - (w753 - w681),"Gower, J.F.R.; Brown,L.; Borstad, G.A.; Observation of chlorophyll fluorescence in west coast waters of Canada using the MODIS satellite sensor. Can. J. Remote Sens., 2004, 30 (1), 17<31><37>?5."
BGA_HU103BDA,Phycocyanin (BGA_PC),(((1 / w615) - (1 / w600)) - w725),"Hunter, P.D.; Tyler, A.N.; Willby, N.J.; Gilvear, D.J.; The spatial dynamics of vertical migration by Microcystis aeruginosa in a eutrophic shallow lake: A case study using high spatial resolution time-series airborne remote sensing. Limn. Oceanogr. 2008, 53, 2391-2406"
BGA_Ku15PhyCI,Phycocyanin (BGA_PC),-1 * (W681 - W665 - (W709 - W665)),"Kudela, R.M., Palacios, S.L., Austerberry, D.C., Accorsi, E.K., Guild, L.S.; Application of hyperspectral remote sensing to cyanobacterial blooms in inland waters, Torres-Perez, J., 2015, Remote Sens. Environ., 2015, 167, 1-10."
BGA_Ku15SLH,Phycocyanin (BGA_PC),(w715 - w658) + (w715 - w658),"Kudela, R.M., Palacios, S.L., Austerberry, D.C., Accorsi, E.K., Guild, L.S.; Application of hyperspectral remote sensing to cyanobacterial blooms in inland waters, Torres-Perez, J., 2015, Remote Sens. Environ., 2015, 167, 1-11."
BGA_MI092BDA,Phycocyanin (BGA_PC),w700 / w600,"Mishra, S.; Mishra, D.R.; Schluchter, W. M., A novel algorithm for predicting PC concentrations in cyanobacteria: A proximal hyperspectral remote sensing approach. Remote Sens., 2009, 1, 758<35><38>?75."
BGA_MM092BDA,Phycocyanin (BGA_PC),w724 / w600,"Mishra, S.; Mishra, D.R.; Schluchter, W. M., A novel algorithm for predicting PC concentrations in cyanobacteria: A proximal hyperspectral remote sensing approach. Remote Sens., 2009, 1, 758<35><38>?76."
BGA_MM12NDCIalt,Phycocyanin (BGA_PC),(w700 - w658) / (w700 + w658),"Mishra, S.; Mishra, D.R.; A novel remote sensing algorithm to quantify phycocyanin in cyanobacterial algal blooms, Env. Res. Lett., 2014, 9 (11), DOI:10.1088/1748-9326/9/11/114003"
BGA_MM143BDAopt,Phycocyanin (BGA_PC),((1 / w629) - (1 / w659)) * w724,"Mishra, S.; Mishra, D.R.; A novel remote sensing algorithm to quantify phycocyanin in cyanobacterial algal blooms, Env. Res. Lett., 2014, 9 (11), DOI:10.1088/1748-9326/9/11/114004"
BGA_SI052BDA,Phycocyanin (BGA_PC),w709 / w620,"Simis, S. G. H.; Peters, S.W. M.; Gons, H. J.; Remote sensing of the cyanobacteria pigment phycocyanin in turbid inland water. Limn. Oceanogr., 2005, 50, 237<33><37>?45"
BGA_SM122BDA,Phycocyanin (BGA_PC),w709 / w600,"Mishra, S. Remote sensing of cyanobacteria in turbid productive waters, PhD Dissertation. Mississippi State University, USA. 2012."
BGA_SY002BDA,Phycocyanin (BGA_PC),w650 / w625,"Schalles, J.; Yacobi, Y. Remote detection and seasonal patterns of phycocyanin, carotenoid and chlorophyll-a pigments in eutrophic waters. Archiv fur Hydrobiologie, Special Issues Advances in Limnology, 2000, 55,153<35><33>?68"
BGA_Wy08CI,Phycocyanin (BGA_PC),-1 * (W686 - W672 - (W715 - W672)),"Wynne, T. T., Stumpf, R. P., Tomlinson, M. C., Warner, R. A., Tester, P. A., Dyble, J.; Relating spectral shape to cyanobacterial blooms in the Laurentian Great Lakes. Int. J. Remote Sens., 2008, 29, 3665-3672."
Chl_Al10SABI,chlorophyll_a,(w857 - w644) / (w458 + w529),"Alawadi, F. Detection of surface algal blooms using the newly developed algorithm surface algal bloom index (SABI). Proc. SPIE 2010, 7825."
Chl_Am092Bsub,chlorophyll_a,w681 - w665,"Amin, R.; Zhou, J.; Gilerson, A.; Gross, B.; Moshary, F.; Ahmed, S. Novel optical techniques for detecting and classifying toxic dinoflagellate Karenia brevis blooms using satellite imagery. Opt. Express 2009, 17, 9126<32><36>?144."
Chl_Be16FLHblue,chlorophyll_a,w529 - (w644 + (w458 - w644)),"Beck, R.A. and 22 others; Comparison of satellite reflectance algorithms for estimating chlorophyll-a in a temperate reservoir using coincident hyperspectral aircraft imagery and dense coincident surface observations, Remote Sens. Environ., 2016, 178, 15-30."
Chl_Be16FLHviolet,chlorophyll_a,w529 - (w644 + (w429 - w644)),"Beck, R.A. and 22 others; Comparison of satellite reflectance algorithms for estimating chlorophyll-a in a temperate reservoir using coincident hyperspectral aircraft imagery and dense coincident surface observations, Remote Sens. Environ., 2016, 178, 15-30."
Chl_Be16NDTIblue,chlorophyll_a,(w658 - w458) / (w658 + w458),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 543."
Chl_Be16NDTIviolet,chlorophyll_a,(w658 - w444) / (w658 + w444),"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 544."
Chl_De933BDA,chlorophyll_a,w600 - w648 - w625,"Dekker, A.; Detection of the optical water quality parameters for eutrophic waters by high resolution remote sensing, Ph.D. thesis, 1993, Free University, Amsterdam."
Chl_Gi033BDA,chlorophyll_a,((1 / w672) - (1 / w715)) * w757,"Gitelson, A.A.; U. Gritz, and M. N. Merzlyak.; Relationships between leaf chlorophyll content and spectral reflectance and algorithms for non-destructive chlorophyll assessment in higher plant leaves. J. Plant Phys. 2003, 160, 271-282."
Chl_Kn07KIVU,chlorophyll_a,(w458 - w644) / w529,"Kneubuhler, M.; Frank T.; Kellenberger, T.W; Pasche N.; Schmid M.; Mapping chlorophyll-a in Lake Kivu with remote sensing methods. 2007, Proceedings of the Envisat Symposium 2007, Montreux, Switzerland 23<32><33>?7 April 2007 (ESA SP-636, July 2007)."
Chl_MM12NDCI,chlorophyll_a,(w715 - w686) / (w715 + w686),"Mishra, S.; and Mishra, D.R. Normalized difference chlorophyll index: A novel model for remote estimation of chlorophyll-a concentration in turbid productive waters, Remote Sens. Environ., 2012, 117, 394-406"
Chl_Zh10FLH,chlorophyll_a,w686 - (w715 + (w672 - w751)),"Zhao, D.Z.; Xing, X.G.; Liu, Y.G.; Yang, J.H.; Wang, L. The relation of chlorophyll-a concentration with the reflectance peak near 700 nm in algae-dominated waters and sensitivity of fluorescence algorithms for detecting algal bloom. Int. J. Remote Sens. 2010, 31, 39-48"
Turb_Be16GreenPlusRedBothOverViolet,Turbidity,(w558 + w658) / w444,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538"
Turb_Be16RedOverViolet,Turbidity,w658 / w444,"Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539"
Turb_Bow06RedOverGreen,Turbidity,w658 / w558,"Bowers, D. G., and C. E. Binding. 2006. 鈥淭he Optical Properties of Mineral Suspended Particles: A Review and Synthesis.<2E><>?Estuarine Coastal and Shelf Science 67 (1<><31>?): 219<31><39>?30. doi:10.1016/j.ecss.2005.11.010"
Turb_Chip09NIROverGreen,Turbidity,w857 / w558,"Chipman, J. W.; Olmanson, L.G.; Gitelson, A.A.; Remote sensing methods for lake management: A guide for resource managers and decision-makers. 2009."
Turb_Dox02NIRoverRed,Turbidity,w857 / w658,"Doxaran, D., Froidefond, J.-M.; Castaing, P. ; A reflectance band ratio used to estimate suspended matter concentrations in sediment-dominated coastal waters, Remote Sens., 2002, 23, 5079-5085"
Turb_Frohn09GreenPlusRedBothOverBlue,Turbidity,(w558 + w658) / w458,"Frohn, R. C., & Autrey, B. C. (2009). Water quality assessment in the Ohio River using new indices for turbidity and chlorophyll-a with Landsat-7 Imagery. Draft Internal Report, US Environmental Protection Agency."
Turb_Harr92NIR,Turbidity,w857,"Schiebe F.R., Harrington J.A., Ritchie J.C. Remote-Sensing of Suspended Sediments鈥攖he Lake Chicot, Arkansas Project. Int. J. Remote Sens. 1992;13:1487<38><37>?509"
Turb_Lath91RedOverBlue,Turbidity,w658 / w458,"Lathrop, R. G., Jr., T. M. Lillesand, and B. S. Yandell, 1991. Testing the utility of simple multi-date Thematic Mapper calibration algorithms for monitoring turbid inland waters. International Journal of Remote Sensing"
Turb_Moore80Red,Turbidity,w658,"Moore, G.K., Satellite remote sensing of water turbidity, Hydrological Sciences, 1980, 25, 4, 407-422"
1 Formula_Name Category Formula Reference
2 BGA_Am09KBBI Phycocyanin (BGA_PC) (w686 - w658) / (w686 + w658) Amin, R.; Zhou, J.; Gilerson, A.; Gross, B.; Moshary, F.; Ahmed, S.; Novel optical techniques for detecting and classifying toxic dinoflagellate Karenia brevis blooms using satellite imagery, Optics Express, 2009, 17, 11, 1-13.
3 BGA_Be162B643sub629 Phycocyanin (BGA_PC) w644 - w629 Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538.
4 BGA_Be162B700sub601 Phycocyanin (BGA_PC) w700 - w601 Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539.
5 BGA_Be162BsubPhy Phycocyanin (BGA_PC) w715 - w615 Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 540.
6 BGA_Be16FLHBlueRedNIR Phycocyanin (BGA_PC) w658 - (w857 + (w458 - w857)) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538.
7 BGA_Be16FLHGreenRedNIR Phycocyanin (BGA_PC) w658 - (w857 + (w558 - w857)) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539.
8 BGA_Be16FLHVioletRedNIR Phycocyanin (BGA_PC) w658 - (w857 + (w444 - w857)) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538.
9 BGA_Be16MPI Phycocyanin (BGA_PC) (w615 - w601) - (w644 - w601) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539.
10 BGA_Be16NDPhyI Phycocyanin (BGA_PC) (w700 - w622) / (w700 + w622) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 540.
11 BGA_Be16NDPhyI644over615 Phycocyanin (BGA_PC) (w644 - w615) / (w644 + w615) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 541.
12 BGA_Be16NDPhyI644over629 Phycocyanin (BGA_PC) (w644 - w629) / (w644 + w629) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 542.
13 BGA_Be16Phy2BDA644over629 Phycocyanin (BGA_PC) w644 / w629 Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 545.
14 BGA_Da052BDA Phycocyanin (BGA_PC) w714 / w672 Wynne, T. T., Stumpf, R. P., Tomlinson, M. C., Warner, R. A., Tester, P. A., Dyble, J.; Relating spectral shape to cyanobacterial blooms in the Laurentian Great Lakes. Int. J. Remote Sens., 2008, 29, 3665-3672.
15 BGA_Go04MCI Phycocyanin (BGA_PC) w709 - w681 - (w753 - w681) Gower, J.F.R.; Brown,L.; Borstad, G.A.; Observation of chlorophyll fluorescence in west coast waters of Canada using the MODIS satellite sensor. Can. J. Remote Sens., 2004, 30 (1), 17?5.
16 BGA_HU103BDA Phycocyanin (BGA_PC) (((1 / w615) - (1 / w600)) - w725) Hunter, P.D.; Tyler, A.N.; Willby, N.J.; Gilvear, D.J.; The spatial dynamics of vertical migration by Microcystis aeruginosa in a eutrophic shallow lake: A case study using high spatial resolution time-series airborne remote sensing. Limn. Oceanogr. 2008, 53, 2391-2406
17 BGA_Ku15PhyCI Phycocyanin (BGA_PC) -1 * (W681 - W665 - (W709 - W665)) Kudela, R.M., Palacios, S.L., Austerberry, D.C., Accorsi, E.K., Guild, L.S.; Application of hyperspectral remote sensing to cyanobacterial blooms in inland waters, Torres-Perez, J., 2015, Remote Sens. Environ., 2015, 167, 1-10.
18 BGA_Ku15SLH Phycocyanin (BGA_PC) (w715 - w658) + (w715 - w658) Kudela, R.M., Palacios, S.L., Austerberry, D.C., Accorsi, E.K., Guild, L.S.; Application of hyperspectral remote sensing to cyanobacterial blooms in inland waters, Torres-Perez, J., 2015, Remote Sens. Environ., 2015, 167, 1-11.
19 BGA_MI092BDA Phycocyanin (BGA_PC) w700 / w600 Mishra, S.; Mishra, D.R.; Schluchter, W. M., A novel algorithm for predicting PC concentrations in cyanobacteria: A proximal hyperspectral remote sensing approach. Remote Sens., 2009, 1, 758?75.
20 BGA_MM092BDA Phycocyanin (BGA_PC) w724 / w600 Mishra, S.; Mishra, D.R.; Schluchter, W. M., A novel algorithm for predicting PC concentrations in cyanobacteria: A proximal hyperspectral remote sensing approach. Remote Sens., 2009, 1, 758?76.
21 BGA_MM12NDCIalt Phycocyanin (BGA_PC) (w700 - w658) / (w700 + w658) Mishra, S.; Mishra, D.R.; A novel remote sensing algorithm to quantify phycocyanin in cyanobacterial algal blooms, Env. Res. Lett., 2014, 9 (11), DOI:10.1088/1748-9326/9/11/114003
22 BGA_MM143BDAopt Phycocyanin (BGA_PC) ((1 / w629) - (1 / w659)) * w724 Mishra, S.; Mishra, D.R.; A novel remote sensing algorithm to quantify phycocyanin in cyanobacterial algal blooms, Env. Res. Lett., 2014, 9 (11), DOI:10.1088/1748-9326/9/11/114004
23 BGA_SI052BDA Phycocyanin (BGA_PC) w709 / w620 Simis, S. G. H.; Peters, S.W. M.; Gons, H. J.; Remote sensing of the cyanobacteria pigment phycocyanin in turbid inland water. Limn. Oceanogr., 2005, 50, 237?45
24 BGA_SM122BDA Phycocyanin (BGA_PC) w709 / w600 Mishra, S. Remote sensing of cyanobacteria in turbid productive waters, PhD Dissertation. Mississippi State University, USA. 2012.
25 BGA_SY002BDA Phycocyanin (BGA_PC) w650 / w625 Schalles, J.; Yacobi, Y. Remote detection and seasonal patterns of phycocyanin, carotenoid and chlorophyll-a pigments in eutrophic waters. Archiv fur Hydrobiologie, Special Issues Advances in Limnology, 2000, 55,153?68
26 BGA_Wy08CI Phycocyanin (BGA_PC) -1 * (W686 - W672 - (W715 - W672)) Wynne, T. T., Stumpf, R. P., Tomlinson, M. C., Warner, R. A., Tester, P. A., Dyble, J.; Relating spectral shape to cyanobacterial blooms in the Laurentian Great Lakes. Int. J. Remote Sens., 2008, 29, 3665-3672.
27 Chl_Al10SABI chlorophyll_a (w857 - w644) / (w458 + w529) Alawadi, F. Detection of surface algal blooms using the newly developed algorithm surface algal bloom index (SABI). Proc. SPIE 2010, 7825.
28 Chl_Am092Bsub chlorophyll_a w681 - w665 Amin, R.; Zhou, J.; Gilerson, A.; Gross, B.; Moshary, F.; Ahmed, S. Novel optical techniques for detecting and classifying toxic dinoflagellate Karenia brevis blooms using satellite imagery. Opt. Express 2009, 17, 9126?144.
29 Chl_Be16FLHblue chlorophyll_a w529 - (w644 + (w458 - w644)) Beck, R.A. and 22 others; Comparison of satellite reflectance algorithms for estimating chlorophyll-a in a temperate reservoir using coincident hyperspectral aircraft imagery and dense coincident surface observations, Remote Sens. Environ., 2016, 178, 15-30.
30 Chl_Be16FLHviolet chlorophyll_a w529 - (w644 + (w429 - w644)) Beck, R.A. and 22 others; Comparison of satellite reflectance algorithms for estimating chlorophyll-a in a temperate reservoir using coincident hyperspectral aircraft imagery and dense coincident surface observations, Remote Sens. Environ., 2016, 178, 15-30.
31 Chl_Be16NDTIblue chlorophyll_a (w658 - w458) / (w658 + w458) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 543.
32 Chl_Be16NDTIviolet chlorophyll_a (w658 - w444) / (w658 + w444) Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 544.
33 Chl_De933BDA chlorophyll_a w600 - w648 - w625 Dekker, A.; Detection of the optical water quality parameters for eutrophic waters by high resolution remote sensing, Ph.D. thesis, 1993, Free University, Amsterdam.
34 Chl_Gi033BDA chlorophyll_a ((1 / w672) - (1 / w715)) * w757 Gitelson, A.A.; U. Gritz, and M. N. Merzlyak.; Relationships between leaf chlorophyll content and spectral reflectance and algorithms for non-destructive chlorophyll assessment in higher plant leaves. J. Plant Phys. 2003, 160, 271-282.
35 Chl_Kn07KIVU chlorophyll_a (w458 - w644) / w529 Kneubuhler, M.; Frank T.; Kellenberger, T.W; Pasche N.; Schmid M.; Mapping chlorophyll-a in Lake Kivu with remote sensing methods. 2007, Proceedings of the Envisat Symposium 2007, Montreux, Switzerland 23?7 April 2007 (ESA SP-636, July 2007).
36 Chl_MM12NDCI chlorophyll_a (w715 - w686) / (w715 + w686) Mishra, S.; and Mishra, D.R. Normalized difference chlorophyll index: A novel model for remote estimation of chlorophyll-a concentration in turbid productive waters, Remote Sens. Environ., 2012, 117, 394-406
37 Chl_Zh10FLH chlorophyll_a w686 - (w715 + (w672 - w751)) Zhao, D.Z.; Xing, X.G.; Liu, Y.G.; Yang, J.H.; Wang, L. The relation of chlorophyll-a concentration with the reflectance peak near 700 nm in algae-dominated waters and sensitivity of fluorescence algorithms for detecting algal bloom. Int. J. Remote Sens. 2010, 31, 39-48
38 Turb_Be16GreenPlusRedBothOverViolet Turbidity (w558 + w658) / w444 Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 538
39 Turb_Be16RedOverViolet Turbidity w658 / w444 Beck, R.; Xu, M.; Zhan, S.; Liu, H.; Johansen, R.A.; Tong, S.; Yang, B.; Shu, S.; Wu, Q.; Wang, S.; Berling, K.; Murray, A.; Emery, E.; Reif, M.; Harwood, J.; Young, J.; Martin, M.; Stillings, G.; Stumpf, R.; Su, H.; Ye, Z.; Huang, Y. Comparison of Satellite Reflectance Algorithms for Estimating Phycocyanin Values and Cyanobacterial Total Biovolume in a Temperate Reservoir Using Coincident Hyperspectral Aircraft Imagery and Dense Coincident Surface Observations. Remote Sens. 2017, 9, 539
40 Turb_Bow06RedOverGreen Turbidity w658 / w558 Bowers, D. G., and C. E. Binding. 2006. 鈥淭he Optical Properties of Mineral Suspended Particles: A Review and Synthesis.?Estuarine Coastal and Shelf Science 67 (1?): 219?30. doi:10.1016/j.ecss.2005.11.010
41 Turb_Chip09NIROverGreen Turbidity w857 / w558 Chipman, J. W.; Olmanson, L.G.; Gitelson, A.A.; Remote sensing methods for lake management: A guide for resource managers and decision-makers. 2009.
42 Turb_Dox02NIRoverRed Turbidity w857 / w658 Doxaran, D., Froidefond, J.-M.; Castaing, P. ; A reflectance band ratio used to estimate suspended matter concentrations in sediment-dominated coastal waters, Remote Sens., 2002, 23, 5079-5085
43 Turb_Frohn09GreenPlusRedBothOverBlue Turbidity (w558 + w658) / w458 Frohn, R. C., & Autrey, B. C. (2009). Water quality assessment in the Ohio River using new indices for turbidity and chlorophyll-a with Landsat-7 Imagery. Draft Internal Report, US Environmental Protection Agency.
44 Turb_Harr92NIR Turbidity w857 Schiebe F.R., Harrington J.A., Ritchie J.C. Remote-Sensing of Suspended Sediments鈥攖he Lake Chicot, Arkansas Project. Int. J. Remote Sens. 1992;13:1487?509
45 Turb_Lath91RedOverBlue Turbidity w658 / w458 Lathrop, R. G., Jr., T. M. Lillesand, and B. S. Yandell, 1991. Testing the utility of simple multi-date Thematic Mapper calibration algorithms for monitoring turbid inland waters. International Journal of Remote Sensing
46 Turb_Moore80Red Turbidity w658 Moore, G.K., Satellite remote sensing of water turbidity, Hydrological Sciences, 1980, 25, 4, 407-422

139
docs/README_py2exe.md Normal file
View File

@ -0,0 +1,139 @@
# 使用py2exe打包水质分析GUI应用
## 概述
本项目现在支持使用py2exe进行打包这是一个专门用于Windows的Python打包工具。
## 文件说明
- `setup_py2exe.py` - py2exe的配置文件包含所有依赖和打包设置
- `install_py2exe.bat` - 安装py2exe的批处理脚本
- `check_conda.bat` - 诊断工具检查conda安装和配置
- `build_with_py2exe.bat` - 完整构建脚本尝试多种conda激活方法
- `build_with_py2exe_simple.bat` - 简化构建脚本使用conda run最稳定
- `build_with_py2exe.ps1` - PowerShell构建脚本自动查找conda路径
## 快速开始
### 方法1使用PowerShell脚本推荐
右键运行 `build_with_py2exe.ps1` 并选择"使用PowerShell运行"它会自动查找conda并处理所有步骤。
### 方法2使用简单构建脚本
双击运行 `build_with_py2exe_simple.bat`,它使用 `conda run` 方法,最稳定可靠。
### 方法3使用完整构建脚本
双击运行 `build_with_py2exe.bat`它会尝试多种conda激活方法。
### 方法4诊断问题
如果构建失败,首先双击运行 `check_conda.bat` 来诊断conda安装和配置问题。
### 方法5手动步骤
1. **安装py2exe**
```cmd
cd /d E:\code\WQ\fengzhuang
conda activate insect
conda install -c conda-forge py2exe -y
```
2. **运行打包**
```cmd
python setup_py2exe.py py2exe
```
## 输出目录
打包完成后,可执行文件将在 `dist_py2exe/` 目录中:
- `water_quality_gui.exe` - 主程序
- 相关依赖文件
## 配置说明
### 包含的模块
- **科学计算**numpy, scipy, OpenCV
- **地理数据**GDAL, OGR
- **机器学习**XGBoost
- **图像处理**PIL/Pillow, matplotlib
- **GUI**tkinter
- **项目模块**:所有自定义模块
### 数据文件
- `icons/` - 图标文件
- `sub/` - 子目录文件
- `example_config.json` - 配置文件
- `xgboost.dll` - XGBoost动态库
### 排除的模块
排除了大量标准库和测试模块以减小包体积。
## 故障排除
### 0. Conda环境激活失败
**错误信息**`'conda' 不是内部或外部命令`
**解决方案**
1. **推荐**:使用 `build_with_py2exe_simple.bat` 而不是 `build_with_py2exe.bat`
2. 手动初始化conda
```cmd
conda init cmd.exe
```
然后关闭并重新打开命令提示符
3. 检查conda是否在PATH中
```cmd
conda --version
```
4. 如果conda不在PATH中请重新安装Anaconda/Miniconda
### 1. 导入错误
如果运行时出现模块导入错误,可能需要:
- 检查conda环境是否正确
- 添加缺失的模块到 `includes` 列表
- 移除不需要的模块从 `excludes` 列表
### 2. 文件缺失
如果数据文件缺失:
- 检查源文件路径是否正确
- 确认文件存在于项目目录中
### 3. DLL错误
如果出现DLL相关错误
- 检查XGBoost DLL路径
- 添加缺失的DLL到 `dll_excludes` 列表
## 自定义配置
如需修改打包配置,请编辑 `setup_py2exe.py` 文件:
- **添加模块**:在 `packages` 或 `includes` 中添加
- **添加数据文件**:修改 `data_files` 列表
- **排除模块**:在 `excludes` 中添加
- **优化设置**
- `bundle_files`: 1 (单文件), 2 (单目录), 3 (分离)
- `compressed`: True/False (压缩)
- `optimize`: 0, 1, 2 (优化级别)
## 与PyInstaller的比较
| 特性 | py2exe | PyInstaller |
|------|--------|-------------|
| 单文件打包 | 支持 | 支持 |
| Windows专用 | 是 | 跨平台 |
| 包体积 | 较小 | 较大 |
| 兼容性 | 良好 | 优秀 |
| 配置复杂度 | 中等 | 简单 |
## 技术支持
如果遇到问题,请检查:
1. Python版本兼容性
2. conda环境配置
3. 依赖包版本
4. 系统环境变量

65
environment.yml Normal file
View File

@ -0,0 +1,65 @@
# 水质参数反演分析系统 - Conda环境配置
# Water Quality Inversion Analysis System - Conda Environment
# 安装命令: conda env create -f environment.yml
# 更新命令: conda env update -f environment.yml
name: water_quality_analysis
channels:
- conda-forge
dependencies:
# Python版本
- python>=3.12
# 基础科学计算库
- numpy>=1.21.0
- scipy>=1.7.0
- pandas>=1.3.0
# 机器学习库
- scikit-learn>=1.0.0
# - lightgbm>=3.3.0 # 注释掉lightgbm
# 图像处理库
- pillow>=8.0.0
- opencv>=4.5.0
- scikit-image>=0.19.0
# GIS和地理空间处理
- gdal>=3.4.0
- rasterio>=1.2.0
- geopandas>=0.10.0
- shapely>=1.8.0
- fiona>=1.8.0
- pyproj>=3.3.0
# GUI界面库
- pyqt>=5.12.0
# 数据可视化
- matplotlib>=3.5.0
- seaborn>=0.11.0
- matplotlib-scalebar>=0.8.0
# 光谱数据处理
- spectral>=0.23.0
# 小波变换
- pywavelets>=1.3.0
# 并行计算和序列化
- joblib>=1.1.0
# 进度条
- tqdm>=4.62.0
# YAML配置处理
- pyyaml>=6.0
# 打包工具
- pyinstaller>=5.0.0
# 开发工具 (可选,移除以减小环境大小)
# - jupyter
# - notebook
# - ipython

151
pyproject.toml Normal file
View File

@ -0,0 +1,151 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "water-quality-inversion"
version = "1.0.0"
description = "水质参数反演分析系统 - 基于遥感影像和机器学习的水质监测专业软件"
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.8"
authors = [
{name = "Water Quality Research Team", email = "support@waterquality.com"},
]
maintainers = [
{name = "Water Quality Research Team", email = "support@waterquality.com"},
]
keywords = ["water quality", "remote sensing", "machine learning", "GIS", "environmental monitoring"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Science/Research",
"Topic :: Scientific/Engineering :: GIS",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"numpy>=1.21.0",
"pandas>=1.3.0",
"scipy>=1.7.0",
"scikit-learn>=1.0.0",
"matplotlib>=3.5.0",
"opencv-python>=4.5.0",
"gdal>=3.4.0",
"rasterio>=1.2.0",
"shapely>=1.8.0",
"geopandas>=0.10.0",
# "lightgbm>=3.3.0", # 注释掉lightgbm
"xgboost>=1.5.0",
"torch>=1.11.0",
"torchvision>=0.12.0",
"plotly>=5.0.0",
"PyQt5>=5.15.0",
"pyyaml>=6.0",
]
[project.optional-dependencies]
dev = [
"pytest>=6.0",
"pytest-cov>=2.0",
"black>=21.0",
"flake8>=3.9",
"mypy>=0.900",
"pre-commit>=2.17",
]
packaging = [
"pyinstaller>=5.0",
"py2exe>=0.12",
]
docs = [
"sphinx>=4.0",
"sphinx-rtd-theme>=1.0",
]
[project.scripts]
water-quality-gui = "gui.water_quality_gui:main"
water-quality-pipeline = "core.water_quality_inversion_pipeline:main"
[project.urls]
Homepage = "https://github.com/waterquality/water-quality-inversion"
Documentation = "https://water-quality-inversion.readthedocs.io/"
Repository = "https://github.com/waterquality/water-quality-inversion"
Issues = "https://github.com/waterquality/water-quality-inversion/issues"
Changelog = "https://github.com/waterquality/water-quality-inversion/blob/main/CHANGELOG.md"
[tool.setuptools]
zip-safe = false
include-package-data = true
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
"*" = [
"data/icons/*.png",
"data/sub/**/*",
]
[tool.black]
line-length = 88
target-version = ['py38', 'py39', 'py310', 'py311', 'py312']
include = '\.pyi?$'
extend-exclude = '''
/(
# directories
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| build
| dist
| V1
| V2
)/
'''
[tool.isort]
profile = "black"
multi_line_output = 3
line_length = 88
known_first_party = ["src"]
skip = ["__init__.py"]
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true
[[tool.mypy.overrides]]
module = [
"cv2.*",
"gdal.*",
"osgeo.*",
"torch.*",
"torchvision.*",
]
ignore_missing_imports = true
[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -q --cov=src --cov-report=html --cov-report=term-missing"
testpaths = ["tests"]
python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"

View File

@ -0,0 +1,26 @@
numpy>=1.26
scipy>=1.11
pandas>=2.0
scikit-learn>=1.4
xgboost>=2.0
pillow>=10
opencv>=4.8
scikit-image>=0.22
rasterio>=1.3.9
geopandas>=0.14
shapely>=2.0
fiona>=1.9.5
pyproj>=3.6
pyqt>=5.15
matplotlib>=3.8
seaborn>=0.13
matplotlib-scalebar>=0.8
spectral>=0.22
pywavelets>=1.5
joblib>=1.3
tqdm>=4.66
pyyaml>=6.0
openpyxl>=3.1
python-docx>=1.1
lxml>=4.9
pyinstaller>=6.0

52
requirements-conda.txt Normal file
View File

@ -0,0 +1,52 @@
# 水质参数反演分析系统 - Conda环境依赖包
# Water Quality Inversion Analysis System - Conda Dependencies
# 安装命令: conda install -c conda-forge --file requirements-conda.txt
# 基础科学计算库
numpy>=1.21.0
scipy>=1.7.0
pandas>=1.3.0
# 机器学习库
scikit-learn>=1.0.0
xgboost>=1.5.0
# lightgbm>=3.3.0 # 注释掉lightgbm
# 图像处理库
pillow>=8.0.0
opencv>=4.5.0
scikit-image>=0.19.0
# GIS和地理空间处理
gdal>=3.4.0
rasterio>=1.2.0
geopandas>=0.10.0
shapely>=1.8.0
fiona>=1.8.0
pyproj>=3.3.0
# GUI界面库
pyqt>=5.12.0
# 数据可视化
matplotlib>=3.5.0
seaborn>=0.11.0
matplotlib-scalebar>=0.8.0
# 光谱数据处理
spectral>=0.23.0
# 小波变换
pywavelets>=1.3.0
# 并行计算和序列化
joblib>=1.1.0
# 进度条
tqdm>=4.62.0
# YAML配置处理
pyyaml>=6.0
# 打包工具
pyinstaller>=5.0.0

55
requirements-py310.txt Normal file
View File

@ -0,0 +1,55 @@
# 水质参数反演分析系统 - Python 3.10 依赖
# 安装: pip install -r requirements-py310.txt
#
# 说明:
# - Windows 下 GDAL 若 pip 安装失败,建议用 conda-forge: conda install -c conda-forge gdal
# 或使用已编译的 GDAL wheel / OSGeo4W并保证与 rasterio 版本匹配。
#
# ---------- GUI ----------
PyQt5>=5.15.0
matplotlib>=3.5.0
# ---------- 科学计算 ----------
numpy>=1.21.0
scipy>=1.7.0
pandas>=1.3.0
# ---------- 机器学习 ----------
scikit-learn>=1.0.0
# xgboost>=1.5.0 # 可选
# lightgbm>=3.3.0 # 可选
# ---------- 地理空间 ----------
rasterio>=1.2.0
fiona>=1.8.0
shapely>=1.8.0
geopandas>=0.10.0
pyproj>=3.3.0
spectral>=0.22.0
# ---------- 图像 ----------
opencv-python>=4.5.0
Pillow>=8.0.0
scikit-image>=0.19.0
# ---------- 可视化 ----------
seaborn>=0.11.0
matplotlib-scalebar>=0.8.0
# ---------- 信号处理 ----------
PyWavelets>=1.1.0
# ---------- 通用工具 ----------
joblib>=1.1.0
tqdm>=4.62.0
PyYAML>=6.0
# ---------- 表格导出(.xlsx----------
openpyxl>=3.0.0
# ---------- Word 报告生成 ----------
python-docx>=1.1.0
lxml>=4.9.0
# ---------- 打包(可选,仅构建 exe 时需要)----------
pyinstaller>=6.0.0

58
requirements.txt Normal file
View File

@ -0,0 +1,58 @@
# 水质参数反演分析系统 - Python 依赖
# 安装: pip install -r requirements.txt
#
# 说明:
# - Windows 下 GDAL 若 pip 安装失败,建议用 conda-forge: conda install -c conda-forge gdal
# 或使用已编译的 GDAL wheel / OSGeo4W并保证与 rasterio 版本匹配。
# - Word 报告report_word与 GUI「报告生成」页依赖 python-docxAI 解读走 Ollama HTTP API
# 无需额外 pip 包(本地或远程部署 Ollama 即可)。
# ---------- GUI ----------
PyQt5>=5.15.0
# ---------- 科学计算 ----------
# 注:当前工程打包/运行日志显示使用 Python 3.12,因此下限按 Py3.12 兼容版本设置
numpy>=1.26.0
scipy>=1.11.0
pandas>=2.0.0
# ---------- 机器学习 ----------
scikit-learn>=1.4.0
# xgboost>=2.0.0 # 可选;仅在环境已安装时 spec 会自动打入
# lightgbm>=4.0.0 # 可选;当前流水线默认未启用
# ---------- 地理空间 ----------
rasterio>=1.3.9
fiona>=1.9.5
shapely>=2.0.0
geopandas>=0.14.0
pyproj>=3.6.0
spectral>=0.22.0
# ---------- 图像 ----------
opencv-python>=4.5.0
Pillow>=8.0.0
scikit-image>=0.22.0
# ---------- 可视化 ----------
matplotlib>=3.8.0
seaborn>=0.11.0
matplotlib-scalebar>=0.8.0
# ---------- 信号处理 ----------
PyWavelets>=1.1.0
# ---------- 通用工具 ----------
joblib>=1.1.0
tqdm>=4.62.0
PyYAML>=6.0
# ---------- 表格导出(.xlsx----------
openpyxl>=3.0.0
# ---------- Word 报告生成 ----------
python-docx>=1.1.0
lxml>=4.9.0
# ---------- 打包(可选,仅构建 exe 时需要)----------
pyinstaller>=6.0.0

44
scripts/build.bat Normal file
View File

@ -0,0 +1,44 @@
@echo off
chcp 65001 >nul
echo.
echo ================================================
echo 水质参数反演分析系统 - 打包工具
echo ================================================
echo.
:: 检查是否在正确目录
if not exist "src\gui\water_quality_gui.py" (
echo [错误] 请在项目根目录下运行此脚本!
pause
exit /b 1
)
echo [1/4] 清理旧构建文件...
if exist "build" rmdir /s /q build
if exist "dist" rmdir /s /q dist
echo [2/4] 确保依赖已安装...
python -m pip install -r requirements.txt --quiet
python -m pip install pyinstaller --quiet
echo [3/4] 开始打包(首次可能需要 5-15 分钟,请耐心等待)...
pyinstaller --clean scripts/water_quality_gui.spec
echo.
echo [打包提示] 如果仍然出现 "No module named 'styles'",请检查:
echo 1. dist\water_quality_gui\styles.py 是否存在
echo 2. 是否需要添加 --collect-all styles 参数
echo.
echo [4/4] 打包完成!
echo.
echo 输出位置:
echo dist\water_quality_gui\water_quality_gui.exe
echo dist\water_quality_gui\_internal\
echo.
echo 建议:
echo 1. 将 dist 文件夹整个复制给用户(包含所有依赖)
echo 2. 首次运行可能需要 10-30 秒解压(正常现象)
echo 3. 如遇 DLL 缺失,可尝试在 conda 环境中打包
echo.
pause

View File

@ -0,0 +1,25 @@
import os
import sys
def _safe_add(path: str) -> None:
if not path or not os.path.isdir(path):
return
try:
if hasattr(os, "add_dll_directory"):
os.add_dll_directory(path)
except Exception:
pass
try:
os.environ["PATH"] = path + os.pathsep + os.environ.get("PATH", "")
except Exception:
pass
# PyInstaller onefile 解包目录
base = getattr(sys, "_MEIPASS", None)
if base:
_safe_add(base)
_safe_add(os.path.join(base, "lib-dynload"))
_safe_add(os.path.join(base, "DLLs"))

83
setup.py Normal file
View File

@ -0,0 +1,83 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
水质参数反演分析系统 - 安装配置
"""
from setuptools import setup, find_packages
import os
# 读取README文件
def read_readme():
readme_path = os.path.join(os.path.dirname(__file__), 'README.md')
if os.path.exists(readme_path):
with open(readme_path, 'r', encoding='utf-8') as f:
return f.read()
return ""
# 读取requirements.txt
def read_requirements():
requirements_path = os.path.join(os.path.dirname(__file__), 'requirements.txt')
if os.path.exists(requirements_path):
with open(requirements_path, 'r', encoding='utf-8') as f:
return [line.strip() for line in f if line.strip() and not line.startswith('#')]
return []
setup(
name="water-quality-inversion",
version="1.0.0",
author="Water Quality Research Team",
author_email="support@waterquality.com",
description="水质参数反演分析系统 - 基于遥感影像和机器学习的水质监测专业软件",
long_description=read_readme(),
long_description_content_type="text/markdown",
url="https://github.com/waterquality/water-quality-inversion",
packages=find_packages(where="src"),
package_dir={"": "src"},
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Science/Research",
"Topic :: Scientific/Engineering :: GIS",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
keywords="water quality remote sensing machine learning GIS",
python_requires=">=3.8",
install_requires=read_requirements(),
extras_require={
"dev": [
"pytest>=6.0",
"pytest-cov>=2.0",
"black>=21.0",
"flake8>=3.9",
"mypy>=0.900",
],
"gui": [
"PyQt5>=5.15",
"matplotlib>=3.5",
],
"packaging": [
"pyinstaller>=5.0",
"py2exe>=0.12",
],
},
entry_points={
"console_scripts": [
"water-quality-gui=gui.water_quality_gui:main",
"water-quality-pipeline=core.water_quality_inversion_pipeline:main",
],
},
include_package_data=True,
package_data={
"": [
"data/icons/*.png",
"data/sub/**/*",
],
},
zip_safe=False,
)

26
setup_conda_mirrors.bat Normal file
View File

@ -0,0 +1,26 @@
@echo off
REM 配置Conda使用北外镜像源
REM Configure Conda to use BFSU mirrors
echo 配置Conda镜像源...
echo Configuring Conda mirrors...
REM 添加conda-forge镜像
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/cloud/conda-forge/
REM 添加main仓库镜像
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/pkgs/main/
REM 添加free仓库镜像
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/pkgs/free/
REM 显示当前配置
echo.
echo 当前通道配置:
echo Current channel configuration:
conda config --show channels
echo.
echo 配置完成!现在可以使用 environment.yml 创建环境了。
echo Configuration completed! You can now create the environment using environment.yml.
pause

25
setup_conda_mirrors.sh Normal file
View File

@ -0,0 +1,25 @@
#!/bin/bash
# 配置Conda使用北外镜像源
# Configure Conda to use BFSU mirrors
echo "配置Conda镜像源..."
echo "Configuring Conda mirrors..."
# 添加conda-forge镜像
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/cloud/conda-forge/
# 添加main仓库镜像
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/pkgs/main/
# 添加free仓库镜像
conda config --add channels https://mirrors.bfsu.edu.cn/anaconda/pkgs/free/
# 显示当前配置
echo
echo "当前通道配置:"
echo "Current channel configuration:"
conda config --show channels
echo
echo "配置完成!现在可以使用 environment.yml 创建环境了。"
echo "Configuration completed! You can now create the environment using environment.yml."

1
src/__init__.py Normal file
View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

1
src/core/__init__.py Normal file
View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,367 @@
import numpy as np
# import preprocessing
try:
from osgeo import gdal
GDAL_AVAILABLE = True
except ImportError:
GDAL_AVAILABLE = False
print("警告: GDAL未安装将使用numpy处理模式")
try:
from tqdm import tqdm
TQDM_AVAILABLE = True
except ImportError:
TQDM_AVAILABLE = False
# 如果tqdm不可用定义一个简单的包装器
def tqdm(iterable, desc=None, total=None):
return iterable
class Goodman:
def __init__(self, im_aligned, NIR_lower = 25, NIR_upper = 37, A = 0.000019, B = 0.1,
use_gdal=True, chunk_size=None, water_mask=None, output_path=None):
"""
:param im_aligned (np.ndarray or str): band aligned and calibrated & corrected reflectance image
可以是numpy数组或GDAL可读取的文件路径
:param NIR_lower (int): band index which corresponds to 641.93nm, closest band to 640nm
:param NIR_upper (int): band index which corresponds to 751.49nm, closest band to 750nm
:param A (float): the values in Goodman et al's paper, using AVIRIS reflectance (rather than radiance) data
:param B (float): the values in Goodman et al's paper, using AVIRIS reflectance (rather than radiance) data
see Goodman et al, which corrects each pixel independently. The NIR radiance is subtracted from the radiance at each wavelength,
but a wavelength-independent offset is also added.
it is not clear how A and B were chosen, but an optimization for a case where in situ data is
available would enable values to be found
:param use_gdal (bool): 是否使用GDAL加速处理需要GDAL可用且输入为文件路径或大数组
:param chunk_size (int): 已废弃,不再使用分块处理,改为逐波段处理
:param water_mask (np.ndarray or str or None): 水域掩膜1表示水域0表示非水域
可以是numpy数组、栅格文件路径(.dat/.tif)或shapefile路径(.shp)
如果为None则处理全图
:param output_path (str or None): 输出文件路径,如果提供则保存校正后的图像
如果为None则不保存
"""
self.im_aligned = im_aligned
self.NIR_lower = NIR_lower
self.NIR_upper = NIR_upper
self.A = A
self.B = B
self.use_gdal = use_gdal and GDAL_AVAILABLE
self.chunk_size = chunk_size
self.is_file_path = isinstance(im_aligned, str)
self.output_path = output_path
# 获取图像信息(需要在加载掩膜之前获取尺寸)
if self.is_file_path:
if not self.use_gdal:
raise ValueError("输入为文件路径时必须安装GDAL")
self.dataset = gdal.Open(im_aligned, gdal.GA_ReadOnly)
if self.dataset is None:
raise ValueError(f"无法打开影像文件: {im_aligned}")
self.height = self.dataset.RasterYSize
self.width = self.dataset.RasterXSize
self.n_bands = self.dataset.RasterCount
else:
self.dataset = None
self.height = im_aligned.shape[0]
self.width = im_aligned.shape[1]
self.n_bands = im_aligned.shape[-1]
# 加载水域掩膜(在获取图像尺寸之后)
self.water_mask = self._load_water_mask(water_mask)
def _load_water_mask(self, water_mask):
"""
加载水域掩膜
:param water_mask: 可以是None、numpy数组、文件路径(.dat/.tif)或shapefile路径(.shp)
:return: numpy数组或None1表示水域0表示非水域
"""
if water_mask is None:
return None
# 如果已经是numpy数组
if isinstance(water_mask, np.ndarray):
if water_mask.shape[:2] != (self.height, self.width):
raise ValueError(f"掩膜尺寸 {water_mask.shape[:2]} 与图像尺寸 {(self.height, self.width)} 不匹配")
return (water_mask > 0).astype(np.uint8) # 确保是0/1掩膜
# 如果是文件路径
if isinstance(water_mask, str):
if not GDAL_AVAILABLE:
raise ValueError("使用文件路径作为掩膜时必须安装GDAL")
# 检查是否为shapefile
if water_mask.lower().endswith('.shp'):
# 从shp文件创建掩膜
if self.is_file_path:
ref_path = self.im_aligned
else:
raise ValueError("输入为numpy数组时无法从shp文件创建掩膜需要参考栅格")
try:
from osgeo import ogr
ref_dataset = gdal.Open(ref_path, gdal.GA_ReadOnly)
if ref_dataset is None:
raise ValueError(f"无法打开参考栅格文件: {ref_path}")
geotransform = ref_dataset.GetGeoTransform()
projection = ref_dataset.GetProjection()
width = ref_dataset.RasterXSize
height = ref_dataset.RasterYSize
# 创建内存中的栅格数据集
mem_driver = gdal.GetDriverByName('MEM')
mask_dataset = mem_driver.Create('', width, height, 1, gdal.GDT_Byte)
mask_dataset.SetGeoTransform(geotransform)
mask_dataset.SetProjection(projection)
mask_band = mask_dataset.GetRasterBand(1)
mask_band.Fill(0)
# 打开shp文件
shp_dataset = ogr.Open(water_mask)
if shp_dataset is None:
raise ValueError(f"无法打开shp文件: {water_mask}")
layer = shp_dataset.GetLayer()
gdal.RasterizeLayer(mask_dataset, [1], layer, burn_values=[1])
water_mask_array = mask_band.ReadAsArray()
ref_dataset = None
mask_dataset = None
shp_dataset = None
return (water_mask_array > 0).astype(np.uint8)
except Exception as e:
raise ValueError(f"从shp文件创建掩膜时出错: {e}")
else:
# 栅格文件
mask_dataset = gdal.Open(water_mask, gdal.GA_ReadOnly)
if mask_dataset is None:
raise ValueError(f"无法打开掩膜文件: {water_mask}")
mask_array = mask_dataset.GetRasterBand(1).ReadAsArray()
mask_dataset = None
if mask_array.shape != (self.height, self.width):
raise ValueError(f"掩膜尺寸 {mask_array.shape} 与图像尺寸 {(self.height, self.width)} 不匹配")
return (mask_array > 0).astype(np.uint8)
raise ValueError(f"不支持的掩膜类型: {type(water_mask)}")
def _get_corrected_bands_numpy(self):
"""
使用numpy处理用于小图像或GDAL不可用时
注意由于输入已经是numpy数组数据已在内存中。
此方法通过逐波段处理,避免同时创建多个校正后的波段数组。
内存峰值 = 原始数组 + NIR波段(2个) + 当前处理的波段(1个)
"""
# 预提取重复使用的NIR波段避免在循环中重复访问
# 这些波段会一直保存在内存中,因为它们需要用于所有波段的校正
R_640 = self.im_aligned[:,:,self.NIR_lower]
R_750 = self.im_aligned[:,:,self.NIR_upper]
# 预计算常量部分
diff_640_750 = R_640 - R_750
corrected_bands = []
# 获取水域掩膜(如果存在)
water_mask_bool = self.water_mask.astype(bool) if self.water_mask is not None else None
# 逐波段处理:每次只处理一个波段,处理完后立即添加到结果列表
for i in tqdm(range(self.n_bands), desc="处理波段 (numpy)", total=self.n_bands):
# 获取当前波段(这是数组视图,不是复制)
R = self.im_aligned[:,:,i]
# 优化计算:减少中间数组创建
corrected_band = R - R_750 + self.A + self.B * diff_640_750
# 使用np.maximum原地操作将负值设为0
np.maximum(corrected_band, 0, out=corrected_band)
# 如果存在水域掩膜,只对水域区域应用校正
if water_mask_bool is not None:
corrected_band = np.where(water_mask_bool, corrected_band, R)
# 立即添加到结果列表corrected_band会保留在列表中
corrected_bands.append(corrected_band)
return corrected_bands
def _get_corrected_bands_gdal(self):
"""
使用GDAL逐波段处理直接处理整个波段不分块
内存峰值 = NIR波段(2个) + 当前处理的波段(1个) + 已处理的波段(累积在列表中)
"""
corrected_bands = []
# 获取NIR波段对象用于所有波段的校正
band_640 = self.dataset.GetRasterBand(self.NIR_lower + 1) # GDAL波段从1开始
band_750 = self.dataset.GetRasterBand(self.NIR_upper + 1)
# 先读取NIR波段用于所有波段的校正会一直保存在内存中
R_640 = band_640.ReadAsArray().astype(np.float32)
R_750 = band_750.ReadAsArray().astype(np.float32)
diff_640_750 = R_640 - R_750
# 获取水域掩膜
water_mask_bool = self.water_mask.astype(bool) if self.water_mask is not None else None
# 逐波段处理:每次只读取和处理一个波段
for i in tqdm(range(self.n_bands), desc="处理波段 (GDAL)", total=self.n_bands):
# 读取当前波段(只加载一个波段到内存)
current_band = self.dataset.GetRasterBand(i + 1)
R = current_band.ReadAsArray().astype(np.float32)
# 校正计算
corrected_band = R - R_750 + self.A + self.B * diff_640_750
np.maximum(corrected_band, 0, out=corrected_band)
# 如果存在水域掩膜,只对水域区域应用校正
if water_mask_bool is not None:
corrected_band = np.where(water_mask_bool, corrected_band, R)
# 添加到结果列表corrected_band会保留在列表中
corrected_bands.append(corrected_band)
# 释放当前波段数据(显式删除有助于及时释放内存)
del R
return corrected_bands
def _get_corrected_bands_gdal_mem(self):
"""使用GDAL内存驱动处理numpy数组逐波段处理"""
# 创建内存数据集
driver = gdal.GetDriverByName('MEM')
mem_dataset = driver.Create('', self.width, self.height, self.n_bands, gdal.GDT_Float32)
# 将numpy数组写入内存数据集显示进度
for i in tqdm(range(self.n_bands), desc="加载波段到内存", total=self.n_bands):
band = mem_dataset.GetRasterBand(i + 1)
band.WriteArray(self.im_aligned[:,:,i])
band.FlushCache()
# 临时保存原始dataset引用
original_dataset = self.dataset
self.dataset = mem_dataset
try:
# 使用逐波段处理方法
result = self._get_corrected_bands_gdal()
finally:
# 恢复原始dataset
self.dataset = original_dataset
mem_dataset = None
return result
def _save_corrected_bands(self, corrected_bands):
"""
保存校正后的波段到文件BSQ格式ENVI格式
注意:为了节省内存,直接逐波段写入,不先堆叠成完整数组
:param corrected_bands: 校正后的波段列表
"""
if not GDAL_AVAILABLE:
raise ImportError("GDAL未安装无法保存影像文件")
if self.output_path is None:
return
import os
# 确保输出目录存在
output_dir = os.path.dirname(self.output_path)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir, exist_ok=True)
# 从第一个波段获取尺寸信息(避免堆叠所有波段)
if not corrected_bands:
raise ValueError("校正后的波段列表为空")
first_band = corrected_bands[0]
height, width = first_band.shape
n_bands = len(corrected_bands)
# 获取地理变换和投影信息
if self.is_file_path and self.dataset is not None:
geotransform = self.dataset.GetGeoTransform()
projection = self.dataset.GetProjection()
else:
# 如果没有地理信息,使用默认值
geotransform = (0, 1, 0, 0, 0, -1)
projection = ""
# 强制使用ENVI格式BSQ格式确保文件扩展名为.bsq
base_path, ext = os.path.splitext(self.output_path)
# 如果扩展名不是.bsq使用基础路径添加.bsq
if ext.lower() != '.bsq':
bsq_path = base_path + '.bsq'
else:
bsq_path = self.output_path
# 使用ENVI驱动默认就是BSQ格式
driver = gdal.GetDriverByName('ENVI')
if driver is None:
raise ValueError("无法创建ENVI格式文件ENVI驱动不可用")
# 创建ENVI格式数据集会自动生成.hdr文件
dataset = driver.Create(bsq_path, width, height, n_bands, gdal.GDT_Float32)
if dataset is None:
raise ValueError(f"无法创建输出文件: {bsq_path}")
try:
# 设置地理变换和投影
if geotransform:
dataset.SetGeoTransform(geotransform)
if projection:
dataset.SetProjection(projection)
# 直接逐波段写入(不先堆叠,节省内存)
for i in tqdm(range(n_bands), desc="保存波段", total=n_bands):
band = dataset.GetRasterBand(i + 1)
# 直接从列表中获取波段并写入,避免创建完整数组
band.WriteArray(corrected_bands[i])
band.FlushCache()
finally:
dataset = None
# 检查.hdr文件是否已创建
hdr_path = bsq_path + '.hdr'
if os.path.exists(hdr_path):
print(f"校正后的图像已保存至: {bsq_path} (BSQ格式)")
print(f"头文件已保存至: {hdr_path}")
else:
print(f"校正后的图像已保存至: {bsq_path} (BSQ格式)")
print(f"警告: 未检测到.hdr文件但GDAL应该已自动创建")
def get_corrected_bands(self):
"""
获取校正后的波段
根据输入类型和大小自动选择最优处理方法
:return: 校正后的波段列表
"""
# 如果输入是文件路径使用GDAL直接读取
if self.is_file_path:
if self.use_gdal:
corrected_bands = self._get_corrected_bands_gdal()
else:
raise ValueError("输入为文件路径时必须安装GDAL")
else:
# 如果输入是numpy数组
if self.use_gdal and self.height * self.width * self.n_bands > 100000000:
# 大图像使用GDAL内存驱动逐波段处理
corrected_bands = self._get_corrected_bands_gdal_mem()
else:
# 小图像使用numpy直接处理
corrected_bands = self._get_corrected_bands_numpy()
# 如果提供了输出路径,保存结果
if self.output_path is not None:
self._save_corrected_bands(corrected_bands)
return corrected_bands
def __del__(self):
"""清理资源"""
if self.dataset is not None and self.is_file_path:
self.dataset = None

View File

@ -0,0 +1,290 @@
import numpy as np
# import preprocessing
import os
try:
from osgeo import gdal
GDAL_AVAILABLE = True
except ImportError:
GDAL_AVAILABLE = False
class Hedley:
def __init__(self, im_aligned, shp_path=None, NIR_band = 47, water_mask=None, output_path=None):
"""
:param im_aligned (np.ndarray): band aligned and calibrated & corrected reflectance image
:param shp_path (str, optional): path to shapefile (.shp) defining the region containing the glint region in deep water.
If None, uses the entire image. The shapefile can use pixel coordinates or geographic coordinates.
:param NIR_band (int): band index for NIR band which corresponds to 842.36nm, which corresponds closely to the NIR band in Micasense
:param water_mask (np.ndarray or str or None): 水域掩膜1表示水域0表示非水域
可以是numpy数组、栅格文件路径(.dat/.tif)或shapefile路径(.shp)
如果为None则处理全图
:param output_path (str or None): 输出文件路径,如果提供则保存校正后的图像
如果为None则不保存
"""
self.im_aligned = im_aligned
self.bbox = self._read_shp_to_bbox(shp_path) if shp_path else None
self.NIR_band = NIR_band
self.n_bands = im_aligned.shape[-1]
self.height = im_aligned.shape[0]
self.width = im_aligned.shape[1]
self.output_path = output_path
# 加载水域掩膜
self.water_mask = self._load_water_mask(water_mask)
# 使用ravel()而不是flatten(),避免不必要的复制
# 如果存在水域掩膜只在掩膜内计算R_min
if self.water_mask is not None:
nir_band_masked = self.im_aligned[:,:,self.NIR_band][self.water_mask.astype(bool)]
self.R_min = np.percentile(nir_band_masked, 5, interpolation='nearest') if nir_band_masked.size > 0 else 0
else:
self.R_min = np.percentile(self.im_aligned[:,:,self.NIR_band].ravel(), 5, interpolation='nearest')
def _read_shp_to_bbox(self, shp_path):
"""
读取shapefile并提取边界框
:param shp_path (str): shapefile文件路径
:return: tuple: ((x1,y1),(x2,y2)), where x1,y1 is the upper left corner, x2,y2 is the lower right corner
"""
if not os.path.exists(shp_path):
raise FileNotFoundError(f"Shapefile not found: {shp_path}")
try:
try:
import geopandas as gpd
gdf = gpd.read_file(shp_path)
# 获取所有几何体的总边界框
bounds = gdf.total_bounds # [minx, miny, maxx, maxy]
min_x, min_y, max_x, max_y = bounds
except ImportError:
# 如果geopandas不可用尝试使用fiona
import fiona
from shapely.geometry import shape
min_x = float('inf')
min_y = float('inf')
max_x = float('-inf')
max_y = float('-inf')
with fiona.open(shp_path) as shp:
for feature in shp:
geom = shape(feature['geometry'])
if geom:
bounds = geom.bounds
min_x = min(min_x, bounds[0])
min_y = min(min_y, bounds[1])
max_x = max(max_x, bounds[2])
max_y = max(max_y, bounds[3])
# 转换为整数像素坐标
x1 = max(0, int(min_x))
y1 = max(0, int(min_y))
x2 = min(self.im_aligned.shape[1], int(max_x) + 1)
y2 = min(self.im_aligned.shape[0], int(max_y) + 1)
return ((x1, y1), (x2, y2))
except Exception as e:
raise ValueError(f"Error reading shapefile {shp_path}: {e}")
def _load_water_mask(self, water_mask):
"""
加载水域掩膜
:param water_mask: 可以是None、numpy数组、文件路径(.dat/.tif)或shapefile路径(.shp)
:return: numpy数组或None1表示水域0表示非水域
"""
if water_mask is None:
return None
# 如果已经是numpy数组
if isinstance(water_mask, np.ndarray):
if water_mask.shape[:2] != (self.height, self.width):
raise ValueError(f"掩膜尺寸 {water_mask.shape[:2]} 与图像尺寸 {(self.height, self.width)} 不匹配")
return (water_mask > 0).astype(np.uint8) # 确保是0/1掩膜
# 如果是文件路径
if isinstance(water_mask, str):
try:
from osgeo import gdal, ogr
except ImportError:
raise ValueError("使用文件路径作为掩膜时必须安装GDAL")
# 检查是否为shapefile
if water_mask.lower().endswith('.shp'):
# 从shp文件创建掩膜需要参考图像这里假设使用im_aligned的尺寸
# 注意如果输入是numpy数组无法从shp创建掩膜需要提供栅格参考
raise ValueError("Hedley类输入为numpy数组时无法从shp文件创建掩膜。请先栅格化shp文件或提供numpy数组掩膜")
else:
# 栅格文件
mask_dataset = gdal.Open(water_mask, gdal.GA_ReadOnly)
if mask_dataset is None:
raise ValueError(f"无法打开掩膜文件: {water_mask}")
mask_array = mask_dataset.GetRasterBand(1).ReadAsArray()
mask_dataset = None
if mask_array.shape != (self.height, self.width):
raise ValueError(f"掩膜尺寸 {mask_array.shape} 与图像尺寸 {(self.height, self.width)} 不匹配")
return (mask_array > 0).astype(np.uint8)
raise ValueError(f"不支持的掩膜类型: {type(water_mask)}")
def covariance_NIR(self,NIR,b):
"""
NIR & b are vectors
reflectance for band i
"""
n = len(NIR)
# 优化减少重复计算使用更高效的numpy操作
nir_mean = np.mean(NIR)
b_mean = np.mean(b)
# 使用更高效的协方差计算
pij = np.mean((NIR - nir_mean) * (b - b_mean))
pjj = np.mean((NIR - nir_mean) ** 2)
# 避免除零错误
return pij / pjj if pjj != 0 else 0.0
def correlation_bands_reflectance(self):
"""
calculate correlation between NIR and other bands for reflectance
NIR_band is 750 nm
"""
# If bbox is None, use the entire image
if self.bbox is None:
# 使用ravel()而不是flatten(),避免不必要的复制
# 直接使用视图,只在需要时创建扁平数组
im_region = self.im_aligned
mask_region = self.water_mask
else:
((x1,y1),(x2,y2)) = self.bbox
im_region = self.im_aligned[y1:y2,x1:x2,:]
mask_region = self.water_mask[y1:y2,x1:x2] if self.water_mask is not None else None
# 如果存在水域掩膜,只在掩膜内计算相关性
if mask_region is not None:
mask_bool = mask_region.astype(bool)
if mask_bool.any():
# 只在掩膜内提取数据
NIR_reflectance = im_region[:,:,self.NIR_band][mask_bool]
else:
# 如果掩膜内没有有效像素,使用全区域
NIR_reflectance = im_region[:,:,self.NIR_band].ravel()
mask_bool = None
else:
NIR_reflectance = im_region[:,:,self.NIR_band].ravel()
mask_bool = None
# 优化:一次性计算所有波段的相关性,减少循环开销
corr_list = []
for v in range(self.n_bands):
if mask_bool is not None and mask_bool.any():
band_reflectance = im_region[:,:,v][mask_bool]
else:
band_reflectance = im_region[:,:,v].ravel()
corr = self.covariance_NIR(NIR_reflectance, band_reflectance)
corr_list.append(corr)
return corr_list
def _save_corrected_bands(self, corrected_bands):
"""
保存校正后的波段到文件BSQ格式ENVI格式
:param corrected_bands: 校正后的波段列表
"""
if not GDAL_AVAILABLE:
raise ImportError("GDAL未安装无法保存影像文件")
if self.output_path is None:
return
# 确保输出目录存在
output_dir = os.path.dirname(self.output_path)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir, exist_ok=True)
# 将波段列表转换为数组
corrected_array = np.stack(corrected_bands, axis=2)
# 如果没有地理信息,使用默认值
geotransform = (0, 1, 0, 0, 0, -1)
projection = ""
# 强制使用ENVI格式BSQ格式确保文件扩展名为.bsq
base_path, ext = os.path.splitext(self.output_path)
# 如果扩展名不是.bsq使用基础路径添加.bsq
if ext.lower() != '.bsq':
bsq_path = base_path + '.bsq'
else:
bsq_path = self.output_path
# 使用ENVI驱动默认就是BSQ格式
driver = gdal.GetDriverByName('ENVI')
if driver is None:
raise ValueError("无法创建ENVI格式文件ENVI驱动不可用")
height, width, n_bands = corrected_array.shape
# 创建ENVI格式数据集会自动生成.hdr文件
dataset = driver.Create(bsq_path, width, height, n_bands, gdal.GDT_Float32)
if dataset is None:
raise ValueError(f"无法创建输出文件: {bsq_path}")
try:
# 设置地理变换和投影
if geotransform:
dataset.SetGeoTransform(geotransform)
if projection:
dataset.SetProjection(projection)
# 写入每个波段BSQ格式按波段顺序存储
for i in range(n_bands):
band = dataset.GetRasterBand(i + 1)
band.WriteArray(corrected_array[:, :, i])
band.FlushCache()
finally:
dataset = None
# 检查.hdr文件是否已创建
hdr_path = bsq_path + '.hdr'
if os.path.exists(hdr_path):
print(f"校正后的图像已保存至: {bsq_path} (BSQ格式)")
print(f"头文件已保存至: {hdr_path}")
else:
print(f"校正后的图像已保存至: {bsq_path} (BSQ格式)")
print(f"警告: 未检测到.hdr文件但GDAL应该已自动创建")
def get_corrected_bands(self):
"""
correction is done in reflectance
:return: 校正后的波段列表
"""
corr = self.correlation_bands_reflectance()
NIR_reflectance = self.im_aligned[:,:,self.NIR_band]
# 预计算NIR-R_min避免在循环中重复计算
NIR_diff = NIR_reflectance - self.R_min
# 获取水域掩膜(如果存在)
water_mask_bool = self.water_mask.astype(bool) if self.water_mask is not None else None
corrected_bands = []
for band_number in range(self.n_bands): #iterate across bands
b = corr[band_number]
R = self.im_aligned[:,:,band_number]
# 优化:减少中间数组创建
corrected_band = R - b * NIR_diff
# 如果存在水域掩膜,只对水域区域应用校正
if water_mask_bool is not None:
corrected_band = np.where(water_mask_bool, corrected_band, R)
corrected_bands.append(corrected_band)
# 如果提供了输出路径,保存结果
if self.output_path is not None:
self._save_corrected_bands(corrected_bands)
return corrected_bands

View File

@ -0,0 +1,313 @@
import numpy as np
# import preprocessing
import os
try:
from osgeo import gdal
GDAL_AVAILABLE = True
except ImportError:
GDAL_AVAILABLE = False
class Kutser:
def __init__(self, im_aligned, shp_path=None, oxy_band = 38,lower_oxy = 36, upper_oxy = 49, NIR_band = 47, water_mask=None, output_path=None):
"""
:param im_aligned (np.ndarray): band aligned and calibrated & corrected reflectance image
:param shp_path (str, optional): path to shapefile (.shp) defining the region containing the glint region in deep water.
If None, uses the entire image. The shapefile can use pixel coordinates or geographic coordinates.
:param oxy_band (int): band index for oxygen absorption band, which corresponds to 760.6nm
:param lower_oxy (int): band index for outside oxygen absorption band, which corresponds to 742.39nm
:param upper_oxy (int): band index for outside oxygen absorption band, which corresponds to 860.48nm
see Kutser, Vahtmäe and Praks
:param water_mask (np.ndarray or str or None): 水域掩膜1表示水域0表示非水域
可以是numpy数组、栅格文件路径(.dat/.tif)或shapefile路径(.shp)
如果为None则处理全图
:param output_path (str or None): 输出文件路径,如果提供则保存校正后的图像
如果为None则不保存
"""
self.im_aligned = im_aligned
self.bbox = self._read_shp_to_bbox(shp_path) if shp_path else None
self.oxy_band = oxy_band
self.lower_oxy = lower_oxy
self.upper_oxy = upper_oxy
self.NIR_band = NIR_band
self.n_bands = im_aligned.shape[-1]
self.height = im_aligned.shape[0]
self.width = im_aligned.shape[1]
self.output_path = output_path
# 加载水域掩膜
self.water_mask = self._load_water_mask(water_mask)
# 使用ravel()而不是flatten(),避免不必要的复制
# 如果存在水域掩膜只在掩膜内计算R_min
if self.water_mask is not None:
nir_band_masked = self.im_aligned[:,:,self.NIR_band][self.water_mask.astype(bool)]
self.R_min = np.percentile(nir_band_masked, 5, interpolation='nearest') if nir_band_masked.size > 0 else 0
else:
self.R_min = np.percentile(self.im_aligned[:,:,self.NIR_band].ravel(), 5, interpolation='nearest')
def _read_shp_to_bbox(self, shp_path):
"""
读取shapefile并提取边界框
:param shp_path (str): shapefile文件路径
:return: tuple: ((x1,y1),(x2,y2)), where x1,y1 is the upper left corner, x2,y2 is the lower right corner
"""
if not os.path.exists(shp_path):
raise FileNotFoundError(f"Shapefile not found: {shp_path}")
try:
try:
import geopandas as gpd
gdf = gpd.read_file(shp_path)
# 获取所有几何体的总边界框
bounds = gdf.total_bounds # [minx, miny, maxx, maxy]
min_x, min_y, max_x, max_y = bounds
except ImportError:
# 如果geopandas不可用尝试使用fiona
import fiona
from shapely.geometry import shape
min_x = float('inf')
min_y = float('inf')
max_x = float('-inf')
max_y = float('-inf')
with fiona.open(shp_path) as shp:
for feature in shp:
geom = shape(feature['geometry'])
if geom:
bounds = geom.bounds
min_x = min(min_x, bounds[0])
min_y = min(min_y, bounds[1])
max_x = max(max_x, bounds[2])
max_y = max(max_y, bounds[3])
# 转换为整数像素坐标
x1 = max(0, int(min_x))
y1 = max(0, int(min_y))
x2 = min(self.im_aligned.shape[1], int(max_x) + 1)
y2 = min(self.im_aligned.shape[0], int(max_y) + 1)
return ((x1, y1), (x2, y2))
except Exception as e:
raise ValueError(f"Error reading shapefile {shp_path}: {e}")
def _load_water_mask(self, water_mask):
"""
加载水域掩膜
:param water_mask: 可以是None、numpy数组、文件路径(.dat/.tif)或shapefile路径(.shp)
:return: numpy数组或None1表示水域0表示非水域
"""
if water_mask is None:
return None
# 如果已经是numpy数组
if isinstance(water_mask, np.ndarray):
if water_mask.shape[:2] != (self.height, self.width):
raise ValueError(f"掩膜尺寸 {water_mask.shape[:2]} 与图像尺寸 {(self.height, self.width)} 不匹配")
return (water_mask > 0).astype(np.uint8) # 确保是0/1掩膜
# 如果是文件路径
if isinstance(water_mask, str):
try:
from osgeo import gdal, ogr
except ImportError:
raise ValueError("使用文件路径作为掩膜时必须安装GDAL")
# 检查是否为shapefile
if water_mask.lower().endswith('.shp'):
# 从shp文件创建掩膜需要参考图像这里假设使用im_aligned的尺寸
# 注意如果输入是numpy数组无法从shp创建掩膜需要提供栅格参考
raise ValueError("Kutser类输入为numpy数组时无法从shp文件创建掩膜。请先栅格化shp文件或提供numpy数组掩膜")
else:
# 栅格文件
mask_dataset = gdal.Open(water_mask, gdal.GA_ReadOnly)
if mask_dataset is None:
raise ValueError(f"无法打开掩膜文件: {water_mask}")
mask_array = mask_dataset.GetRasterBand(1).ReadAsArray()
mask_dataset = None
if mask_array.shape != (self.height, self.width):
raise ValueError(f"掩膜尺寸 {mask_array.shape} 与图像尺寸 {(self.height, self.width)} 不匹配")
return (mask_array > 0).astype(np.uint8)
raise ValueError(f"不支持的掩膜类型: {type(water_mask)}")
def get_depth_D(self):
"""
Assume the amount of glint is proportional to the depth of the oxygen absorption feature, D
returns the normalised D by dividing it by the maximum D found in a deep water region
"""
# 优化:减少中间数组创建,使用更高效的计算
lower_oxy_band = self.im_aligned[:,:,self.lower_oxy]
upper_oxy_band = self.im_aligned[:,:,self.upper_oxy]
oxy_band = self.im_aligned[:,:,self.oxy_band]
D = (lower_oxy_band + upper_oxy_band) * 0.5 - oxy_band
# 确定用于计算D_max的区域
if self.bbox is None:
search_region = D
else:
((x1,y1),(x2,y2)) = self.bbox
search_region = D[y1:y2,x1:x2]
# 如果存在水域掩膜,只在掩膜内搜索最大值
if self.water_mask is not None:
if self.bbox is None:
mask_region = self.water_mask.astype(bool)
else:
((x1,y1),(x2,y2)) = self.bbox
mask_region = self.water_mask[y1:y2,x1:x2].astype(bool)
if mask_region.any():
D_max = search_region[mask_region].max()
else:
D_max = search_region.max()
else:
D_max = search_region.max() # assumed to be the maximum glint value
# 避免除零错误
if D_max == 0:
return np.zeros_like(D)
return D / D_max
def get_glint_G(self):
"""
The spectral variation of glint G is found by subtracting the spectrum at the darkest (ie. lowest D) NIR deep-water pixel from the brightest
returns G as a function of wavelength
"""
# If bbox is None, use the entire image
if self.bbox is None:
im_region = self.im_aligned
mask_region = self.water_mask
else:
((x1,y1),(x2,y2)) = self.bbox
im_region = self.im_aligned[y1:y2,x1:x2,:]
mask_region = self.water_mask[y1:y2,x1:x2] if self.water_mask is not None else None
# 如果存在水域掩膜,只在掩膜内计算最大最小值
if mask_region is not None:
mask_bool = mask_region.astype(bool)
if mask_bool.any():
# 对每个波段,只在掩膜内计算最大最小值
G_list = []
for i in range(self.n_bands):
band_data = im_region[:,:,i]
G_max = band_data[mask_bool].max()
G_min = band_data[mask_bool].min()
G_list.append(G_max - G_min)
else:
# 如果掩膜内没有有效像素,使用全区域
G_max = np.amax(im_region, axis=(0, 1))
G_min = np.amin(im_region, axis=(0, 1))
G_list = (G_max - G_min).tolist()
else:
# 优化:一次性计算所有波段的最大最小值,减少循环开销
# 使用numpy的amax和amin沿最后一个轴计算
G_max = np.amax(im_region, axis=(0, 1)) # 沿空间维度计算最大值
G_min = np.amin(im_region, axis=(0, 1)) # 沿空间维度计算最小值
G_list = (G_max - G_min).tolist()
return G_list
def _save_corrected_bands(self, corrected_bands):
"""
保存校正后的波段到文件BSQ格式ENVI格式
:param corrected_bands: 校正后的波段列表
"""
if not GDAL_AVAILABLE:
raise ImportError("GDAL未安装无法保存影像文件")
if self.output_path is None:
return
# 确保输出目录存在
output_dir = os.path.dirname(self.output_path)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir, exist_ok=True)
# 将波段列表转换为数组
corrected_array = np.stack(corrected_bands, axis=2)
# 如果没有地理信息,使用默认值
geotransform = (0, 1, 0, 0, 0, -1)
projection = ""
# 强制使用ENVI格式BSQ格式确保文件扩展名为.bsq
base_path, ext = os.path.splitext(self.output_path)
# 如果扩展名不是.bsq使用基础路径添加.bsq
if ext.lower() != '.bsq':
bsq_path = base_path + '.bsq'
else:
bsq_path = self.output_path
# 使用ENVI驱动默认就是BSQ格式
driver = gdal.GetDriverByName('ENVI')
if driver is None:
raise ValueError("无法创建ENVI格式文件ENVI驱动不可用")
height, width, n_bands = corrected_array.shape
# 创建ENVI格式数据集会自动生成.hdr文件
dataset = driver.Create(bsq_path, width, height, n_bands, gdal.GDT_Float32)
if dataset is None:
raise ValueError(f"无法创建输出文件: {bsq_path}")
try:
# 设置地理变换和投影
if geotransform:
dataset.SetGeoTransform(geotransform)
if projection:
dataset.SetProjection(projection)
# 写入每个波段BSQ格式按波段顺序存储
for i in range(n_bands):
band = dataset.GetRasterBand(i + 1)
band.WriteArray(corrected_array[:, :, i])
band.FlushCache()
finally:
dataset = None
# 检查.hdr文件是否已创建
hdr_path = bsq_path + '.hdr'
if os.path.exists(hdr_path):
print(f"校正后的图像已保存至: {bsq_path} (BSQ格式)")
print(f"头文件已保存至: {hdr_path}")
else:
print(f"校正后的图像已保存至: {bsq_path} (BSQ格式)")
print(f"警告: 未检测到.hdr文件但GDAL应该已自动创建")
def get_corrected_bands(self):
"""
correction is done in reflectance
:return: 校正后的波段列表
"""
g_list = self.get_glint_G()
D = self.get_depth_D()
# 获取水域掩膜(如果存在)
water_mask_bool = self.water_mask.astype(bool) if self.water_mask is not None else None
corrected_bands = []
for band_number in range(self.n_bands): #iterate across bands
G = g_list[band_number]
R = self.im_aligned[:,:,band_number]
# 优化:减少中间数组创建,直接计算
corrected_band = R - G * D
# 如果存在水域掩膜,只对水域区域应用校正
if water_mask_bool is not None:
corrected_band = np.where(water_mask_bool, corrected_band, R)
corrected_bands.append(corrected_band)
# 如果提供了输出路径,保存结果
if self.output_path is not None:
self._save_corrected_bands(corrected_bands)
return corrected_bands

View File

@ -0,0 +1,572 @@
import cv2
import os
import numpy as np
from scipy import ndimage
from scipy.optimize import minimize_scalar
try:
from osgeo import gdal
GDAL_AVAILABLE = True
except ImportError:
GDAL_AVAILABLE = False
# SUn-Glint-Aware Restoration (SUGAR):A sweet and simple algorithm for correcting sunglint
class SUGAR:
def __init__(self, im_aligned,bounds=[(1,2)],sigma=1,estimate_background=True, glint_mask_method="cdf", water_mask=None, output_path=None):
"""
:param im_aligned (np.ndarray): band aligned and calibrated & corrected reflectance image
:param bounds (a list of tuple): lower and upper bound for optimisation of b for each band
:param sigma (float): smoothing sigma for LoG
:param estimate_background (bool): whether to estimate background spectra using median filtering
:param glint_mask_method (str): choose either "cdf" or "otsu", "cdf" is set as the default
:param water_mask (np.ndarray or str or None): 水域掩膜1表示水域0表示非水域
可以是numpy数组、栅格文件路径(.dat/.tif)或shapefile路径(.shp)
如果为None则处理全图
:param output_path (str or None): 输出文件路径,如果提供则保存校正后的图像
如果为None则不保存
"""
self.im_aligned = im_aligned
self.sigma = sigma
self.estimate_background = estimate_background
self.n_bands = im_aligned.shape[-1]
self.bounds = bounds*self.n_bands
self.glint_mask_method = glint_mask_method
self.height = im_aligned.shape[0]
self.width = im_aligned.shape[1]
self.output_path = output_path
# 加载水域掩膜
self.water_mask = self._load_water_mask(water_mask)
def _load_water_mask(self, water_mask):
"""
加载水域掩膜
:param water_mask: 可以是None、numpy数组、文件路径(.dat/.tif)或shapefile路径(.shp)
:return: numpy数组或None1表示水域0表示非水域
"""
if water_mask is None:
return None
# 如果已经是numpy数组
if isinstance(water_mask, np.ndarray):
if water_mask.shape[:2] != (self.height, self.width):
raise ValueError(f"掩膜尺寸 {water_mask.shape[:2]} 与图像尺寸 {(self.height, self.width)} 不匹配")
return (water_mask > 0).astype(np.uint8) # 确保是0/1掩膜
# 如果是文件路径
if isinstance(water_mask, str):
try:
from osgeo import gdal, ogr
except ImportError:
raise ValueError("使用文件路径作为掩膜时必须安装GDAL")
# 检查是否为shapefile
if water_mask.lower().endswith('.shp'):
# 从shp文件创建掩膜需要参考图像这里假设使用im_aligned的尺寸
# 注意如果输入是numpy数组无法从shp创建掩膜需要提供栅格参考
raise ValueError("SUGAR类输入为numpy数组时无法从shp文件创建掩膜。请先栅格化shp文件或提供numpy数组掩膜")
else:
# 栅格文件
mask_dataset = gdal.Open(water_mask, gdal.GA_ReadOnly)
if mask_dataset is None:
raise ValueError(f"无法打开掩膜文件: {water_mask}")
mask_array = mask_dataset.GetRasterBand(1).ReadAsArray()
mask_dataset = None
if mask_array.shape != (self.height, self.width):
raise ValueError(f"掩膜尺寸 {mask_array.shape} 与图像尺寸 {(self.height, self.width)} 不匹配")
return (mask_array > 0).astype(np.uint8)
raise ValueError(f"不支持的掩膜类型: {type(water_mask)}")
def otsu_thresholding(self,im):
"""
:param im (np.ndarray) of shape mxn. Note that it is the LoG of image
otsu thresholding with Brent's minimisation of a univariate function
returns the value of the threshold for input
"""
auto_bins = int(0.005*im.shape[0]*im.shape[1])
# 使用ravel()而不是flatten(),避免不必要的复制(如果可能)
# 如果存在无效值如NaN或极大值过滤掉它们
im_flat = im.ravel()
# 过滤掉NaN和无穷大值
valid_mask = np.isfinite(im_flat)
if not valid_mask.all():
im_flat = im_flat[valid_mask]
count, bin_edges = np.histogram(im_flat, bins=auto_bins)
bin = (bin_edges[:-1] + bin_edges[1:]) * 0.5 # bin centers使用乘法替代除法
count_sum = count.sum()
hist_norm = count / count_sum # normalised histogram
Q = hist_norm.cumsum() # CDF function ranges from 0 to 1
N = count.shape[0]
N_negative = np.sum(bin < 0)
bins = np.arange(N, dtype=np.float32) # 使用float32减少内存
def otsu_thresh(x):
x = int(x)
# 使用切片而不是hsplit避免创建新数组
p1 = hist_norm[:x]
p2 = hist_norm[x:]
q1 = Q[x]
q2 = Q[N-1] - Q[x]
b1 = bins[:x]
b2 = bins[x:]
# finding means and variances
m1 = np.sum(p1 * b1) / q1 if q1 > 0 else 0
m2 = np.sum(p2 * b2) / q2 if q2 > 0 else 0
v1 = np.sum(((b1 - m1) ** 2) * p1) / q1 if q1 > 0 else 0
v2 = np.sum(((b2 - m2) ** 2) * p2) / q2 if q2 > 0 else 0
# calculates the minimization function
fn = v1 * q1 + v2 * q2
return fn
# brent method is used to minimise an univariate function
# bounded minimisation
# we can just limit the search to negative values since we know thresh should be negative as L<0 for glint pixels
if N_negative <= 1:
# 如果没有足够的负值,使用默认阈值
return bin[np.argmax(count)]
res = minimize_scalar(otsu_thresh, bounds=(1, N_negative), method='bounded')
thresh = bin[int(res.x)]
return thresh
# def cdf_thresholding(self,im, percentile=0.05):
# """
# :param im (np.ndarray) of shape mxn
# :param percentile (float): lower and upper percentile values are potential glint pixels
# """
# lower_perc = percentile
# upper_perc = 1-percentile
# im_flatten = im.flatten()
# H,X1 = np.histogram(im_flatten, bins = int(0.005*im.shape[0]*im.shape[1]), density=True )
# dx = X1[1] - X1[0]
# F1 = np.cumsum(H)*dx
# F_lower = X1[1:][F1<lower_perc]
# F_upper = X1[1:][F1>upper_perc]
# while((F_lower.size == 0) or (F_upper.size == 0)):
# if (F_lower.size == 0):
# lower_perc += 0.01
# F_lower = X1[1:][F1<lower_perc]
# if (F_upper.size == 0):
# upper_perc -= 0.01
# F_upper = X1[1:][F1>upper_perc]
# lower_thresh = F_lower[-1]
# upper_thresh = F_upper[0]
# return lower_thresh,upper_thresh
def cdf_thresholding(self,im,auto_bins=10):
"""
:param im (np.ndarray) of shape mxn. Note that it is the LoG of image
:param percentile (float): lower and upper percentile values are potential glint pixels
"""
# 使用ravel()而不是flatten(),避免不必要的复制
im_flat = im.ravel()
# 过滤掉NaN和无穷大值
valid_mask = np.isfinite(im_flat)
if not valid_mask.all():
im_flat = im_flat[valid_mask]
count, bin_edges = np.histogram(im_flat, bins=auto_bins)
bin = (bin_edges[:-1] + bin_edges[1:]) * 0.5 # bin centers使用乘法替代除法
thresh = bin[np.argmax(count)]
return thresh
def glint_list(self):
"""
returns a list of np.ndarray, where each item is an extracted glint for each band based on get_glint_mask
"""
glint_mask = self.glint_mask_list()
extracted_glint_list = []
for i in range(self.im_aligned.shape[-1]):
gm = glint_mask[i]
extracted_glint = gm*self.im_aligned[:,:,i]
extracted_glint_list.append(extracted_glint)
return extracted_glint_list
def glint_mask_list(self):
"""
get glint mask using laplacian of gaussian image.
returns a list of np.ndarray
"""
glint_mask_list = []
for i in range(self.im_aligned.shape[-1]):
glint_mask = self.get_glint_mask(self.im_aligned[:,:,i])
glint_mask_list.append(glint_mask)
return glint_mask_list
def log_image_list(self):
"""
get Laplacian of Gaussian (LoG) images for all bands.
returns a list of np.ndarray
"""
log_image_list = []
for i in range(self.im_aligned.shape[-1]):
log_im = self.get_log_image(self.im_aligned[:,:,i])
log_image_list.append(log_im)
return log_image_list
def get_log_image(self, im):
"""
get Laplacian of Gaussian (LoG) image for a single band.
returns a np.ndarray
"""
LoG_im = ndimage.gaussian_laplace(im, sigma=self.sigma)
return LoG_im
def get_glint_mask(self,im):
"""
get glint mask using laplacian of gaussian image.
We assume that water constituents and features follow a smooth continuum,
but glint pixels vary a lot spatially and in intensities
Note that for very extensive glint, this method may not work as well <--:TODO use U-net to identify glint mask
returns a np.ndarray
"""
LoG_im = ndimage.gaussian_laplace(im,sigma=self.sigma)
# 如果存在水域掩膜,只在掩膜内计算阈值
if self.water_mask is not None:
mask_bool = self.water_mask.astype(bool)
if mask_bool.any():
# 只在掩膜内提取LoG值用于阈值计算
LoG_masked = LoG_im[mask_bool]
# 将非掩膜区域设为极大值,确保不影响阈值计算
LoG_for_thresh = LoG_im.copy()
LoG_for_thresh[~mask_bool] = LoG_masked.max() + 1
else:
LoG_for_thresh = LoG_im
else:
LoG_for_thresh = LoG_im
#threshold mask
if (self.glint_mask_method == "otsu"):
thresh = self.otsu_thresholding(LoG_for_thresh)
elif (self.glint_mask_method == "cdf"):
thresh = self.cdf_thresholding(LoG_for_thresh)
else:
raise ValueError('Enter only cdf or otsu as glint_mask_method')
# 使用更高效的方式创建mask避免np.where的开销
glint_mask = (LoG_im < thresh).astype(np.uint8)
# 如果存在水域掩膜将非水域区域设为0
if self.water_mask is not None:
glint_mask = glint_mask * self.water_mask
return glint_mask
def get_est_background(self, im,k_size=5):
"""
:param im (np.ndarray): image of a band
estimate background spectra
returns a np.ndarray
"""
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(k_size,k_size))
dst = cv2.erode(im, kernel)
return dst
def optimise_correction_by_band(self,im,glint_mask,R_BG,bounds):
"""
:param im (np.ndarray): image of a band
:param glint_mask (np.ndarray): glint mask, where glint area is 1 and non-glint area is 0
use brent method to get the optimimum b which minimises the variation (i.e. variance) in the entire image
returns regression slope b
"""
# 预计算常量,避免在优化函数中重复计算
glint_mask_bool = glint_mask.astype(bool)
R_BG_flat = R_BG if isinstance(R_BG, (int, float)) else R_BG[glint_mask_bool]
def optimise_b(b):
# 优化计算只在glint区域计算校正
if isinstance(R_BG, (int, float)):
im_corrected = im.copy()
im_corrected[glint_mask_bool] = im[glint_mask_bool] - glint_mask[glint_mask_bool] * (im[glint_mask_bool] / b - R_BG)
else:
im_corrected = im.copy()
im_corrected[glint_mask_bool] = im[glint_mask_bool] - glint_mask[glint_mask_bool] * (im[glint_mask_bool] / b - R_BG[glint_mask_bool])
return np.var(im_corrected)
res = minimize_scalar(optimise_b, bounds=bounds, method='bounded')
return res.x
def divide_and_conquer(self):
"""
instead of computing b_list for each window, use the previous b_list to narrow the bounds,
because of the strong spatial autocorrelation, we know that the b (correction magnitude) cannot diff too much
this can optimise the run time
"""
def optimise_correction(self):
"""
returns a list of slope in band order i.e. 0,1,2,3,4,5,6,7,8,9 through optimisation
"""
b_list = []
glint_mask_list = []
est_background_list = []
for i in range(self.n_bands):
glint_mask = self.get_glint_mask(self.im_aligned[:,:,i])
glint_mask_list.append(glint_mask)
if self.estimate_background is True:
est_background = self.get_est_background(self.im_aligned[:,:,i])
est_background_list.append(est_background)
else:
est_background = np.percentile(self.im_aligned[:,:,i], 5, interpolation='nearest')
est_background_list.append(est_background)
bounds = self.bounds[i]
b = self.optimise_correction_by_band(self.im_aligned[:,:,i],glint_mask,est_background,bounds)
b_list.append(b)
# add attributes
self.b_list = b_list
self.glint_mask = glint_mask_list
self.est_background = est_background_list
return b_list, glint_mask_list, est_background_list
def _save_corrected_bands(self, corrected_bands):
"""
保存校正后的波段到文件BSQ格式ENVI格式
:param corrected_bands: 校正后的波段列表
"""
if not GDAL_AVAILABLE:
raise ImportError("GDAL未安装无法保存影像文件")
if self.output_path is None:
return
# 确保输出目录存在
output_dir = os.path.dirname(self.output_path)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir, exist_ok=True)
# 将波段列表转换为数组
corrected_array = np.stack(corrected_bands, axis=2)
# 如果没有地理信息,使用默认值
geotransform = (0, 1, 0, 0, 0, -1)
projection = ""
# 强制使用ENVI格式BSQ格式确保文件扩展名为.bsq
base_path, ext = os.path.splitext(self.output_path)
# 如果扩展名不是.bsq使用基础路径添加.bsq
if ext.lower() != '.bsq':
bsq_path = base_path + '.bsq'
else:
bsq_path = self.output_path
# 使用ENVI驱动默认就是BSQ格式
driver = gdal.GetDriverByName('ENVI')
if driver is None:
raise ValueError("无法创建ENVI格式文件ENVI驱动不可用")
height, width, n_bands = corrected_array.shape
# 创建ENVI格式数据集会自动生成.hdr文件
dataset = driver.Create(bsq_path, width, height, n_bands, gdal.GDT_Float32)
if dataset is None:
raise ValueError(f"无法创建输出文件: {bsq_path}")
try:
# 设置地理变换和投影
if geotransform:
dataset.SetGeoTransform(geotransform)
if projection:
dataset.SetProjection(projection)
# 写入每个波段BSQ格式按波段顺序存储
for i in range(n_bands):
band = dataset.GetRasterBand(i + 1)
band.WriteArray(corrected_array[:, :, i])
band.FlushCache()
finally:
dataset = None
# 检查.hdr文件是否已创建
hdr_path = bsq_path + '.hdr'
if os.path.exists(hdr_path):
print(f"校正后的图像已保存至: {bsq_path} (BSQ格式)")
print(f"头文件已保存至: {hdr_path}")
else:
print(f"校正后的图像已保存至: {bsq_path} (BSQ格式)")
print(f"警告: 未检测到.hdr文件但GDAL应该已自动创建")
def get_corrected_bands(self):
"""
获取校正后的波段
:return: 校正后的波段列表
"""
corrected_bands = []
# 获取水域掩膜(如果存在)
water_mask_bool = self.water_mask.astype(bool) if self.water_mask is not None else None
for i in range(self.n_bands):
im_band = self.im_aligned[:,:,i]
# 一次性计算mask和background避免重复计算
glint_mask = self.get_glint_mask(im_band)
background = self.get_est_background(im_band, k_size=5)
# 使用视图和原地操作减少内存
im_corrected = im_band.copy()
glint_mask_bool = glint_mask.astype(bool)
im_corrected[glint_mask_bool] = background[glint_mask_bool]
# 如果存在水域掩膜,确保只在水域内应用校正
if water_mask_bool is not None:
# 只在水域掩膜内应用校正
correction_mask = glint_mask_bool & water_mask_bool
im_corrected = np.where(correction_mask, background, im_band)
# 非水域区域保持原值
im_corrected = np.where(water_mask_bool, im_corrected, im_band)
corrected_bands.append(im_corrected)
# 如果提供了输出路径,保存结果
if self.output_path is not None:
self._save_corrected_bands(corrected_bands)
return corrected_bands
def correction_iterative(im_aligned,iter=3,bounds = [(1,2)],estimate_background=True,glint_mask_method="cdf",get_glint_mask=False,termination_thresh = 20, water_mask=None, output_path=None):
"""
:param im_aligned (np.ndarray): band aligned and calibrated & corrected reflectance image
:param iter (int or None): number of iterations to run the sugar algorithm. If None, termination conditions are automatically applied
:param bounds (list of tuples): to limit correction magnitude
:param get_glint_mask (np.ndarray):
:param water_mask (np.ndarray or str or None): 水域掩膜1表示水域0表示非水域
可以是numpy数组、栅格文件路径(.dat/.tif)或shapefile路径(.shp)
如果为None则处理全图
:param output_path (str or None): 输出文件路径,如果提供则保存最后一次迭代的校正结果
如果为None则不保存
conducts iterative correction using SUGAR
"""
glint_image = im_aligned.copy()
corrected_images = []
if iter is None:
# termination conditions
relative_difference = lambda sd0,sd1: sd1/sd0*100
marginal_difference = lambda sd1,sd2: (sd1-sd2)/sd1*100
relative_diff_thresh = marginal_difference_thresh = termination_thresh
sd_og = np.var(im_aligned)
iter_count = 0
sd_next = sd_og # 不需要copy直接使用值
max_iter = 100 # 添加最大迭代次数限制,防止无限循环
while ((relative_difference(sd_og,sd_next) > relative_diff_thresh) and iter_count < max_iter):
# do all the processing here
HM = SUGAR(glint_image,bounds,estimate_background=estimate_background, glint_mask_method=glint_mask_method, water_mask=water_mask)
corrected_bands = HM.get_corrected_bands()
glint_image = np.stack(corrected_bands,axis=2)
sd_temp = np.var(glint_image)
# 只在需要时保存中间结果,减少内存占用
if get_glint_mask or iter_count == 0:
corrected_images.append(glint_image.copy())
else:
corrected_images.append(glint_image) # 最后一次迭代的结果
# save glint_mask
# if iter_count == 0 and get_glint_mask is True:
# glint_mask = np.stack(HM.glint_mask,axis=2)
if (marginal_difference(sd_next,sd_temp)<marginal_difference_thresh):
break
else:
sd_next = sd_temp
#increase count
iter_count += 1
# 如果提供了输出路径,保存最后一次迭代的结果
if output_path is not None and len(corrected_images) > 0:
_save_corrected_image(corrected_images[-1], output_path)
else:
for i in range(iter):
HM = SUGAR(glint_image,bounds,estimate_background=estimate_background, glint_mask_method=glint_mask_method, water_mask=water_mask)
corrected_bands = HM.get_corrected_bands()
glint_image = np.stack(corrected_bands,axis=2)
# 只在最后一次迭代或需要时保存所有结果
if i == iter - 1 or get_glint_mask:
corrected_images.append(glint_image.copy())
else:
# 对于中间迭代,可以只保存引用(但要注意内存管理)
corrected_images.append(glint_image)
# save glint_mask
# if i == 0 and get_glint_mask is True:
# glint_mask = np.stack(HM.glint_mask,axis=2)
# 如果提供了输出路径,保存最后一次迭代的结果
if output_path is not None and len(corrected_images) > 0:
_save_corrected_image(corrected_images[-1], output_path)
return corrected_images
def _save_corrected_image(corrected_image, output_path):
"""
保存校正后的图像到文件用于correction_iterative函数BSQ格式ENVI格式
:param corrected_image: 校正后的图像数组,形状为(height, width, bands)
:param output_path: 输出文件路径
"""
if not GDAL_AVAILABLE:
raise ImportError("GDAL未安装无法保存影像文件")
if output_path is None:
return
# 确保输出目录存在
output_dir = os.path.dirname(output_path)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir, exist_ok=True)
# 如果没有地理信息,使用默认值
geotransform = (0, 1, 0, 0, 0, -1)
projection = ""
# 强制使用ENVI格式BSQ格式确保文件扩展名为.bsq
base_path, ext = os.path.splitext(output_path)
# 如果扩展名不是.bsq使用基础路径添加.bsq
if ext.lower() != '.bsq':
bsq_path = base_path + '.bsq'
else:
bsq_path = output_path
# 使用ENVI驱动默认就是BSQ格式
driver = gdal.GetDriverByName('ENVI')
if driver is None:
raise ValueError("无法创建ENVI格式文件ENVI驱动不可用")
height, width, n_bands = corrected_image.shape
# 创建ENVI格式数据集会自动生成.hdr文件
dataset = driver.Create(bsq_path, width, height, n_bands, gdal.GDT_Float32)
if dataset is None:
raise ValueError(f"无法创建输出文件: {bsq_path}")
try:
# 设置地理变换和投影
if geotransform:
dataset.SetGeoTransform(geotransform)
if projection:
dataset.SetProjection(projection)
# 写入每个波段BSQ格式按波段顺序存储
for i in range(n_bands):
band = dataset.GetRasterBand(i + 1)
band.WriteArray(corrected_image[:, :, i])
band.FlushCache()
finally:
dataset = None
# 检查.hdr文件是否已创建
hdr_path = bsq_path + '.hdr'
if os.path.exists(hdr_path):
print(f"校正后的图像已保存至: {bsq_path} (BSQ格式)")
print(f"头文件已保存至: {hdr_path}")
else:
print(f"校正后的图像已保存至: {bsq_path} (BSQ格式)")
print(f"警告: 未检测到.hdr文件但GDAL应该已自动创建")

View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,926 @@
from osgeo import gdal, osr
import numpy as np
import pandas as pd
import os
import spectral
from math import sin, cos, tan, sqrt, radians
try:
from scipy.ndimage import distance_transform_edt
from scipy.spatial import cKDTree
SCIPY_AVAILABLE = True
except ImportError:
SCIPY_AVAILABLE = False
# 启用GDAL异常处理
osr.UseExceptions()
# WGS84椭球参数
WGS84_A = 6378137.0 # 长半轴(米)
WGS84_F = 1 / 298.257223563 # 扁率
WGS84_E2 = WGS84_F * (2 - WGS84_F) # 第一偏心率平方
WGS84_EP2 = WGS84_E2 / (1 - WGS84_E2) # 第二偏心率平方
UTM_K0 = 0.9996 # UTM比例因子
def pixel_to_geo(pixel_x, pixel_y, geotransform):
"""
像素坐标转换为地图坐标
"""
geo_x = geotransform[0] + pixel_x * geotransform[1] + pixel_y * geotransform[2]
geo_y = geotransform[3] + pixel_x * geotransform[4] + pixel_y * geotransform[5]
return geo_x, geo_y
def prepare_boundary_adjuster(boundary_mask):
"""
为边界掩膜构建辅助结构,用于根据半径调整采样中心
"""
if not SCIPY_AVAILABLE:
print("警告: 未安装SciPy无法根据水体边界自动调整采样点位置。")
return None
if boundary_mask is None:
return None
boundary_bool = boundary_mask > 0
if not np.any(boundary_bool):
print("警告: 边界掩膜中未检测到有效水域,无法调整采样点。")
return None
distance_map = distance_transform_edt(boundary_bool.astype(np.uint8))
return {
'mask': boundary_bool,
'distance_map': distance_map,
'trees': {}
}
def _get_boundary_tree(adjuster, radius):
"""
根据半径获取或构建适用的KDTree
"""
radius_key = float(radius)
if radius_key in adjuster['trees']:
return adjuster['trees'][radius_key]
distance_map = adjuster['distance_map']
valid_positions = np.column_stack(np.where(distance_map >= radius_key))
if valid_positions.size == 0:
adjuster['trees'][radius_key] = None
return None
tree = cKDTree(valid_positions)
adjuster['trees'][radius_key] = (tree, valid_positions)
return adjuster['trees'][radius_key]
def adjust_sampling_center(pixel_x, pixel_y, radius, adjuster):
"""
如果采样半径范围超出水体边界,则将像素向内移动
直至采样区域完全位于水体内部(与边界相切)
"""
if adjuster is None or radius <= 0:
return pixel_x, pixel_y, False
distance_map = adjuster['distance_map']
mask = adjuster['mask']
if pixel_y < 0 or pixel_y >= distance_map.shape[0] or pixel_x < 0 or pixel_x >= distance_map.shape[1]:
return pixel_x, pixel_y, False
if not mask[pixel_y, pixel_x]:
# 当前像素不在水域内,需要移动到最近的合法位置
tree_info = _get_boundary_tree(adjuster, max(radius, 1))
if tree_info is None:
return pixel_x, pixel_y, False
else:
if distance_map[pixel_y, pixel_x] >= radius:
return pixel_x, pixel_y, False
tree_info = _get_boundary_tree(adjuster, radius)
if tree_info is None:
# 没有任何可以容纳该半径的像素,直接返回原位置
return pixel_x, pixel_y, False
tree, valid_positions = tree_info
if tree is None or valid_positions.size == 0:
return pixel_x, pixel_y, False
# 查询附近潜在位置
max_candidates = min(64, len(valid_positions))
distances, indices = tree.query([pixel_y, pixel_x], k=max_candidates)
if np.isscalar(indices):
indices = [int(indices)]
else:
indices = np.atleast_1d(indices).astype(int)
best_candidate = None
best_delta = None
for idx in indices:
cy, cx = valid_positions[idx]
if distance_map[cy, cx] < radius:
continue
delta = distance_map[cy, cx] - radius
center_shift = (cx - pixel_x) ** 2 + (cy - pixel_y) ** 2
score = (abs(delta), center_shift)
if best_candidate is None or score < best_delta:
best_candidate = (cx, cy)
best_delta = score
if best_candidate is None:
# 没有找到满足条件的候选点
return pixel_x, pixel_y, False
return int(best_candidate[0]), int(best_candidate[1]), True
def transform_coordinates(lon, lat, source_srs, target_srs):
"""
坐标系转换
Args:
lon: 经度
lat: 纬度
source_srs: 源坐标系
target_srs: 目标坐标系
Returns:
transformed_lon, transformed_lat: 转换后的坐标
"""
# 创建坐标转换对象
transform = osr.CoordinateTransformation(source_srs, target_srs)
# 执行坐标转换
point = transform.TransformPoint(lon, lat)
return point[0], point[1]
def geo_to_pixel(lon, lat, geotransform, dataset_srs=None):
"""
地理坐标转换为像素坐标
Args:
lon: 经度
lat: 纬度
geotransform: 仿射变换参数
dataset_srs: 数据集的空间参考系统(可选)
Returns:
pixel_x, pixel_y: 像素坐标
"""
# 使用仿射变换的逆变换将地理坐标转换为像素坐标
x_origin = geotransform[0]
y_origin = geotransform[3]
pixel_width = geotransform[1]
pixel_height = geotransform[5]
pixel_x = int((lon - x_origin) / pixel_width)
pixel_y = int((lat - y_origin) / pixel_height)
return pixel_x, pixel_y
def get_pixel_spectrum_batch(dataset, pixel_x_array, pixel_y_array):
"""
批量获取多个像素点的光谱数据(优化版本)
Args:
dataset: GDAL数据集
pixel_x_array: 像素X坐标数组
pixel_y_array: 像素Y坐标数组
Returns:
spectrum_array: 光谱数据数组 (n_points, n_bands)
"""
n_points = len(pixel_x_array)
n_bands = dataset.RasterCount
# 初始化输出数组
spectrum_array = np.zeros((n_points, n_bands), dtype=np.float32)
# 按波段批量读取(更高效)
for band_idx in range(n_bands):
band = dataset.GetRasterBand(band_idx + 1) # GDAL波段索引从1开始
band_data = band.ReadAsArray() # 读取整个波段
# 批量提取像素值
for i in range(n_points):
px, py = int(pixel_x_array[i]), int(pixel_y_array[i])
if 0 <= px < band_data.shape[1] and 0 <= py < band_data.shape[0]:
spectrum_array[i, band_idx] = band_data[py, px]
else:
spectrum_array[i, band_idx] = np.nan
return spectrum_array
def get_average_spectral_in_radius(dataset, center_x, center_y, radius, flare_mask=None, boundary_mask=None):
"""
获取指定半径内的平均光谱,避开耀斑和边界区域
Args:
dataset: GDAL数据集
center_x, center_y: 中心像素坐标
radius: 半径(像素)
flare_mask: 耀斑掩膜数组(可选)
boundary_mask: 边界掩膜数组(可选)
Returns:
平均光谱值数组
"""
num_bands = dataset.RasterCount
# 计算采样区域边界
x_start = max(0, center_x - radius)
x_end = min(dataset.RasterXSize, center_x + radius + 1)
y_start = max(0, center_y - radius)
y_end = min(dataset.RasterYSize, center_y + radius + 1)
# 读取区域数据
width = x_end - x_start
height = y_end - y_start
if width <= 0 or height <= 0:
return np.zeros(num_bands)
# 读取所有波段数据
spectral_data = dataset.ReadAsArray(x_start, y_start, width, height)
if spectral_data is None:
return np.zeros(num_bands)
# 确保数据是3维的 (bands, height, width)
if len(spectral_data.shape) == 2:
spectral_data = spectral_data.reshape(1, spectral_data.shape[0], spectral_data.shape[1])
# 创建圆形掩膜
y_indices, x_indices = np.ogrid[:height, :width]
center_x_local = center_x - x_start
center_y_local = center_y - y_start
# 计算距离掩膜
distance_mask = ((x_indices - center_x_local) ** 2 + (y_indices - center_y_local) ** 2) <= radius ** 2
# 应用耀斑掩膜(如果提供)
if flare_mask is not None:
flare_region = flare_mask[y_start:y_end, x_start:x_end]
if flare_region.shape == distance_mask.shape:
distance_mask = distance_mask & (flare_region == 0) # 假设0表示无耀斑
# 应用边界掩膜(如果提供)
if boundary_mask is not None:
boundary_region = boundary_mask[y_start:y_end, x_start:x_end]
if boundary_region.shape == distance_mask.shape:
distance_mask = distance_mask & (boundary_region == 1) # 假设0表示无边界
# 计算平均光谱
average_spectrum = np.zeros(num_bands)
valid_pixels = np.sum(distance_mask)
if valid_pixels > 0:
for band in range(num_bands):
band_data = spectral_data[band, :, :]
# 排除无效值
valid_data = band_data[distance_mask & (band_data != 0) & np.isfinite(band_data)]
if len(valid_data) > 0:
average_spectrum[band] = np.mean(valid_data)
return average_spectrum
def load_mask_file(mask_path):
"""
加载掩膜文件
Args:
mask_path: 掩膜文件路径(支持栅格文件如.dat/.tif等
Returns:
掩膜数组
"""
if mask_path is None or not os.path.exists(mask_path):
return None
try:
# 使用gdal.OpenEx打开文件明确指定为栅格文件
# 如果文件是矢量格式会返回None避免"多图层"错误
dataset = gdal.OpenEx(mask_path, gdal.OF_RASTER)
if dataset is None:
# 如果OpenEx失败尝试使用Open向后兼容
dataset = gdal.Open(mask_path, gdal.GA_ReadOnly)
if dataset is None:
print(f"警告: 无法打开掩膜文件 {mask_path},可能不是有效的栅格文件")
return None
# 检查是否为栅格数据集有RasterCount属性
if not hasattr(dataset, 'RasterCount') or dataset.RasterCount == 0:
print(f"警告: {mask_path} 不是有效的栅格文件")
del dataset
return None
mask_data = dataset.GetRasterBand(1).ReadAsArray()
del dataset
return mask_data
except Exception as e:
print(f"警告: 加载掩膜文件 {mask_path} 时出错: {str(e)}")
return None
def get_hdr_file_path(file_path):
"""
获取HDR文件路径
Args:
file_path: 影像文件路径
Returns:
HDR文件路径
"""
return os.path.splitext(file_path)[0] + ".hdr"
def calculate_utm_zone(longitude):
"""
根据经度计算UTM分区号
Args:
longitude: 经度
Returns:
utm_zone: UTM分区号1-60
"""
# UTM分区从180度开始每个分区6度
utm_zone = int((longitude + 180) / 6) + 1
# 确保分区号在有效范围内
utm_zone = max(1, min(60, utm_zone))
return utm_zone
def latlon_to_utm_math(lat_deg, lon_deg, zone=None):
"""
使用数学公式将WGS84经纬度转换为UTM坐标
Args:
lat_deg: 纬度(度)
lon_deg: 经度(度)
zone: UTM分区号如果为None则根据经度自动计算
Returns:
easting, northing: UTM坐标
"""
# 如果未指定分区,根据经度计算
if zone is None:
zone = calculate_utm_zone(lon_deg)
# 计算中央经线(度)
lon0 = (zone * 6 - 183)
lam0 = radians(lon0)
# 转换为弧度
phi = radians(lat_deg)
lam = radians(lon_deg)
# 计算中间变量
sinphi = sin(phi)
cosphi = cos(phi)
tanphi = tan(phi)
# 计算卯酉圈曲率半径
N = WGS84_A / sqrt(1 - WGS84_E2 * sinphi * sinphi)
T = tanphi * tanphi
C = WGS84_EP2 * cosphi * cosphi
A = cosphi * (lam - lam0)
# 计算子午圈弧长使用Snyder公式
M = (WGS84_A * ((1 - WGS84_E2/4 - 3*WGS84_E2**2/64 - 5*WGS84_E2**3/256) * phi
- (3*WGS84_E2/8 + 3*WGS84_E2**2/32 + 45*WGS84_E2**3/1024) * sin(2*phi)
+ (15*WGS84_E2**2/256 + 45*WGS84_E2**3/1024) * sin(4*phi)
- (35*WGS84_E2**3/3072) * sin(6*phi)))
# 计算东坐标Easting
E = (UTM_K0 * N * (A + (1 - T + C) * A**3 / 6
+ (5 - 18*T + T*T + 72*C - 58*WGS84_EP2) * A**5 / 120)
+ 500000.0)
# 计算北坐标Northing
# 对于南半球需要添加10000000米偏移
if lat_deg < 0:
Nn = (UTM_K0 * (M + N * tanphi * (A**2 / 2
+ (5 - T + 9*C + 4*C*C) * A**4 / 24
+ (61 - 58*T + T*T + 600*C - 330*WGS84_EP2) * A**6 / 720))
+ 10000000.0)
else:
Nn = (UTM_K0 * (M + N * tanphi * (A**2 / 2
+ (5 - T + 9*C + 4*C*C) * A**4 / 24
+ (61 - 58*T + T*T + 600*C - 330*WGS84_EP2) * A**6 / 720)))
return E, Nn
def convert_to_utm(lon, lat, source_epsg=4326, target_epsg=None):
"""
将坐标转换为UTM格式使用数学公式根据经度自动计算UTM分区
Args:
lon: 经度数组
lat: 纬度数组
source_epsg: 源坐标系EPSG代码默认为4326 (WGS84地理坐标系)
target_epsg: 目标坐标系EPSG代码如果为None则根据经度自动计算如果指定则从EPSG代码提取分区号
Returns:
utm_x, utm_y: 转换后的UTM坐标
"""
try:
# 检查源坐标系是否为WGS84
if source_epsg != 4326:
print(f"警告: 数学公式转换仅支持WGS84 (EPSG:4326)当前源坐标系为EPSG:{source_epsg}")
print("将尝试使用数学公式进行转换,但可能不准确")
# 批量转换坐标
utm_x = np.zeros_like(lon)
utm_y = np.zeros_like(lat)
# 如果指定了目标EPSG提取分区号
fixed_zone = None
if target_epsg is not None:
# 从EPSG代码提取分区号
# EPSG:32651 -> 51, EPSG:32751 -> 51
if 32601 <= target_epsg <= 32660:
fixed_zone = target_epsg - 32600
elif 32701 <= target_epsg <= 32760:
fixed_zone = target_epsg - 32700
else:
print(f"警告: 无法从EPSG代码 {target_epsg} 提取UTM分区号将根据经度自动计算")
# 向量化处理:标记无效坐标
invalid_mask = (np.isnan(lon) | np.isnan(lat) |
(lon < -180) | (lon > 180) |
(lat < -90) | (lat > 90))
# 统计无效坐标
invalid_count = np.sum(invalid_mask)
if invalid_count > 0:
invalid_indices = np.where(invalid_mask)[0]
print(f"警告: 发现 {invalid_count} 个无效坐标点,将跳过")
for idx in invalid_indices[:10]: # 只打印前10个
print(f" 坐标点 {idx + 1}: 经度={lon[idx]}, 纬度={lat[idx]}")
if invalid_count > 10:
print(f" ... 还有 {invalid_count - 10} 个无效坐标点")
# 对有效坐标进行转换
valid_mask = ~invalid_mask
if np.any(valid_mask):
valid_lon = lon[valid_mask]
valid_lat = lat[valid_mask]
valid_indices = np.where(valid_mask)[0]
# 计算UTM分区向量化
if fixed_zone is not None:
zones = np.full(len(valid_lon), fixed_zone)
else:
zones = np.array([calculate_utm_zone(lon_val) for lon_val in valid_lon])
# 批量转换(仍需要循环,但减少了开销)
for i, (lat_val, lon_val, zone) in enumerate(zip(valid_lat, valid_lon, zones)):
try:
E, Nn = latlon_to_utm_math(lat_val, lon_val, zone)
if not (np.isnan(E) or np.isnan(Nn) or np.isinf(E) or np.isinf(Nn)):
utm_x[valid_indices[i]] = E
utm_y[valid_indices[i]] = Nn
else:
utm_x[valid_indices[i]] = np.nan
utm_y[valid_indices[i]] = np.nan
except Exception as e:
utm_x[valid_indices[i]] = np.nan
utm_y[valid_indices[i]] = np.nan
# 设置无效坐标为NaN
utm_x[invalid_mask] = np.nan
utm_y[invalid_mask] = np.nan
return utm_x, utm_y
except Exception as e:
print(f"坐标转换初始化失败: {str(e)}")
return np.full_like(lon, np.nan), np.full_like(lat, np.nan)
def convert_to_utm51n(lon, lat, source_epsg=4326):
"""
将坐标转换为WGS84 UTM 51N格式保留向后兼容性
Args:
lon: 经度数组
lat: 纬度数组
source_epsg: 源坐标系EPSG代码默认为4326 (WGS84地理坐标系)
Returns:
utm_x, utm_y: 转换后的UTM坐标
"""
# 使用新的转换函数但强制使用UTM 51N
return convert_to_utm(lon, lat, source_epsg, target_epsg=32651)
def get_spectral_in_coor(imgpath, coorpath, outpath, radius=0, flare_path=None, boundary_path=None, source_epsg=4326):
"""
获取给定坐标的光谱曲线并将坐标转换为UTM格式根据经度自动计算UTM分区
Args:
imgpath: 影像文件路径BIL格式
coorpath: 坐标文件路径CSV格式第1、2列为纬度和经度
outpath: 输出文件路径CSV格式
radius: 采样半径(像素)
flare_path: 耀斑文件路径(可选)
boundary_path: 边界文件路径(可选)
source_epsg: 源坐标系EPSG代码默认为4326 (WGS84地理坐标系)
"""
# 读取原始坐标文件CSV格式
coor_df = None
coor_data = None
# 尝试不同的编码方式读取CSV文件
encodings = ['utf-8', 'gbk', 'gb2312', 'latin1', 'cp1252']
for encoding in encodings:
try:
# 尝试读取CSV文件
coor_df = pd.read_csv(coorpath, encoding=encoding)
# 只提取数值数据,跳过表头
coor_data = coor_df.select_dtypes(include=[np.number]).values
# 如果没有数值列,尝试转换所有列(跳过第一行表头)
if coor_data.shape[1] == 0:
# 尝试从第二行开始读取,第一行作为表头
coor_df = pd.read_csv(coorpath, encoding=encoding, header=0)
# 尝试将所有列转换为数值
numeric_df = coor_df.apply(pd.to_numeric, errors='coerce')
# 删除全为NaN的行通常是表头转换失败的行
numeric_df = numeric_df.dropna(how='all')
coor_data = numeric_df.values
print(f"成功使用 {encoding} 编码读取文件")
break
except Exception as e:
print(f"使用 {encoding} 编码读取失败: {str(e)}")
continue
# 如果所有编码都失败尝试numpy读取
if coor_data is None:
try:
print("尝试使用numpy读取数值数据...")
# 跳过第一行(表头),只读取数值
coor_data = np.loadtxt(coorpath, delimiter=",", skiprows=1)
except:
try:
coor_data = np.loadtxt(coorpath, delimiter="\t", skiprows=1)
except Exception as e:
raise Exception(f"无法读取坐标文件,请检查文件格式: {str(e)}")
if len(coor_data.shape) == 1:
coor_data = coor_data.reshape(1, -1)
# 检查数据有效性
if coor_data is None or coor_data.shape[1] < 2:
raise Exception("坐标文件格式错误需要至少2列数据第1列为纬度第2列为经度")
print(f"成功读取坐标文件,共 {coor_data.shape[0]} 行,{coor_data.shape[1]}")
print(f"数据预览前3行")
for i in range(min(3, coor_data.shape[0])):
print(f"{i + 1}: {coor_data[i, :min(5, coor_data.shape[1])]}") # 只显示前5列
# 提取原始坐标
lat_array = coor_data[:, 0] # 第1列是纬度
lon_array = coor_data[:, 1] # 第2列是经度
print(f"\n=== 原始坐标信息 ===")
print(f"原始坐标范围: 经度 {np.min(lon_array):.6f} ~ {np.max(lon_array):.6f}, 纬度 {np.min(lat_array):.6f} ~ {np.max(lat_array):.6f}")
# 坐标转换为UTM根据经度自动计算UTM分区
print("正在进行坐标转换...")
utm_x, utm_y = convert_to_utm(lon_array, lat_array, source_epsg, target_epsg=None)
# 检查转换结果
valid_utm_mask = ~(np.isnan(utm_x) | np.isnan(utm_y) | np.isinf(utm_x) | np.isinf(utm_y))
valid_count = np.sum(valid_utm_mask)
if valid_count > 0:
print(f"转换后UTM坐标范围: X {np.nanmin(utm_x):.2f} ~ {np.nanmax(utm_x):.2f}, Y {np.nanmin(utm_y):.2f} ~ {np.nanmax(utm_y):.2f}")
print(f"成功转换 {valid_count}/{len(utm_x)} 个坐标点")
else:
print("警告: 所有UTM坐标转换都失败了将尝试使用原始经纬度坐标进行像素坐标转换")
# 打开影像数据集
dataset = gdal.Open(imgpath)
im_width = dataset.RasterXSize # 栅格矩阵的列数
im_height = dataset.RasterYSize # 栅格矩阵的行数
num_bands = dataset.RasterCount # 栅格矩阵的波段数
geotransform = dataset.GetGeoTransform() # 仿射矩阵
im_proj = dataset.GetProjection() # 地图投影信息
print(f"影像尺寸: {im_width} x {im_height}, 波段数: {num_bands}")
print(f"仿射变换参数: {geotransform}")
print("\n=== 开始光谱提取 ===")
# 加载掩膜文件
flare_mask = load_mask_file(flare_path)
boundary_mask = load_mask_file(boundary_path)
boundary_adjuster = None
if boundary_mask is not None and radius > 0:
boundary_adjuster = prepare_boundary_adjuster(boundary_mask)
if boundary_adjuster is None:
print("提示: 无法构建边界调整器,采样点将不会根据水体边界进行内移。")
# 获取数据集的空间参考系统
dataset_srs = dataset.GetSpatialRef()
# 准备输出数组在原有数据基础上添加UTM坐标和光谱列
original_cols = coor_data.shape[1]
# 添加UTM坐标列2列和光谱列num_bands列
new_columns = np.zeros((coor_data.shape[0], 2 + num_bands))
coor_spectral = np.hstack((coor_data, new_columns))
# 将UTM坐标添加到数据中会在采样点调整后再更新为最终位置
coor_spectral[:, original_cols] = utm_x # 初始UTM X坐标
coor_spectral[:, original_cols + 1] = utm_y # 初始UTM Y坐标
print(f"处理 {coor_data.shape[0]} 个坐标点...")
# 如果UTM转换失败尝试使用影像坐标系进行转换
use_utm_fallback = False
if valid_count == 0 and dataset_srs is not None:
print("尝试使用影像坐标系进行坐标转换...")
try:
source_srs = osr.SpatialReference()
source_srs.ImportFromEPSG(source_epsg)
transform_to_image = osr.CoordinateTransformation(source_srs, dataset_srs)
use_utm_fallback = True
except:
use_utm_fallback = False
# 批量转换所有坐标点为像素坐标
pixel_x_array = np.zeros(coor_data.shape[0], dtype=np.int32)
pixel_y_array = np.zeros(coor_data.shape[0], dtype=np.int32)
valid_pixel_mask = np.zeros(coor_data.shape[0], dtype=bool)
# 批量计算像素坐标
for i in range(coor_data.shape[0]):
# 优先使用UTM坐标如果无效则使用备用方案
utm_x_point = utm_x[i]
utm_y_point = utm_y[i]
# 检查UTM坐标是否有效
if np.isnan(utm_x_point) or np.isnan(utm_y_point) or np.isinf(utm_x_point) or np.isinf(utm_y_point):
# 如果UTM转换失败尝试使用影像坐标系
if use_utm_fallback:
try:
lon_point = lon_array[i]
lat_point = lat_array[i]
if not (np.isnan(lon_point) or np.isnan(lat_point)):
# 转换为影像坐标系
img_coords = transform_to_image.TransformPoint(lon_point, lat_point)
pixel_x, pixel_y = geo_to_pixel(img_coords[0], img_coords[1], geotransform, dataset_srs)
# 更新UTM坐标列使用影像坐标系坐标
coor_spectral[i, original_cols] = img_coords[0]
coor_spectral[i, original_cols + 1] = img_coords[1]
else:
print(f"跳过坐标点 {i + 1}: 坐标无效")
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
continue
except Exception as e:
# 如果影像坐标系转换也失败,尝试直接使用经纬度
try:
lon_point = lon_array[i]
lat_point = lat_array[i]
if not (np.isnan(lon_point) or np.isnan(lat_point)):
pixel_x, pixel_y = geo_to_pixel(lon_point, lat_point, geotransform, dataset_srs)
# 保留原始经纬度作为坐标
coor_spectral[i, original_cols] = lon_point
coor_spectral[i, original_cols + 1] = lat_point
else:
print(f"跳过坐标点 {i + 1}: 坐标无效")
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
continue
except:
print(f"跳过坐标点 {i + 1}: 所有坐标转换方式都失败")
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
continue
else:
# 尝试直接使用经纬度坐标
try:
lon_point = lon_array[i]
lat_point = lat_array[i]
if not (np.isnan(lon_point) or np.isnan(lat_point)):
pixel_x, pixel_y = geo_to_pixel(lon_point, lat_point, geotransform, dataset_srs)
# 保留原始经纬度作为坐标
coor_spectral[i, original_cols] = lon_point
coor_spectral[i, original_cols + 1] = lat_point
else:
print(f"跳过坐标点 {i + 1}: 坐标无效")
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
continue
except:
print(f"跳过坐标点 {i + 1}: 坐标转换失败")
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
continue
else:
# UTM坐标转换为像素坐标
pixel_x, pixel_y = geo_to_pixel(utm_x_point, utm_y_point, geotransform, dataset_srs)
# 存储像素坐标
pixel_x_array[i] = pixel_x
pixel_y_array[i] = pixel_y
# 根据水体边界调整采样中心
moved = False
original_pixel_x, original_pixel_y = pixel_x, pixel_y
if boundary_adjuster is not None and radius > 0:
new_pixel_x, new_pixel_y, moved = adjust_sampling_center(pixel_x, pixel_y, radius, boundary_adjuster)
if moved:
pixel_x, pixel_y = new_pixel_x, new_pixel_y
if i < 10 or (i % 100 == 0):
print(f" 采样点 {i + 1} 调整至水体内部: ({original_pixel_x}, {original_pixel_y}) -> ({pixel_x}, {pixel_y})")
pixel_x_array[i] = pixel_x
pixel_y_array[i] = pixel_y
# 检查坐标是否在影像范围内(使用调整后的坐标)
if 0 <= pixel_x < im_width and 0 <= pixel_y < im_height:
valid_pixel_mask[i] = True
# 更新UTM列为最终采样点的实际地图坐标
geo_x, geo_y = pixel_to_geo(pixel_x, pixel_y, geotransform)
coor_spectral[i, original_cols] = geo_x
coor_spectral[i, original_cols + 1] = geo_y
else:
valid_pixel_mask[i] = False
if i < 10 or (i % 100 == 0): # 只打印前10个或每100个打印一次
print(f"警告: 坐标点 {i + 1} (UTM X:{utm_x_point:.2f}, Y:{utm_y_point:.2f}) 超出影像范围")
# 批量提取光谱数据优化减少I/O操作
print(f"批量提取光谱数据... (有效坐标点: {np.sum(valid_pixel_mask)})")
if radius > 0:
# 半径采样模式:需要逐个处理
for i in range(coor_data.shape[0]):
if valid_pixel_mask[i]:
spectrum = get_average_spectral_in_radius(
dataset, pixel_x_array[i], pixel_y_array[i], radius, flare_mask, boundary_mask
)
coor_spectral[i, original_cols + 2:] = spectrum
else:
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
else:
# 单点采样模式:批量读取(优化)
# 预读取所有波段数据(如果内存允许)
try:
# 尝试读取所有波段到内存(适用于内存充足的情况)
print("正在预加载所有波段数据到内存(优化模式)...")
all_bands_data = []
for band_idx in range(num_bands):
band = dataset.GetRasterBand(band_idx + 1)
band_data = band.ReadAsArray()
all_bands_data.append(band_data)
all_bands_data = np.array(all_bands_data) # shape: (bands, height, width)
print("预加载完成,开始批量提取像素值...")
# 批量提取像素值
for i in range(coor_data.shape[0]):
if valid_pixel_mask[i]:
px, py = int(pixel_x_array[i]), int(pixel_y_array[i])
# GDAL读取的数组形状是 (bands, height, width),像素坐标 (x,y) 对应数组索引 [:, y, x]
# 注意py是行y坐标px是列x坐标
if 0 <= px < all_bands_data.shape[2] and 0 <= py < all_bands_data.shape[1]:
spectrum = all_bands_data[:, py, px] # 直接索引,非常快
coor_spectral[i, original_cols + 2:] = spectrum
else:
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
else:
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
# 释放内存
del all_bands_data
print("批量提取完成")
except MemoryError:
# 如果内存不足,回退到逐个波段读取
print("内存不足,使用逐个波段读取模式...")
for i in range(coor_data.shape[0]):
if valid_pixel_mask[i]:
px, py = pixel_x_array[i], pixel_y_array[i]
spectrum = np.zeros(num_bands)
for band_idx in range(num_bands):
band = dataset.GetRasterBand(band_idx + 1)
spectrum[band_idx] = band.ReadAsArray(px, py, 1, 1)[0, 0]
coor_spectral[i, original_cols + 2:] = spectrum
else:
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
del dataset
# 创建DataFrame用于CSV输出
# 去除前两列坐标列纬度和经度和UTM列
try:
# 如果原始数据有列名,使用原始列名(跳过前两列)
if coor_df is not None and hasattr(coor_df, 'columns'):
# 跳过前两列经纬度从第3列开始
if len(coor_df.columns) >= original_cols:
# 保留第3列及之后的原始列如果有的话
if original_cols > 2:
original_columns = list(coor_df.columns[2:original_cols])
else:
original_columns = []
else:
# 如果原始列数不足,只保留存在的列(跳过前两列)
if len(coor_df.columns) > 2:
original_columns = list(coor_df.columns[2:])
else:
original_columns = []
else:
# 如果没有列名只保留第3列及之后的列如果有的话
if original_cols > 2:
original_columns = ["col_" + str(j + 1) for j in range(2, original_cols)]
else:
original_columns = []
except:
# 异常处理只保留第3列及之后的列如果有的话
if original_cols > 2:
original_columns = ["col_" + str(j + 1) for j in range(2, original_cols)]
else:
original_columns = []
# 读取波长信息,用作光谱列名
wavelengths = None
try:
in_hdr_dict = spectral.envi.read_envi_header(get_hdr_file_path(imgpath))
wavelengths = np.array(in_hdr_dict['wavelength']).astype('float64')
# 将波长值转换为字符串作为列名
spectral_columns = [str(wl) for wl in wavelengths]
print(f"成功读取波长信息,共 {len(spectral_columns)} 个波段")
except Exception as e:
print(f"警告: 无法读取波长信息 ({str(e)}),使用默认列名 band_1, band_2, ...")
spectral_columns = ["band_" + str(j + 1) for j in range(num_bands)]
# 构建输出列名不包含前两列坐标列和UTM列
all_columns = original_columns + spectral_columns
# 从coor_spectral中提取需要输出的列
# 跳过前两列经纬度和UTM列只保留
# - 第3列到第original_cols列如果有的话
# - 光谱数据列从original_cols+2开始
output_data = []
if original_cols > 2:
# 保留第3列到第original_cols列
output_data.append(coor_spectral[:, 2:original_cols])
# 保留光谱数据列从original_cols+2开始
output_data.append(coor_spectral[:, original_cols + 2:])
# 合并数据
if len(output_data) > 0:
output_array = np.hstack(output_data) if len(output_data) > 1 else output_data[0]
else:
# 如果没有原始列,只输出光谱数据
output_array = coor_spectral[:, original_cols + 2:]
# 创建结果DataFrame
result_df = pd.DataFrame(output_array, columns=all_columns)
# 保存为CSV格式
result_df.to_csv(outpath, index=False, float_format='%.6f')
print(f"结果已保存到CSV文件: {outpath}")
return coor_spectral
# 直接运行示例
if __name__ == '__main__':
# 在这里直接设置参数
imgpath = r"D:\BaiduNetdiskDownload\yaobao\result3.bsq"# BIL格式影像文件路径
coorpath = r"E:\code\WQ\封装\work_dir\4_processed_data\processed_data.csv"# CSV格式坐标文件路径第1、2列为纬度和经度
output_path = r"E:\code\WQ\封装\test/yangdian_output.csv" # CSV格式输出文件路径
radius = 5 # 采样半径像素0表示单点采样>0表示半径内平均
flare_path = r"E:\code\WQ\封装\work_dir\2_glint\severe_glint_area.dat" # 耀斑掩膜文件路径可选None表示不使用
boundary_path ="D:\BaiduNetdiskDownload\yaobao\water_mask.dat" # 边界掩膜文件路径可选None表示不使用
source_epsg = 4326 # 源坐标系EPSG代码默认为4326 (WGS84地理坐标系)
verbose = True # 是否启用详细模式
if verbose:
print(f"影像文件: {imgpath}")
print(f"坐标文件: {coorpath}")
print(f"输出文件: {output_path}")
print(f"采样半径: {radius}")
if flare_path:
print(f"耀斑掩膜: {flare_path}")
if boundary_path:
print(f"边界掩膜: {boundary_path}")
if source_epsg:
print(f"指定坐标系: EPSG:{source_epsg}")
tmp = get_spectral_in_coor(imgpath, coorpath, output_path,
radius, flare_path, boundary_path, source_epsg)

View File

@ -0,0 +1,785 @@
from osgeo import gdal, osr
import numpy as np
import pandas as pd
import os
import spectral
from math import sin, cos, tan, sqrt, radians
# 启用GDAL异常处理
osr.UseExceptions()
# WGS84椭球参数
WGS84_A = 6378137.0 # 长半轴(米)
WGS84_F = 1 / 298.257223563 # 扁率
WGS84_E2 = WGS84_F * (2 - WGS84_F) # 第一偏心率平方
WGS84_EP2 = WGS84_E2 / (1 - WGS84_E2) # 第二偏心率平方
UTM_K0 = 0.9996 # UTM比例因子
def transform_coordinates(lon, lat, source_srs, target_srs):
"""
坐标系转换
Args:
lon: 经度
lat: 纬度
source_srs: 源坐标系
target_srs: 目标坐标系
Returns:
transformed_lon, transformed_lat: 转换后的坐标
"""
# 创建坐标转换对象
transform = osr.CoordinateTransformation(source_srs, target_srs)
# 执行坐标转换
point = transform.TransformPoint(lon, lat)
return point[0], point[1]
def geo_to_pixel(lon, lat, geotransform, dataset_srs=None):
"""
地理坐标转换为像素坐标
Args:
lon: 经度
lat: 纬度
geotransform: 仿射变换参数
dataset_srs: 数据集的空间参考系统(可选)
Returns:
pixel_x, pixel_y: 像素坐标
"""
# 使用仿射变换的逆变换将地理坐标转换为像素坐标
x_origin = geotransform[0]
y_origin = geotransform[3]
pixel_width = geotransform[1]
pixel_height = geotransform[5]
pixel_x = int((lon - x_origin) / pixel_width)
pixel_y = int((lat - y_origin) / pixel_height)
return pixel_x, pixel_y
def get_pixel_spectrum_batch(dataset, pixel_x_array, pixel_y_array):
"""
批量获取多个像素点的光谱数据(优化版本)
Args:
dataset: GDAL数据集
pixel_x_array: 像素X坐标数组
pixel_y_array: 像素Y坐标数组
Returns:
spectrum_array: 光谱数据数组 (n_points, n_bands)
"""
n_points = len(pixel_x_array)
n_bands = dataset.RasterCount
# 初始化输出数组
spectrum_array = np.zeros((n_points, n_bands), dtype=np.float32)
# 按波段批量读取(更高效)
for band_idx in range(n_bands):
band = dataset.GetRasterBand(band_idx + 1) # GDAL波段索引从1开始
band_data = band.ReadAsArray() # 读取整个波段
# 批量提取像素值
for i in range(n_points):
px, py = int(pixel_x_array[i]), int(pixel_y_array[i])
if 0 <= px < band_data.shape[1] and 0 <= py < band_data.shape[0]:
spectrum_array[i, band_idx] = band_data[py, px]
else:
spectrum_array[i, band_idx] = np.nan
return spectrum_array
def get_average_spectral_in_radius(dataset, center_x, center_y, radius, flare_mask=None, boundary_mask=None):
"""
获取指定半径内的平均光谱,避开耀斑和边界区域
Args:
dataset: GDAL数据集
center_x, center_y: 中心像素坐标
radius: 半径(像素)
flare_mask: 耀斑掩膜数组(可选)
boundary_mask: 边界掩膜数组(可选)
Returns:
平均光谱值数组
"""
num_bands = dataset.RasterCount
# 计算采样区域边界
x_start = max(0, center_x - radius)
x_end = min(dataset.RasterXSize, center_x + radius + 1)
y_start = max(0, center_y - radius)
y_end = min(dataset.RasterYSize, center_y + radius + 1)
# 读取区域数据
width = x_end - x_start
height = y_end - y_start
if width <= 0 or height <= 0:
return np.zeros(num_bands)
# 读取所有波段数据
spectral_data = dataset.ReadAsArray(x_start, y_start, width, height)
if spectral_data is None:
return np.zeros(num_bands)
# 确保数据是3维的 (bands, height, width)
if len(spectral_data.shape) == 2:
spectral_data = spectral_data.reshape(1, spectral_data.shape[0], spectral_data.shape[1])
# 创建圆形掩膜
y_indices, x_indices = np.ogrid[:height, :width]
center_x_local = center_x - x_start
center_y_local = center_y - y_start
# 计算距离掩膜
distance_mask = ((x_indices - center_x_local) ** 2 + (y_indices - center_y_local) ** 2) <= radius ** 2
# 应用耀斑掩膜(如果提供)
if flare_mask is not None:
flare_region = flare_mask[y_start:y_end, x_start:x_end]
if flare_region.shape == distance_mask.shape:
distance_mask = distance_mask & (flare_region == 0) # 假设0表示无耀斑
# 应用边界掩膜(如果提供)
if boundary_mask is not None:
boundary_region = boundary_mask[y_start:y_end, x_start:x_end]
if boundary_region.shape == distance_mask.shape:
distance_mask = distance_mask & (boundary_region == 1) # 假设0表示无边界
# 计算平均光谱
average_spectrum = np.zeros(num_bands)
valid_pixels = np.sum(distance_mask)
if valid_pixels > 0:
for band in range(num_bands):
band_data = spectral_data[band, :, :]
# 排除无效值
valid_data = band_data[distance_mask & (band_data != 0) & np.isfinite(band_data)]
if len(valid_data) > 0:
average_spectrum[band] = np.mean(valid_data)
return average_spectrum
def load_mask_file(mask_path):
"""
加载掩膜文件
Args:
mask_path: 掩膜文件路径(支持栅格文件如.dat/.tif等
Returns:
掩膜数组
"""
if mask_path is None or not os.path.exists(mask_path):
return None
try:
# 使用gdal.OpenEx打开文件明确指定为栅格文件
# 如果文件是矢量格式会返回None避免"多图层"错误
dataset = gdal.OpenEx(mask_path, gdal.OF_RASTER)
if dataset is None:
# 如果OpenEx失败尝试使用Open向后兼容
dataset = gdal.Open(mask_path, gdal.GA_ReadOnly)
if dataset is None:
print(f"警告: 无法打开掩膜文件 {mask_path},可能不是有效的栅格文件")
return None
# 检查是否为栅格数据集有RasterCount属性
if not hasattr(dataset, 'RasterCount') or dataset.RasterCount == 0:
print(f"警告: {mask_path} 不是有效的栅格文件")
del dataset
return None
mask_data = dataset.GetRasterBand(1).ReadAsArray()
del dataset
return mask_data
except Exception as e:
print(f"警告: 加载掩膜文件 {mask_path} 时出错: {str(e)}")
return None
def get_hdr_file_path(file_path):
"""
获取HDR文件路径
Args:
file_path: 影像文件路径
Returns:
HDR文件路径
"""
return os.path.splitext(file_path)[0] + ".hdr"
def calculate_utm_zone(longitude):
"""
根据经度计算UTM分区号
Args:
longitude: 经度
Returns:
utm_zone: UTM分区号1-60
"""
# UTM分区从180度开始每个分区6度
utm_zone = int((longitude + 180) / 6) + 1
# 确保分区号在有效范围内
utm_zone = max(1, min(60, utm_zone))
return utm_zone
def latlon_to_utm_math(lat_deg, lon_deg, zone=None):
"""
使用数学公式将WGS84经纬度转换为UTM坐标
Args:
lat_deg: 纬度(度)
lon_deg: 经度(度)
zone: UTM分区号如果为None则根据经度自动计算
Returns:
easting, northing: UTM坐标
"""
# 如果未指定分区,根据经度计算
if zone is None:
zone = calculate_utm_zone(lon_deg)
# 计算中央经线(度)
lon0 = (zone * 6 - 183)
lam0 = radians(lon0)
# 转换为弧度
phi = radians(lat_deg)
lam = radians(lon_deg)
# 计算中间变量
sinphi = sin(phi)
cosphi = cos(phi)
tanphi = tan(phi)
# 计算卯酉圈曲率半径
N = WGS84_A / sqrt(1 - WGS84_E2 * sinphi * sinphi)
T = tanphi * tanphi
C = WGS84_EP2 * cosphi * cosphi
A = cosphi * (lam - lam0)
# 计算子午圈弧长使用Snyder公式
M = (WGS84_A * ((1 - WGS84_E2/4 - 3*WGS84_E2**2/64 - 5*WGS84_E2**3/256) * phi
- (3*WGS84_E2/8 + 3*WGS84_E2**2/32 + 45*WGS84_E2**3/1024) * sin(2*phi)
+ (15*WGS84_E2**2/256 + 45*WGS84_E2**3/1024) * sin(4*phi)
- (35*WGS84_E2**3/3072) * sin(6*phi)))
# 计算东坐标Easting
E = (UTM_K0 * N * (A + (1 - T + C) * A**3 / 6
+ (5 - 18*T + T*T + 72*C - 58*WGS84_EP2) * A**5 / 120)
+ 500000.0)
# 计算北坐标Northing
# 对于南半球需要添加10000000米偏移
if lat_deg < 0:
Nn = (UTM_K0 * (M + N * tanphi * (A**2 / 2
+ (5 - T + 9*C + 4*C*C) * A**4 / 24
+ (61 - 58*T + T*T + 600*C - 330*WGS84_EP2) * A**6 / 720))
+ 10000000.0)
else:
Nn = (UTM_K0 * (M + N * tanphi * (A**2 / 2
+ (5 - T + 9*C + 4*C*C) * A**4 / 24
+ (61 - 58*T + T*T + 600*C - 330*WGS84_EP2) * A**6 / 720)))
return E, Nn
def convert_to_utm(lon, lat, source_epsg=4326, target_epsg=None):
"""
将坐标转换为UTM格式使用数学公式根据经度自动计算UTM分区
Args:
lon: 经度数组
lat: 纬度数组
source_epsg: 源坐标系EPSG代码默认为4326 (WGS84地理坐标系)
target_epsg: 目标坐标系EPSG代码如果为None则根据经度自动计算如果指定则从EPSG代码提取分区号
Returns:
utm_x, utm_y: 转换后的UTM坐标
"""
try:
# 检查源坐标系是否为WGS84
if source_epsg != 4326:
print(f"警告: 数学公式转换仅支持WGS84 (EPSG:4326)当前源坐标系为EPSG:{source_epsg}")
print("将尝试使用数学公式进行转换,但可能不准确")
# 批量转换坐标
utm_x = np.zeros_like(lon)
utm_y = np.zeros_like(lat)
# 如果指定了目标EPSG提取分区号
fixed_zone = None
if target_epsg is not None:
# 从EPSG代码提取分区号
# EPSG:32651 -> 51, EPSG:32751 -> 51
if 32601 <= target_epsg <= 32660:
fixed_zone = target_epsg - 32600
elif 32701 <= target_epsg <= 32760:
fixed_zone = target_epsg - 32700
else:
print(f"警告: 无法从EPSG代码 {target_epsg} 提取UTM分区号将根据经度自动计算")
# 向量化处理:标记无效坐标
invalid_mask = (np.isnan(lon) | np.isnan(lat) |
(lon < -180) | (lon > 180) |
(lat < -90) | (lat > 90))
# 统计无效坐标
invalid_count = np.sum(invalid_mask)
if invalid_count > 0:
invalid_indices = np.where(invalid_mask)[0]
print(f"警告: 发现 {invalid_count} 个无效坐标点,将跳过")
for idx in invalid_indices[:10]: # 只打印前10个
print(f" 坐标点 {idx + 1}: 经度={lon[idx]}, 纬度={lat[idx]}")
if invalid_count > 10:
print(f" ... 还有 {invalid_count - 10} 个无效坐标点")
# 对有效坐标进行转换
valid_mask = ~invalid_mask
if np.any(valid_mask):
valid_lon = lon[valid_mask]
valid_lat = lat[valid_mask]
valid_indices = np.where(valid_mask)[0]
# 计算UTM分区向量化
if fixed_zone is not None:
zones = np.full(len(valid_lon), fixed_zone)
else:
zones = np.array([calculate_utm_zone(lon_val) for lon_val in valid_lon])
# 批量转换(仍需要循环,但减少了开销)
for i, (lat_val, lon_val, zone) in enumerate(zip(valid_lat, valid_lon, zones)):
try:
E, Nn = latlon_to_utm_math(lat_val, lon_val, zone)
if not (np.isnan(E) or np.isnan(Nn) or np.isinf(E) or np.isinf(Nn)):
utm_x[valid_indices[i]] = E
utm_y[valid_indices[i]] = Nn
else:
utm_x[valid_indices[i]] = np.nan
utm_y[valid_indices[i]] = np.nan
except Exception as e:
utm_x[valid_indices[i]] = np.nan
utm_y[valid_indices[i]] = np.nan
# 设置无效坐标为NaN
utm_x[invalid_mask] = np.nan
utm_y[invalid_mask] = np.nan
return utm_x, utm_y
except Exception as e:
print(f"坐标转换初始化失败: {str(e)}")
return np.full_like(lon, np.nan), np.full_like(lat, np.nan)
def convert_to_utm51n(lon, lat, source_epsg=4326):
"""
将坐标转换为WGS84 UTM 51N格式保留向后兼容性
Args:
lon: 经度数组
lat: 纬度数组
source_epsg: 源坐标系EPSG代码默认为4326 (WGS84地理坐标系)
Returns:
utm_x, utm_y: 转换后的UTM坐标
"""
# 使用新的转换函数但强制使用UTM 51N
return convert_to_utm(lon, lat, source_epsg, target_epsg=32651)
def get_spectral_in_coor(imgpath, coorpath, outpath, radius=0, flare_path=None, boundary_path=None, source_epsg=4326):
"""
获取给定坐标的光谱曲线并将坐标转换为UTM格式根据经度自动计算UTM分区
Args:
imgpath: 影像文件路径BIL格式
coorpath: 坐标文件路径CSV格式第1、2列为纬度和经度
outpath: 输出文件路径CSV格式
radius: 采样半径(像素)
flare_path: 耀斑文件路径(可选)
boundary_path: 边界文件路径(可选)
source_epsg: 源坐标系EPSG代码默认为4326 (WGS84地理坐标系)
"""
# 读取原始坐标文件CSV格式
coor_df = None
coor_data = None
# 尝试不同的编码方式读取CSV文件
encodings = ['utf-8', 'gbk', 'gb2312', 'latin1', 'cp1252']
for encoding in encodings:
try:
# 尝试读取CSV文件
coor_df = pd.read_csv(coorpath, encoding=encoding)
# 只提取数值数据,跳过表头
coor_data = coor_df.select_dtypes(include=[np.number]).values
# 如果没有数值列,尝试转换所有列(跳过第一行表头)
if coor_data.shape[1] == 0:
# 尝试从第二行开始读取,第一行作为表头
coor_df = pd.read_csv(coorpath, encoding=encoding, header=0)
# 尝试将所有列转换为数值
numeric_df = coor_df.apply(pd.to_numeric, errors='coerce')
# 删除全为NaN的行通常是表头转换失败的行
numeric_df = numeric_df.dropna(how='all')
coor_data = numeric_df.values
print(f"成功使用 {encoding} 编码读取文件")
break
except Exception as e:
print(f"使用 {encoding} 编码读取失败: {str(e)}")
continue
# 如果所有编码都失败尝试numpy读取
if coor_data is None:
try:
print("尝试使用numpy读取数值数据...")
# 跳过第一行(表头),只读取数值
coor_data = np.loadtxt(coorpath, delimiter=",", skiprows=1)
except:
try:
coor_data = np.loadtxt(coorpath, delimiter="\t", skiprows=1)
except Exception as e:
raise Exception(f"无法读取坐标文件,请检查文件格式: {str(e)}")
if len(coor_data.shape) == 1:
coor_data = coor_data.reshape(1, -1)
# 检查数据有效性
if coor_data is None or coor_data.shape[1] < 2:
raise Exception("坐标文件格式错误需要至少2列数据第1列为纬度第2列为经度")
print(f"成功读取坐标文件,共 {coor_data.shape[0]} 行,{coor_data.shape[1]}")
print(f"数据预览前3行")
for i in range(min(3, coor_data.shape[0])):
print(f"{i + 1}: {coor_data[i, :min(5, coor_data.shape[1])]}") # 只显示前5列
# 提取原始坐标
lat_array = coor_data[:, 0] # 第1列是纬度
lon_array = coor_data[:, 1] # 第2列是经度
print(f"\n=== 原始坐标信息 ===")
print(f"原始坐标范围: 经度 {np.min(lon_array):.6f} ~ {np.max(lon_array):.6f}, 纬度 {np.min(lat_array):.6f} ~ {np.max(lat_array):.6f}")
# 坐标转换为UTM根据经度自动计算UTM分区
print("正在进行坐标转换...")
utm_x, utm_y = convert_to_utm(lon_array, lat_array, source_epsg, target_epsg=None)
# 检查转换结果
valid_utm_mask = ~(np.isnan(utm_x) | np.isnan(utm_y) | np.isinf(utm_x) | np.isinf(utm_y))
valid_count = np.sum(valid_utm_mask)
if valid_count > 0:
print(f"转换后UTM坐标范围: X {np.nanmin(utm_x):.2f} ~ {np.nanmax(utm_x):.2f}, Y {np.nanmin(utm_y):.2f} ~ {np.nanmax(utm_y):.2f}")
print(f"成功转换 {valid_count}/{len(utm_x)} 个坐标点")
else:
print("警告: 所有UTM坐标转换都失败了将尝试使用原始经纬度坐标进行像素坐标转换")
# 打开影像数据集
dataset = gdal.Open(imgpath)
im_width = dataset.RasterXSize # 栅格矩阵的列数
im_height = dataset.RasterYSize # 栅格矩阵的行数
num_bands = dataset.RasterCount # 栅格矩阵的波段数
geotransform = dataset.GetGeoTransform() # 仿射矩阵
im_proj = dataset.GetProjection() # 地图投影信息
print(f"影像尺寸: {im_width} x {im_height}, 波段数: {num_bands}")
print(f"仿射变换参数: {geotransform}")
print("\n=== 开始光谱提取 ===")
# 加载掩膜文件
flare_mask = load_mask_file(flare_path)
boundary_mask = load_mask_file(boundary_path)
# 获取数据集的空间参考系统
dataset_srs = dataset.GetSpatialRef()
# 准备输出数组在原有数据基础上添加UTM坐标和光谱列
original_cols = coor_data.shape[1]
# 添加UTM坐标列2列和光谱列num_bands列
new_columns = np.zeros((coor_data.shape[0], 2 + num_bands))
coor_spectral = np.hstack((coor_data, new_columns))
# 将UTM坐标添加到数据中
coor_spectral[:, original_cols] = utm_x # UTM X坐标
coor_spectral[:, original_cols + 1] = utm_y # UTM Y坐标
print(f"处理 {coor_data.shape[0]} 个坐标点...")
# 如果UTM转换失败尝试使用影像坐标系进行转换
use_utm_fallback = False
if valid_count == 0 and dataset_srs is not None:
print("尝试使用影像坐标系进行坐标转换...")
try:
source_srs = osr.SpatialReference()
source_srs.ImportFromEPSG(source_epsg)
transform_to_image = osr.CoordinateTransformation(source_srs, dataset_srs)
use_utm_fallback = True
except:
use_utm_fallback = False
# 批量转换所有坐标点为像素坐标
pixel_x_array = np.zeros(coor_data.shape[0], dtype=np.int32)
pixel_y_array = np.zeros(coor_data.shape[0], dtype=np.int32)
valid_pixel_mask = np.zeros(coor_data.shape[0], dtype=bool)
# 批量计算像素坐标
for i in range(coor_data.shape[0]):
# 优先使用UTM坐标如果无效则使用备用方案
utm_x_point = utm_x[i]
utm_y_point = utm_y[i]
# 检查UTM坐标是否有效
if np.isnan(utm_x_point) or np.isnan(utm_y_point) or np.isinf(utm_x_point) or np.isinf(utm_y_point):
# 如果UTM转换失败尝试使用影像坐标系
if use_utm_fallback:
try:
lon_point = lon_array[i]
lat_point = lat_array[i]
if not (np.isnan(lon_point) or np.isnan(lat_point)):
# 转换为影像坐标系
img_coords = transform_to_image.TransformPoint(lon_point, lat_point)
pixel_x, pixel_y = geo_to_pixel(img_coords[0], img_coords[1], geotransform, dataset_srs)
# 更新UTM坐标列使用影像坐标系坐标
coor_spectral[i, original_cols] = img_coords[0]
coor_spectral[i, original_cols + 1] = img_coords[1]
else:
print(f"跳过坐标点 {i + 1}: 坐标无效")
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
continue
except Exception as e:
# 如果影像坐标系转换也失败,尝试直接使用经纬度
try:
lon_point = lon_array[i]
lat_point = lat_array[i]
if not (np.isnan(lon_point) or np.isnan(lat_point)):
pixel_x, pixel_y = geo_to_pixel(lon_point, lat_point, geotransform, dataset_srs)
# 保留原始经纬度作为坐标
coor_spectral[i, original_cols] = lon_point
coor_spectral[i, original_cols + 1] = lat_point
else:
print(f"跳过坐标点 {i + 1}: 坐标无效")
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
continue
except:
print(f"跳过坐标点 {i + 1}: 所有坐标转换方式都失败")
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
continue
else:
# 尝试直接使用经纬度坐标
try:
lon_point = lon_array[i]
lat_point = lat_array[i]
if not (np.isnan(lon_point) or np.isnan(lat_point)):
pixel_x, pixel_y = geo_to_pixel(lon_point, lat_point, geotransform, dataset_srs)
# 保留原始经纬度作为坐标
coor_spectral[i, original_cols] = lon_point
coor_spectral[i, original_cols + 1] = lat_point
else:
print(f"跳过坐标点 {i + 1}: 坐标无效")
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
continue
except:
print(f"跳过坐标点 {i + 1}: 坐标转换失败")
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
continue
else:
# UTM坐标转换为像素坐标
pixel_x, pixel_y = geo_to_pixel(utm_x_point, utm_y_point, geotransform, dataset_srs)
# 存储像素坐标
pixel_x_array[i] = pixel_x
pixel_y_array[i] = pixel_y
# 检查坐标是否在影像范围内
if 0 <= pixel_x < im_width and 0 <= pixel_y < im_height:
valid_pixel_mask[i] = True
else:
valid_pixel_mask[i] = False
if i < 10 or (i % 100 == 0): # 只打印前10个或每100个打印一次
print(f"警告: 坐标点 {i + 1} (UTM X:{utm_x_point:.2f}, Y:{utm_y_point:.2f}) 超出影像范围")
# 批量提取光谱数据优化减少I/O操作
print(f"批量提取光谱数据... (有效坐标点: {np.sum(valid_pixel_mask)})")
if radius > 0:
# 半径采样模式:需要逐个处理
for i in range(coor_data.shape[0]):
if valid_pixel_mask[i]:
spectrum = get_average_spectral_in_radius(
dataset, pixel_x_array[i], pixel_y_array[i], radius, flare_mask, boundary_mask
)
coor_spectral[i, original_cols + 2:] = spectrum
else:
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
else:
# 单点采样模式:批量读取(优化)
# 预读取所有波段数据(如果内存允许)
try:
# 尝试读取所有波段到内存(适用于内存充足的情况)
print("正在预加载所有波段数据到内存(优化模式)...")
all_bands_data = []
for band_idx in range(num_bands):
band = dataset.GetRasterBand(band_idx + 1)
band_data = band.ReadAsArray()
all_bands_data.append(band_data)
all_bands_data = np.array(all_bands_data) # shape: (bands, height, width)
print("预加载完成,开始批量提取像素值...")
# 批量提取像素值
for i in range(coor_data.shape[0]):
if valid_pixel_mask[i]:
px, py = int(pixel_x_array[i]), int(pixel_y_array[i])
# GDAL读取的数组形状是 (bands, height, width),像素坐标 (x,y) 对应数组索引 [:, y, x]
# 注意py是行y坐标px是列x坐标
if 0 <= px < all_bands_data.shape[2] and 0 <= py < all_bands_data.shape[1]:
spectrum = all_bands_data[:, py, px] # 直接索引,非常快
coor_spectral[i, original_cols + 2:] = spectrum
else:
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
else:
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
# 释放内存
del all_bands_data
print("批量提取完成")
except MemoryError:
# 如果内存不足,回退到逐个波段读取
print("内存不足,使用逐个波段读取模式...")
for i in range(coor_data.shape[0]):
if valid_pixel_mask[i]:
px, py = pixel_x_array[i], pixel_y_array[i]
spectrum = np.zeros(num_bands)
for band_idx in range(num_bands):
band = dataset.GetRasterBand(band_idx + 1)
spectrum[band_idx] = band.ReadAsArray(px, py, 1, 1)[0, 0]
coor_spectral[i, original_cols + 2:] = spectrum
else:
coor_spectral[i, original_cols + 2:] = np.zeros(num_bands)
del dataset
# 创建DataFrame用于CSV输出
# 去除前两列坐标列纬度和经度和UTM列
try:
# 如果原始数据有列名,使用原始列名(跳过前两列)
if coor_df is not None and hasattr(coor_df, 'columns'):
# 跳过前两列经纬度从第3列开始
if len(coor_df.columns) >= original_cols:
# 保留第3列及之后的原始列如果有的话
if original_cols > 2:
original_columns = list(coor_df.columns[2:original_cols])
else:
original_columns = []
else:
# 如果原始列数不足,只保留存在的列(跳过前两列)
if len(coor_df.columns) > 2:
original_columns = list(coor_df.columns[2:])
else:
original_columns = []
else:
# 如果没有列名只保留第3列及之后的列如果有的话
if original_cols > 2:
original_columns = ["col_" + str(j + 1) for j in range(2, original_cols)]
else:
original_columns = []
except:
# 异常处理只保留第3列及之后的列如果有的话
if original_cols > 2:
original_columns = ["col_" + str(j + 1) for j in range(2, original_cols)]
else:
original_columns = []
# 读取波长信息,用作光谱列名
wavelengths = None
try:
in_hdr_dict = spectral.envi.read_envi_header(get_hdr_file_path(imgpath))
wavelengths = np.array(in_hdr_dict['wavelength']).astype('float64')
# 将波长值转换为字符串作为列名
spectral_columns = [str(wl) for wl in wavelengths]
print(f"成功读取波长信息,共 {len(spectral_columns)} 个波段")
except Exception as e:
print(f"警告: 无法读取波长信息 ({str(e)}),使用默认列名 band_1, band_2, ...")
spectral_columns = ["band_" + str(j + 1) for j in range(num_bands)]
# 构建输出列名不包含前两列坐标列和UTM列
all_columns = original_columns + spectral_columns
# 从coor_spectral中提取需要输出的列
# 跳过前两列经纬度和UTM列只保留
# - 第3列到第original_cols列如果有的话
# - 光谱数据列从original_cols+2开始
output_data = []
if original_cols > 2:
# 保留第3列到第original_cols列
output_data.append(coor_spectral[:, 2:original_cols])
# 保留光谱数据列从original_cols+2开始
output_data.append(coor_spectral[:, original_cols + 2:])
# 合并数据
if len(output_data) > 0:
output_array = np.hstack(output_data) if len(output_data) > 1 else output_data[0]
else:
# 如果没有原始列,只输出光谱数据
output_array = coor_spectral[:, original_cols + 2:]
# 创建结果DataFrame
result_df = pd.DataFrame(output_array, columns=all_columns)
# 保存为CSV格式
result_df.to_csv(outpath, index=False, float_format='%.6f')
print(f"结果已保存到CSV文件: {outpath}")
return coor_spectral
# 直接运行示例
if __name__ == '__main__':
# 在这里直接设置参数
imgpath = r"E:\code\WQ\封装\work_dir\3_deglint\deglint_goodman.bsq" # BIL格式影像文件路径
coorpath = r"E:\code\WQ\封装\work_dir\4_processed_data\processed_data.csv"# CSV格式坐标文件路径第1、2列为纬度和经度
output_path = r"E:\code\WQ\封装\work_dir\5_training_spectra/yangdian_output.csv" # CSV格式输出文件路径
radius = 5 # 采样半径像素0表示单点采样>0表示半径内平均
flare_path = r"E:\code\WQ\封装\work_dir\2_glint\severe_glint_area.dat" # 耀斑掩膜文件路径可选None表示不使用
boundary_path = r"D:\BaiduNetdiskDownload\yaobao\water_mask.dat" # 边界掩膜文件路径可选None表示不使用
source_epsg = 4326 # 源坐标系EPSG代码默认为4326 (WGS84地理坐标系)
verbose = True # 是否启用详细模式
if verbose:
print(f"影像文件: {imgpath}")
print(f"坐标文件: {coorpath}")
print(f"输出文件: {output_path}")
print(f"采样半径: {radius}")
if flare_path:
print(f"耀斑掩膜: {flare_path}")
if boundary_path:
print(f"边界掩膜: {boundary_path}")
if source_epsg:
print(f"指定坐标系: EPSG:{source_epsg}")
tmp = get_spectral_in_coor(imgpath, coorpath, output_path,
radius, flare_path, boundary_path, source_epsg)

View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@ -0,0 +1,124 @@
import pandas as pd
# ---- 工具:在多个候选列名里自动匹配实际列名 ----
def _find_col(df, candidates, required=True):
cols = [c.strip() for c in df.columns]
colmap = {c.strip(): c for c in df.columns} # strip 后到原名的映射
for cand in candidates:
if cand in cols:
return colmap[cand]
if required:
raise KeyError(f"找不到列:候选 {candidates} ,实际列有:{list(df.columns)}")
return None
# ---- 主函数:输入文件路径,输出文件路径(直接传参)----
def pick_best_by_target(input_csv: str,
output_csv: str = "best_by_target.csv",
tie_break_priority: list | None = None) -> pd.DataFrame:
"""
读取一个CSV表头包含目标列、测试集R² 等),
按“目标列”分组挑选“测试集R²”最高的那一行并做可选的并列打破
导出到 output_csv并返回结果 DataFrame。
"""
df = pd.read_csv(input_csv)
# 处理表头空格/BOM
df.columns = df.columns.str.replace("\ufeff", "", regex=False).str.strip()
# 兼容多种列名写法
target_col = _find_col(df, ["目标列", "Target", "target"])
test_r2_col = _find_col(df, ["测试集R²", "测试集R2", "测试集R^2", "Test R2", "test_R2", "test r2"])
# 常见可选并列指标(按需要会自动忽略不存在的列)
default_ties = [
# metric, order: "min" 表示越小越好;"max" 表示越大越好
("测试集RMSE", "min"), ("Test RMSE", "min"), ("test_RMSE", "min"),
("测试集MAE", "min"), ("Test MAE", "min"), ("test_MAE", "min"),
("测试集MSE", "min"), ("Test MSE", "min"), ("test_MSE", "min"),
]
# 如果用户传入自定义优先级,就覆盖;否则用默认
tie_break_priority = tie_break_priority or default_ties
# 转数值(无法解析置 NaN
df[test_r2_col] = pd.to_numeric(df[test_r2_col], errors="coerce")
# 仅使用有 R² 的行参与选择
df_valid = df.dropna(subset=[test_r2_col]).copy()
if df_valid.empty:
raise ValueError("没有有效的测试集R²数值全为空无法挑选最佳。")
# 每个目标列的候选数量
counts = df.groupby(target_col).size().rename("模型条数")
# 构造排序键:先按 测试集R² 降序,其次按若干并列指标(若列不存在会被跳过)
sort_cols = [test_r2_col]
sort_ascending = [False] # R² 越大越好
for col_name, order in tie_break_priority:
if col_name in df_valid.columns:
sort_cols.append(col_name)
sort_ascending.append(order == "min") # min → True, max → False
# 对每个目标列分组排序后取第一行
best = (
df_valid
.sort_values(by=sort_cols, ascending=sort_ascending, kind="mergesort")
.groupby(target_col, as_index=False)
.head(1)
)
# 合并候选数量,并按 测试集R² 再整体排序一下(可选)
best = best.merge(counts, left_on=target_col, right_index=True)
best = best.sort_values(by=[test_r2_col], ascending=False)
# 导出
best.to_csv(output_csv, index=False, encoding="utf-8-sig")
return best
# ---- 另一个便捷函数:直接传 DataFrame不用落盘读写----
def pick_best_by_target_df(df: pd.DataFrame,
tie_break_priority: list | None = None) -> pd.DataFrame:
"""
与 pick_best_by_target 相同逻辑,但输入是 DataFrame返回挑选后的 DataFrame。
"""
df = df.copy()
df.columns = df.columns.str.replace("\ufeff", "", regex=False).str.strip()
target_col = _find_col(df, ["目标列", "Target", "target"])
test_r2_col = _find_col(df, ["测试集R²", "测试集R2", "测试集R^2", "Test R2", "test_R2", "test r2"])
default_ties = [
("测试集RMSE", "min"), ("Test RMSE", "min"), ("test_RMSE", "min"),
("测试集MAE", "min"), ("Test MAE", "min"), ("test_MAE", "min"),
("测试集MSE", "min"), ("Test MSE", "min"), ("test_MSE", "min"),
]
tie_break_priority = tie_break_priority or default_ties
df[test_r2_col] = pd.to_numeric(df[test_r2_col], errors="coerce")
df_valid = df.dropna(subset=[test_r2_col]).copy()
if df_valid.empty:
raise ValueError("没有有效的测试集R²数值全为空无法挑选最佳。")
counts = df.groupby(target_col).size().rename("模型条数")
sort_cols = [test_r2_col]
sort_ascending = [False]
for col_name, order in tie_break_priority:
if col_name in df_valid.columns:
sort_cols.append(col_name)
sort_ascending.append(order == "min")
best = (
df_valid
.sort_values(by=sort_cols, ascending=sort_ascending, kind="mergesort")
.groupby(target_col, as_index=False)
.head(1)
.merge(counts, left_on=target_col, right_index=True)
.sort_values(by=[test_r2_col], ascending=False)
)
return best
# 路径方式
res = pick_best_by_target(r"E:\code\WQ\yaobao925\qvchuyaoban\batch_detailed_results.csv", output_csv=r"E:\code\WQ\yaobao925\qvchuyaoban\best_by_target.csv")
print(res.head())
# DataFrame 方式(如果你在笔记本里已有 df
# res_df = pick_best_by_target_df(df)
# res_df.to_csv("best_by_target.csv", index=False, encoding="utf-8-sig")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,392 @@
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
import warnings
warnings.filterwarnings('ignore')
class SingleVariableRegressionAnalysis:
"""
单变量回归分析类,支持多种回归方法和对每个自变量单独分析
"""
def __init__(self):
self.results = []
def linear_regression(self, x, y):
"""线性回归: y = a + b*x"""
try:
x_2d = x.reshape(-1, 1)
model = LinearRegression()
model.fit(x_2d, y)
y_pred = model.predict(x_2d)
r2 = r2_score(y, y_pred)
params = f"y = {model.intercept_:.6f} + {model.coef_[0]:.6f}*x"
return r2, params, y_pred
except Exception as e:
return np.nan, f"Error: {str(e)}", None
def exponential_regression(self, x, y):
"""指数回归: y = a * exp(b*x)"""
try:
# 确保y为正数
if np.any(y <= 0):
return np.nan, "Error: y must be positive for exponential regression", None
# 转换为线性形式: ln(y) = ln(a) + b*x
y_log = np.log(y)
x_2d = x.reshape(-1, 1)
model = LinearRegression()
model.fit(x_2d, y_log)
# 转换回指数形式
a = np.exp(model.intercept_)
b = model.coef_[0]
y_pred = a * np.exp(b * x)
r2 = r2_score(y, y_pred)
params = f"y = {a:.6f} * exp({b:.6f}*x)"
return r2, params, y_pred
except Exception as e:
return np.nan, f"Error: {str(e)}", None
def power_regression(self, x, y):
"""乘幂回归: y = a * x^b"""
try:
# 确保x和y为正数
if np.any(x <= 0) or np.any(y <= 0):
return np.nan, "Error: x and y must be positive for power regression", None
# 转换为线性形式: ln(y) = ln(a) + b*ln(x)
x_log = np.log(x)
y_log = np.log(y)
x_2d = x_log.reshape(-1, 1)
model = LinearRegression()
model.fit(x_2d, y_log)
# 转换回幂函数形式
a = np.exp(model.intercept_)
b = model.coef_[0]
y_pred = a * np.power(x, b)
r2 = r2_score(y, y_pred)
params = f"y = {a:.6f} * x^{b:.6f}"
return r2, params, y_pred
except Exception as e:
return np.nan, f"Error: {str(e)}", None
def logarithmic_regression(self, x, y):
"""对数回归: y = a + b*ln(x)"""
try:
# 确保x为正数
if np.any(x <= 0):
return np.nan, "Error: x must be positive for logarithmic regression", None
# 对x取对数
x_log = np.log(x)
x_2d = x_log.reshape(-1, 1)
model = LinearRegression()
model.fit(x_2d, y)
y_pred = model.predict(x_2d)
r2 = r2_score(y, y_pred)
params = f"y = {model.intercept_:.6f} + {model.coef_[0]:.6f}*ln(x)"
return r2, params, y_pred
except Exception as e:
return np.nan, f"Error: {str(e)}", None
def batch_single_variable_regression(self, data, x_columns, y_columns, methods='all', output_dir='custom_regression_results'):
"""
批量单变量回归分析 - 对每个自变量和因变量组合进行回归
Parameters:
-----------
data : pandas.DataFrame
输入数据
x_columns : list
自变量列名列表,对每个自变量单独进行回归
y_columns : str or list
因变量列名或列名列表
methods : str or list
回归方法,可选 'all' 或方法列表 ['linear', 'exponential', 'power', 'logarithmic']
output_dir : str
输出目录路径每个因变量将单独保存为一个CSV文件
"""
# 处理方法参数
if methods == 'all':
methods = ['linear', 'exponential', 'power', 'logarithmic']
method_functions = {
'linear': self.linear_regression,
'exponential': self.exponential_regression,
'power': self.power_regression,
'logarithmic': self.logarithmic_regression
}
# 确保x_columns为列表
if isinstance(x_columns, str):
x_columns = [x_columns]
# 确保y_columns为列表
if isinstance(y_columns, str):
y_columns = [y_columns]
# 创建输出目录
from pathlib import Path
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True, parents=True)
self.results = {}
all_results = []
print(f"开始单变量回归分析:")
print(f"因变量数量: {len(y_columns)}")
print(f"自变量数量: {len(x_columns)}")
print(f"回归方法: {methods}")
print(f"输出目录: {output_dir}")
print("-" * 80)
# 对每个因变量进行回归分析
for y_col in y_columns:
print(f"\n分析因变量: {y_col}")
self.results[y_col] = []
# 对每个自变量单独进行回归分析
for x_col in x_columns:
print(f"\n 分析自变量: {x_col}")
# 准备数据
x_data = data[x_col].values
y_data = data[y_col].values
# 移除包含NaN的行
valid_mask = ~(np.isnan(x_data) | np.isnan(y_data))
x_clean = x_data[valid_mask]
y_clean = y_data[valid_mask]
if len(x_clean) == 0:
print(f" ⚠ 无有效数据,跳过")
continue
print(f" 有效样本数: {len(x_clean)}")
# 对当前自变量执行所有指定的回归方法
for method_name in methods:
if method_name not in method_functions:
continue
regression_func = method_functions[method_name]
try:
r2, equation, y_pred = regression_func(x_clean, y_clean)
if not np.isnan(r2):
result = {
'regression_method': method_name,
'x_variable': x_col,
'y_variable': y_col,
'r_squared': r2,
'equation': equation,
'sample_size': len(x_clean),
'x_mean': np.mean(x_clean),
'x_std': np.std(x_clean),
'y_mean': np.mean(y_clean),
'y_std': np.std(y_clean)
}
self.results[y_col].append(result)
all_results.append(result)
print(f" {method_name:12} | R² = {r2:.6f}")
else:
print(f" {method_name:12} | 失败")
except Exception as e:
print(f" {method_name:12} | 错误: {str(e)}")
# 为当前因变量保存单独的CSV文件
if self.results[y_col]:
results_df = pd.DataFrame(self.results[y_col])
# 按R²排序
results_df = results_df.sort_values(['x_variable', 'r_squared'], ascending=[True, False])
# 为每个因变量创建单独的文件名
safe_y_name = y_col.replace('/', '_').replace('\\', '_').replace(' ', '_')
output_file = output_path / f"{safe_y_name}_regression_results.csv"
results_df.to_csv(output_file, index=False, encoding='utf-8')
print(f"\n {y_col} 的结果已保存到: {output_file}")
# 显示该因变量的最佳模型
self._show_best_models_for_y(results_df, y_col)
# 保存汇总结果到CSV
if all_results:
summary_df = pd.DataFrame(all_results)
# 按因变量和R²排序
summary_df = summary_df.sort_values(['y_variable', 'x_variable', 'r_squared'], ascending=[True, True, False])
summary_file = output_path / "all_regression_results.csv"
summary_df.to_csv(summary_file, index=False, encoding='utf-8')
print(f"\n汇总结果已保存到: {summary_file}")
return self.results
def _show_best_models_for_y(self, results_df, y_variable):
"""显示指定因变量的最佳回归模型"""
if results_df.empty:
return
print(f"\n {y_variable} 的最佳回归模型:")
for x_var in results_df['x_variable'].unique():
x_results = results_df[results_df['x_variable'] == x_var]
best_model = x_results.loc[x_results['r_squared'].idxmax()]
print(f" 自变量 {x_var}:")
print(f" 方法: {best_model['regression_method']}")
print(f" R²: {best_model['r_squared']:.6f}")
print(f" 方程: {best_model['equation']}")
def _show_best_models(self):
"""显示每个自变量的最佳回归模型"""
if not self.results:
return
print("\n" + "=" * 80)
print("每个自变量的最佳回归模型:")
print("=" * 80)
results_df = pd.DataFrame(self.results)
for x_var in results_df['x_variable'].unique():
x_results = results_df[results_df['x_variable'] == x_var]
best_model = x_results.loc[x_results['r_squared'].idxmax()]
print(f"\n自变量: {x_var}")
print(f" 最佳方法: {best_model['regression_method']}")
print(f" R²: {best_model['r_squared']:.6f}")
print(f" 方程: {best_model['equation']}")
print(f" 样本数: {best_model['sample_size']}")
def get_results_df(self):
"""获取结果DataFrame"""
return pd.DataFrame(self.results)
def get_best_models_summary(self):
"""获取每个自变量的最佳模型汇总"""
if not self.results:
return pd.DataFrame()
results_df = pd.DataFrame(self.results)
best_models = []
for x_var in results_df['x_variable'].unique():
x_results = results_df[results_df['x_variable'] == x_var]
best_model = x_results.loc[x_results['r_squared'].idxmax()].to_dict()
best_models.append(best_model)
return pd.DataFrame(best_models)
def main():
"""主函数示例"""
# 创建示例数据
# 初始化回归分析器
analyzer = SingleVariableRegressionAnalysis()
print("=" * 80)
print("水质参数单变量回归分析")
print("=" * 80)
# 示例1: 使用所有回归方法分析光谱指数
print("\n1. 光谱指数与叶绿素a的回归分析:")
sample_data = pd.read_csv(r"E:\code\WQ\pipeline_result\work_dir\5_training_spectra\water_quality_results.csv")
spectral_indices = ['Al10SABI','Am092Bsub']
results1 = analyzer.batch_single_variable_regression(
data=sample_data,
x_columns=spectral_indices,
y_column='Chlorophyll',
methods='all',
output_file=r'E:\code\WQ\pipeline_result\work_dir\5_training_spectra\spectral_indices_regression.csv'
)
# # 示例2: 使用特定方法分析反射率波段
# print("\n2. 反射率波段与叶绿素a的回归分析:")
# reflectance_bands = ['R443', 'R490', 'R560', 'R665', 'R705', 'R740']
#
# results2 = analyzer.batch_single_variable_regression(
# data=sample_data,
# x_columns=reflectance_bands,
# y_column='Chl_a',
# methods=['linear', 'power', 'logarithmic'],
# output_file='reflectance_bands_regression.csv'
# )
# 示例3: 获取最佳模型汇总
print("\n3. 最佳模型汇总:")
best_models = analyzer.get_best_models_summary()
if not best_models.empty:
print(best_models[['x_variable', 'regression_method', 'r_squared', 'equation']].to_string(index=False))
best_models.to_csv(r'E:\code\WQ\pipeline_result\work_dir\5_training_spectra\best_models_summary.csv', index=False)
print("\n最佳模型汇总已保存到 'best_models_summary.csv'")
#
# def advanced_usage_example():
# """高级使用示例 - 处理实际数据"""
# # 读取您的实际数据
# try:
# # 替换为您的实际数据文件路径
# data = pd.read_csv('your_actual_water_data.csv')
#
# # 假设您的数据包含以下列(根据实际情况调整)
# # 光谱指数列: ['NDCI', 'FLH', 'NDTI', 'SABI', ...]
# # 反射率列: ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', ...] 或 ['R443', 'R490', ...]
# # 水质参数列: ['Chl_a', 'Turbidity', 'TSS', 'CDOM', ...]
#
# analyzer = SingleVariableRegressionAnalysis()
#
# # 分析叶绿素a与所有光谱指数的关系
# spectral_indices = ['NDCI', 'FLH', 'NDTI', 'SABI'] # 替换为您的实际列名
# analyzer.batch_single_variable_regression(
# data=data,
# x_columns=spectral_indices,
# y_column='Chl_a', # 替换为您的实际水质参数列名
# methods='all',
# output_file='chl_a_spectral_regression.csv'
# )
#
# # 分析浊度与反射率波段的关系
# reflectance_bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'] # 替换为您的实际列名
# analyzer.batch_single_variable_regression(
# data=data,
# x_columns=reflectance_bands,
# y_column='Turbidity', # 替换为您的实际水质参数列名
# methods=['linear', 'power'],
# output_file='turbidity_reflectance_regression.csv'
# )
#
# except FileNotFoundError:
# print("请准备您的实际数据文件 'your_actual_water_data.csv'")
# except Exception as e:
# print(f"处理数据时出错: {str(e)}")
if __name__ == "__main__":
main()
# 取消注释以下行来处理您的实际数据
# advanced_usage_example()

View File

@ -0,0 +1,382 @@
import numpy as np
import pandas as pd
import os
from osgeo import gdal
from src.utils.util import *
from src.core.type_define import *
import math
from pyproj import CRS
from pyproj import Transformer
import argparse
import json
def get_spectral_data_from_csv(csv_path, value_col, spectral_start_col, spectral_end_col):
"""
从CSV文件中读取实测值和光谱数据
:param csv_path: CSV文件路径
:param value_col: 实测值列索引
:param spectral_start_col: 光谱数据起始列索引
:param spectral_end_col: 光谱数据结束列索引
:return: 包含实测值和光谱数据的numpy数组和表头信息
"""
try:
# 使用pandas读取CSV数据处理缺失值
df = pd.read_csv(csv_path, na_values=['', ' ', 'NaN', 'nan', 'NULL', 'null'])
# 获取表头
header = df.columns.tolist()
print(f"原始数据形状: {df.shape}")
# 提取实测值列和光谱数据列
measured_values = df.iloc[:, value_col].values
spectral_data = df.iloc[:, spectral_start_col:spectral_end_col+1].values
# 组合数据
combined_data = np.column_stack((measured_values, spectral_data))
# 检查并清理数据中的NaN值
# 找到所有不包含NaN的行
valid_rows = ~np.isnan(combined_data).any(axis=1)
if not np.any(valid_rows):
raise ValueError("所有数据行都包含缺失值,无法进行模型训练")
# 过滤有效数据
cleaned_data = combined_data[valid_rows]
print(f"清理后数据形状: {cleaned_data.shape} (移除了 {combined_data.shape[0] - cleaned_data.shape[0]} 行无效数据)")
return cleaned_data, header
except pd.errors.EmptyDataError:
raise ValueError(f"CSV文件 '{csv_path}' 为空或不存在")
except Exception as e:
raise ValueError(f"读取CSV文件 '{csv_path}' 时出错: {e}")
def fit(x1, x2, y):
A = np.column_stack((x1, x2, np.ones((x2.shape[0], 1))))
coefficients, _, _, _ = np.linalg.lstsq(A, y, rcond=None)
return coefficients
def accuracy_evaluation(x1, x2, y_real, coefficients):
A = np.column_stack((x1, x2, np.ones((x2.shape[0], 1))))
y_pred = A.dot(coefficients)
accuracy = np.absolute((y_real - y_pred) / y_real * 100)
return accuracy
def accuracy_evaluation_tss(x1, x2, y_real, coefficients):
A = np.column_stack((x1, x2, np.ones((x2.shape[0], 1))))
y = A.dot(coefficients)
y_pred = np.exp(y)
accuracy = np.absolute((y_real - y_pred) / y_real * 100)
return accuracy
def get_x_in_coor(coor, *args):
new_columns_counter = len(args)
new_columns = np.zeros((coor.shape[0], new_columns_counter))
coor_extend = np.hstack((coor, new_columns))
for i in range(coor.shape[0]):
for j in range(new_columns_counter):
coor_extend[i, coor_extend.shape[1] - (new_columns_counter - j)] = args[j][
int(coor_extend[i, coor_extend.shape[1] - new_columns_counter - 1]),
int(coor_extend[i, coor_extend.shape[1] - new_columns_counter - 2])]
return coor_extend
def write_model_info(model_type, coefficients, accuracy, long, lat, outpath):
# 将 NumPy 数组转换为列表
#保存模型为json文件包括模型类型、模型系数、准确率、经纬度模型名称由上一级调用决定/model/non_empirical_model/preprocessing_method_model_name.jso
np_dict = {
'model_type': model_type,
'model_info': coefficients.tolist(),
'accuracy': accuracy.tolist(),
'long': long.tolist(),
'lat': lat.tolist()
}
# 将字典写入 JSON 文件,使用 indent 参数进行格式化每一级缩进4个空格
with open(outpath, 'w') as f:
json.dump(np_dict, f, indent=4)
def chl_a(csv_data, outpath_coeff, window=5, header=None): # 叶绿素
"""
叶绿素模型修正
:param csv_data: 从CSV读取的数据数组
:param outpath_coeff: 输出模型信息文件路径
:param window: 窗口大小
:极 header: CSV表头信息
:return: 模型系数
"""
# 实测值在第一列,光谱数据从第二列开始
measured_values = csv_data[:, 0]
spectral_data = csv_data[:, 1:]
# 通过表头查找波长位置
if header is not None:
# 查找波长对应的列索引
wave1_idx = find_wavelength_index(header, 651, spectral_start_col=1)
wave2_idx = find_wavelength_index(header, 707, spectral_start_col=1)
wave3_idx = find_wavelength_index(header, 670, spectral_start_col=1)
else:
# 如果没有表头,使用默认索引
wave1_idx = 651
wave2_idx = 707
wave3_idx = 670
# 计算波段平均值
band_651 = np.mean(spectral_data[:, wave1_idx-window:wave1_idx+window+1], axis=1)
band_707 = np.mean(spectral_data[:, wave2_idx-window:wave2_idx+window+1], axis=1)
band_670 = np.mean(spectral_data[:, wave3_idx-window:wave3_idx+window+1], axis=1)
x = (band_651 - band_707) / (band_707 - band_670)
# 修正模型参数并输出
coefficients = np.polyfit(x, measured_values, 1)
y_pred = np.polyval(coefficients, x)
accuracy = np.absolute((measured_values - y_pred) / measured_values * 100)
# 创建虚拟的经纬度坐标(因为不再需要地理坐标)
long = np.arange(len(measured_values))
lat = np.arange(len(measured_values))
write_model_info("chl-a", coefficients, accuracy, long, lat, outpath_coeff)
return coefficients
def nh3(csv_data, outpath_coeff, window=5, header=None): # 氨氮
measured_values = csv_data[:, 0]
spectral_data = csv_data[:, 1:]
# 通过表头查找波长位置
if header is not None:
wave1_idx = find_wavelength_index(header, 600, spectral_start_col=1)
wave2_idx = find_wavelength_index(header, 500, spectral_start_col=1)
wave3_idx = find_wavelength_index(header, 850, spectral_start_col=1)
else:
wave1_idx = 600
wave2_idx = 500
wave3_idx = 850
band_600 = np.mean(spectral_data[:, wave1_idx-window:wave1_idx+window+1], axis=1)
band_500 = np.mean(spectral_data[:, wave2_idx-window:wave2_idx+window+1], axis=1)
band_850 = np.mean(spectral_data[:, wave3_idx-window:wave3_idx+window+1], axis=1)
x1 = np.log(band_500 / band_850)
x2 = np.exp(band_600 / band_500)
coefficients = fit(x1, x2, measured_values)
accuracy = accuracy_evaluation(x1, x2, measured_values, coefficients)
long = np.arange(len(measured_values))
lat = np.arange(len(measured_values))
write_model_info("nh3", coefficients, accuracy, long, lat, outpath_coeff)
return coefficients
def mno4(csv_data, outpath_coeff, window=5, header=None): # 高猛酸盐
measured_values = csv_data[:, 0]
spectral_data = csv_data[:, 1:]
# 通过表头查找波长位置
if header is not None:
wave1_idx = find_wavelength_index(header, 500, spectral_start_col=1)
wave2_idx = find_wavelength_index(header, 440, spectral_start_col=1)
wave3_idx = find_wavelength_index(header, 610, spectral_start_col=1)
wave4_idx = find_wavelength_index(header, 800, spectral_start_col=1)
else:
wave1_idx = 500
wave2_idx = 440
wave3_idx = 610
wave4_idx = 800
band_500 = np.mean(spectral_data[:, wave1_idx-window:wave1_idx+window+1], axis=1)
band_440 = np.mean(spectral_data[:, wave2_idx-window:wave2_idx+window+1], axis=1)
band_610 = np.mean(spectral_data[:, wave3_idx-window:wave3_idx+window+1], axis=1)
band_800 = np.mean(spectral_data[:, wave4_idx-window:wave4_idx+window+1], axis=1)
x1 = band_500 / band_440
x2 = band_610 / band_800
coefficients = fit(x1, x2, measured_values)
accuracy = accuracy_evaluation(x1, x2, measured_values, coefficients)
long = np.arange(len(measured_values))
lat = np.arange(len(measured_values))
write_model_info("mno4", coefficients, accuracy, long, lat, outpath_coeff)
return coefficients
def tn(csv_data, outpath_coeff, window=5, header=None): # 总氮
measured_values = csv_data[:, 0]
spectral_data = csv_data[:, 1:]
# 通过表头查找波长位置
if header is not None:
wave1_idx = find_wavelength_index(header, 600, spectral_start_col=1)
wave2_idx = find_wavelength_index(header, 500, spectral_start_col=1)
wave3_idx = find_wavelength_index(header, 850, spectral_start_col=1)
else:
wave1_idx = 600
wave2_idx = 500
wave3_idx = 850
band_600 = np.mean(spectral_data[:, wave1_idx-window:wave1_idx+window+1], axis=1)
band_500 = np.mean(spectral_data[:, wave2_idx-window:wave2_idx+window+1], axis=1)
band_850 = np.mean(spectral_data[:, wave3_idx-window:wave3_idx+window+1], axis=1)
x1 = np.log(band_500 / band_850)
x2 = np.exp(band_600 / band_500)
coefficients = fit(x1, x2, measured_values)
accuracy = accuracy_evaluation(x1, x2, measured_values, coefficients)
long = np.arange(len(measured_values))
lat = np.arange(len(measured_values))
write_model_info("tn", coefficients, accuracy, long, lat, outpath_coeff)
return coefficients
def tp(csv_data, outpath_coeff, window=5, header=None): # 总磷
measured_values = csv_data[:, 0]
spectral_data = csv_data[:, 1:]
# 通过表头查找波长位置
if header is not None:
wave1_idx = find_wavelength_index(header, 600, spectral_start_col=1)
wave2_idx = find_wavelength_index(header, 500, spectral_start_col=1)
wave3_idx = find_wavelength_index(header, 850, spectral_start_col=1)
else:
wave1_idx = 600
wave2_idx = 500
wave3_idx = 850
band_600 = np.mean(spectral_data[:, wave1_idx-window:wave1_idx+window+1], axis=1)
band_500 = np.mean(spectral_data[:, wave2_idx-window:wave2_idx+window+1], axis=1)
band_850 = np.mean(spectral_data[:, wave3_idx-window:wave3_idx+window+1], axis=1)
x1 = np.log(band_500 / band_850)
x2 = np.exp(band_600 / band_500)
coefficients = fit(x1, x2, measured_values)
accuracy = accuracy_evaluation(x1, x2, measured_values, coefficients)
long = np.arange(len(measured_values))
lat = np.arange(len(measured_values))
write_model_info("tp", coefficients, accuracy, long, lat, outpath_coeff)
return coefficients
def tss(csv_data, outpath_coeff, window=5, header=None): # 总悬浮物
measured_values = csv_data[:, 0]
spectral_data = csv_data[:, 1:]
# 通过表头查找波长位置
if header is not None:
wave1_idx = find_wavelength_index(header, 555, spectral_start_col=1)
wave2_idx = find_wavelength_index(header, 670, spectral_start_col=1)
wave3_idx = find_wavelength_index(header, 490, spectral_start_col=1)
else:
wave1_idx = 555
wave2_idx = 670
wave3_idx = 490
band_555 = np.mean(spectral_data[:, wave1_idx-window:wave1_idx+window+1], axis=1)
band_670 = np.mean(spectral_data[:, wave2_idx-window:wave2_idx+window+1], axis=1)
band_490 = np.mean(spectral_data[:, wave3_idx-window:wave3_idx+window+1], axis=1)
x1 = band_555 + band_670
x2 = band_490 / band_555
y = np.log(measured_values)
coefficients = fit(x1, x2, y)
accuracy = accuracy_evaluation_tss(x1, x2, measured_values, coefficients)
long = np.arange(len(measured_values))
lat = np.arange(len(measured_values))
write_model_info("tss", coefficients, accuracy, long, lat, outpath_coeff)
return coefficients
def run_model_correction(algorithm, csv_file, value_col, spectral_start, spectral_end, model_info_outpath, window=5):
"""
运行模型修正
:param algorithm: 算法名称 (chl_a, nh3, mno4, tn, tp, tss)
:param csv_file: CSV文件路径
:param value_col: 实测值列索引
:param spectral_start: 光谱数据起始列索引
:param spectral_end: 光谱数据结束列索引
:param model_info_outpath: 输出模型信息文件路径
:param window: 窗口大小默认5
:return: 模型系数
"""
# 从CSV文件读取数据和表头直接找到模型对应的所需数据第一列为实测值从第二列开始为光谱数据
csv_data, header = get_spectral_data_from_csv(csv_file, value_col, spectral_start, spectral_end)
# 根据算法名称调用相应的函数
algorithm_funcs = {
'chl_a': chl_a,
'nh3': nh3,
'mno4': mno4,
'tn': tn,
'tp': tp,
'tss': tss
}
if algorithm not in algorithm_funcs:
raise ValueError(f"不支持的算法: {algorithm}。支持的算法有: {list(algorithm_funcs.keys())}")
# 调用相应的函数,传递表头信息
coefficients = algorithm_funcs[algorithm](csv_data, model_info_outpath, window, header)
return coefficients
def find_wavelength_index(header, target_wavelength, spectral_start_col=1):
"""
在表头中查找最接近目标波长的列索引
:param header: CSV表头列表
:param target_wavelength: 目标波长
:param spectral_start_col: 光谱数据起始列索引
:return: 最接近目标波长的列索引
"""
# 从光谱数据起始列开始查找
min_diff = float('inf')
best_index = target_wavelength # 默认值
for i in range(spectral_start_col, len(header)):
try:
# 尝试将列名转换为波长值
wavelength = float(header[i])
diff = abs(wavelength - target_wavelength)
if diff < min_diff:
min_diff = diff
best_index = i - spectral_start_col # 转换为光谱数据内的相对索引
except ValueError:
# 如果列名不是数字,跳过
continue
return best_index

View File

@ -0,0 +1,253 @@
import sys
from src.utils.util import *
import warnings
import pandas as pd
import re # Added for regex parsing in safe_load_spectral
# 配置光谱起始列前四列是坐标和像素信息x_coord,y_coord,pixel_x,pixel_y
SPEC_START_COL = 4
class RetrievalError(Exception):
"""面向用户的友好错误。"""
pass
def ensure_file_exists(path, name):
if not isinstance(path, str) or not path:
raise RetrievalError(f"{name} 路径为空。")
if not os.path.exists(path):
raise RetrievalError(f"{name} 不存在:{path}")
def safe_load_model(model_info_path):
ensure_file_exists(model_info_path, "模型信息文件")
try:
model_type, model_info, accuracy_ = load_numpy_dict_from_json(model_info_path)
except Exception as e:
raise RetrievalError(f"无法读取/解析模型文件:{model_info_path}\n原因:{e}")
if model_info is None:
raise RetrievalError("模型文件缺少 'model_info'")
model_info = np.asarray(model_info)
if model_info.ndim == 0 or model_info.size == 0:
raise RetrievalError("模型系数为空。")
return model_type, model_info, accuracy_
def safe_load_spectral(coor_spectral_path):
ensure_file_exists(coor_spectral_path, "坐标-光谱文件")
# 使用 pandas 读取文件
try:
# 读取为 DataFrame跳过第一行列名明确指定数据类型为 float
df = pd.read_csv(coor_spectral_path, encoding="utf-8-sig", header=0, dtype=float)
# 转换为 numpy 数组以保持原有格式
coor_spectral = df.values
except Exception as e:
raise RetrievalError(f"无法读取坐标-光谱文件:{coor_spectral_path}\n原因:{e}")
if coor_spectral.ndim != 2 or coor_spectral.shape[0] < 1:
raise RetrievalError("坐标-光谱文件维度异常:需要至少一行数据。")
if coor_spectral.shape[1] <= SPEC_START_COL:
raise RetrievalError(f"坐标-光谱文件列数不足(至少需要 {SPEC_START_COL+1} 列,含 4 列坐标信息 + ≥1 列光谱)。")
# 由于第一行已经是数据,不再需要提取波长行
# 波长信息需要从列名中提取
try:
# 读取列名来获取波长信息
df_with_header = pd.read_csv(coor_spectral_path, encoding="utf-8-sig", header=0)
wavelengths = df_with_header.columns[SPEC_START_COL:].astype(float).values
except Exception as e:
raise RetrievalError(f"无法解析波长信息:{e}")
if not np.all(np.isfinite(wavelengths)):
raise RetrievalError("波长数据包含 NaN/Inf。")
# 非严格单调也可,但给出警告
if np.any(np.diff(wavelengths) <= 0):
warnings.warn("波长非严格递增,这可能导致波段匹配误差。", RuntimeWarning)
return coor_spectral, wavelengths
def find_index(wavelength, array):
differences = np.abs(array - wavelength)
min_position = int(np.argmin(differences))
return min_position
def _clamp_window(index_abs, window, ncols, spec_start_col=SPEC_START_COL):
if window is None:
raise RetrievalError("window 为空。")
window = int(window)
if window < 0:
raise RetrievalError(f"window 必须为非负整数,收到:{window}")
left = max(spec_start_col, index_abs - window)
right = min(ncols, index_abs + window + 1)
if right - left <= 0:
raise RetrievalError(f"窗口无有效光谱列left={left}, right={right}, ncols={ncols})。")
return left, right
def get_mean_value(index_abs, array, window):
"""index_abs 为绝对列索引(含前两列坐标),这里会夹紧窗口。"""
left, right = _clamp_window(index_abs, window, array.shape[1], SPEC_START_COL)
# 仅在样本行上取平均
result = array[1:, left:right].mean(axis=1)
if not np.all(np.isfinite(result)):
warnings.warn("均值结果包含 NaN/Inf可能是窗口内存在异常值。", RuntimeWarning)
return result
def calculate(x1, x2, coefficients):
x1 = np.asarray(x1, dtype=np.float64).ravel()
x2 = np.asarray(x2, dtype=np.float64).ravel()
coeffs = np.asarray(coefficients, dtype=np.float64).reshape(-1)
if x1.shape[0] != x2.shape[0]:
raise RetrievalError(f"x1 与 x2 长度不一致: {x1.shape[0]} vs {x2.shape[0]}")
if coeffs.size != 3:
raise RetrievalError(f"线性模型系数应为 3 个x1, x2, 截距),收到 {coeffs.size} 个。")
# 诊断:检查 NaN/Inf
n_bad = (~np.isfinite(x1) | ~np.isfinite(x2)).sum()
if n_bad:
print(f"[警告] x 含 {n_bad} 个非有限值,将产生 NaN。")
# 避免 dot/blAS直接逐元素计算
y_pred = x1 * coeffs[0] + x2 * coeffs[1] + coeffs[2]
return y_pred
def _safe_polyval(coeffs, x, name):
coeffs = np.asarray(coeffs).reshape(-1)
if coeffs.ndim != 1 or coeffs.size < 1:
raise RetrievalError(f"{name} 的多项式系数非法。")
try:
y = np.polyval(coeffs, x)
except Exception as e:
raise RetrievalError(f"{name} 计算失败polyval{e}")
return y
def retrieval_chl_a(model_info_path, coor_spectral_path, output_path, window=5):
model_type, model_info, accuracy_ = safe_load_model(model_info_path)
coor_spectral, wavelengths = safe_load_spectral(coor_spectral_path)
def idx_abs_for(wave):
idx_rel = find_index(wave, wavelengths) # 相对光谱起始列的索引
return SPEC_START_COL + idx_rel # 转为绝对列索引
try:
idx_651 = idx_abs_for(651)
idx_707 = idx_abs_for(707)
idx_670 = idx_abs_for(670)
except Exception as e:
raise RetrievalError(f"波段索引计算失败:{e}")
band_651 = get_mean_value(idx_651, coor_spectral, window)
band_707 = get_mean_value(idx_707, coor_spectral, window)
band_670 = get_mean_value(idx_670, coor_spectral, window)
with np.errstate(divide='ignore', invalid='ignore'):
denom = (band_707 - band_670)
x = (band_651 - band_707) / denom
bad = ~np.isfinite(x)
if bad.any():
warnings.warn(f"chl_a 极速出现 {bad.sum()} 个无效比值分母≈0 或含 NaN这些位置结果将为 NaN。", RuntimeWarning)
retrieval_result = _safe_polyval(model_info, x, "chl_a")
# 创建DataFrame并保存为CSV
result_df = pd.DataFrame({
'longitude': coor_spectral[1:, 0],
'latitude': coor_spectral[1:, 1],
'prediction': retrieval_result
})
try:
result_df.to_csv(output_path, index=False, float_format='%.8f')
except Exception as e:
raise RetrievalError(f"写出结果失败:{output_path}\n原因:{e}")
return result_df.values
def retrieval_nh3(model_info_path, coor_spectral_path, output_path=None, window=5):
model_type, model_info, accuracy_ = safe_load_model(model_info_path)
coor_spectral, wavelengths = safe_load_spectral(coor_spectral_path)
def idx_abs_for(wave):
return SPEC_START_COL + find_index(wave, wavelengths)
idx_600 = idx_abs_for(600)
idx_500 = idx_abs_for(500)
idx_850 = idx_abs_for(850)
band_600 = get_mean_value(idx_600, coor_spectral, window)
band_500 = get_mean_value(idx_500, coor_spectral, window)
band_850 = get_mean_value(idx_850, coor_spectral, window)
with np.errstate(divide='ignore', invalid='ignore'):
x13 = np.log(band_500 / band_850)
x23 = np.exp(band_600 / band_500)
invalid = ~np.isfinite(x13) | ~np.isfinite(x23)
if invalid.any():
warnings.warn(f"nh3 自变量出现 {invalid.sum()} 个无效值0/负数/NaN对应位置结果将为 NaN。", RuntimeWarning)
retrieval_result = calculate(x13, x23, model_info)
# 创建DataFrame
result_df = pd.DataFrame({
'longitude': coor_spectral[1:, 0],
'latitude': coor_spectral[1:, 1],
'prediction': retrieval_result
})
if output_path is not None:
try:
result_df.to_csv(output_path, index=False, float_format='%.8f')
except Exception as e:
raise RetrievalError(f"写出结果失败:{output_path}\n原因:{e}")
return result_df.values
def retrieval_tss(model_info_path, coor_spectral_path, output_path, window=5):
# 先跑 nh3 的同型模型(按你的原逻辑)
position_content = retrieval_nh3(model_info_path, coor_spectral_path, output_path=None, window=window)
# 对结果进行指数变换
predictions = np.exp(position_content[:, -1])
# 创建DataFrame
result_df = pd.DataFrame({
'longitude': position_content[:, 0],
'latitude': position_content[:, 1],
'prediction': predictions
})
if not np.all(np.isfinite(result_df['prediction'])):
warnings.warn("tss 结果包含非有限值(可能因指数溢出),已保留为 NaN。", RuntimeWarning)
try:
result_df.to_csv(output_path, index=False, float_format='%.8f')
except Exception as e:
raise RetrievalError(f"写出结果失败:{output_path}\n原因:{e}")
return result_df.values
def non_empirical_retrieval(algorithm, model_info_path, coor_spectral_path, output_path, wave_radius=5.0):
try:
if algorithm == "chl_a":
return retrieval_chl_a(model_info_path, coor_spectral_path, output_path, wave_radius)
elif algorithm in ["nh3", "mno4", "tn", "tp"]:
return retrieval_nh3(model_info_path, coor_spectral_path, output_path, wave_radius)
elif algorithm == "tss":
return retrieval_tss(model_info_path, coor_spectral_path, output_path, wave_radius)
else:
raise RetrievalError(f"未知算法:{algorithm}可选chl_a / nh3 / mno4 / tn / tp / tss")
except RetrievalError as e:
# 面向用户的友好错误
print(f"[错误] {e}", file=sys.stderr)
sys.exit(2)
except Exception as e:
# 未预料的异常,附带类型与少量上下文
print(f"[致命错误] {type(e).__name__}: {e}", file=sys.stderr)
sys.exit(3)
if __name__ == "__main__":
algorithm= "chl_a"
model_info_path= r"E:\code\WQ\pipeline_result\work_dir\5_training_spectra\6_5_non_empirical_models\SS\SS_chl_a.json"
coor_spectral_path= r"E:\code\WQ\pipeline_result\work_dir\7_sampling\sampling_spectra.csv"
output_path= r"E:\code\WQ\pipeline_result\work_dir\8_predictions\SS_chl_a.csv"
wave_radius=5.0
non_empirical_retrieval(algorithm, model_info_path, coor_spectral_path, output_path, wave_radius)

View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,894 @@
import numpy as np
import pandas as pd
import joblib
import os
from pathlib import Path
from typing import List, Dict, Union, Tuple, Optional
import warnings
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import scipy.stats as stats
warnings.filterwarnings('ignore')
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['font.size'] = 12
# 机器学习模型导入 - 改为回归模型
from sklearn.svm import SVR
from sklearn.ensemble import RandomForestRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
from sklearn.model_selection import GridSearchCV, cross_val_score, KFold, train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.cross_decomposition import PLSRegression
# 第三方模型导入
# try:
# import lightgbm as lgb
# LGB_AVAILABLE = True
# except ImportError:
# LGB_AVAILABLE = False
LGB_AVAILABLE = False # 注释掉lightgbm
# try:
# import catboost as cb
# CB_AVAILABLE = True
# except ImportError:
# CB_AVAILABLE = False
CB_AVAILABLE = False # 注释掉catboost
# 导入预处理模块
# 动态导入预处理模块
import sys
import os
from src.preprocessing.spectral_Preprocessing import Preprocessing
class WaterQualityScatterBatch:
"""水质参数反演批量散点图绘制类"""
def __init__(self):
"""初始化批量散点图绘制类"""
# 定义支持的回归模型及其参数网格
self.model_configs = {
'SVR': {
'model': SVR,
'params': {
'C': [0.1, 1, 10, 100],
'gamma': ['scale', 'auto', 0.001, 0.01, 0.1, 1],
'kernel': ['rbf', 'poly', 'sigmoid'],
'epsilon': [0.01, 0.1, 0.2]
},
'available': True
},
'RF': {
'model': RandomForestRegressor,
'params': {
'n_estimators': [50, 100, 200],
'max_depth': [None, 10, 20, 30],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4]
},
'available': True
},
'KNN': {
'model': KNeighborsRegressor,
'params': {
'n_neighbors': [3, 5, 7, 9, 11],
'weights': ['uniform', 'distance'],
'metric': ['euclidean', 'manhattan', 'minkowski']
},
'available': True
},
'LinearRegression': {
'model': LinearRegression,
'params': {
'fit_intercept': [True, False]
},
'available': True
},
'Ridge': {
'model': Ridge,
'params': {
'alpha': [0.01, 0.1, 1, 10, 100],
'fit_intercept': [True, False]
},
'available': True
},
'Lasso': {
'model': Lasso,
'params': {
'alpha': [0.01, 0.1, 1, 10, 100],
'fit_intercept': [True, False],
'max_iter': [1000, 2000]
},
'available': True
},
'ElasticNet': {
'model': ElasticNet,
'params': {
'alpha': [0.01, 0.1, 1, 10],
'l1_ratio': [0.1, 0.3, 0.5, 0.7, 0.9],
'fit_intercept': [True, False],
'max_iter': [1000, 2000]
},
'available': True
},
'XGBoost': {
'model': None, # xgboost is removed, so set to None
'params': {
'n_estimators': [50, 100, 200],
'max_depth': [3, 6, 9],
'learning_rate': [0.01, 0.1, 0.2],
'subsample': [0.8, 0.9, 1.0]
},
'available': False
},
'LightGBM': {
'model': lgb.LGBMRegressor if LGB_AVAILABLE else None,
'params': {
'n_estimators': [50, 100, 200],
'max_depth': [3, 6, 9],
'learning_rate': [0.01, 0.1, 0.2],
'num_leaves': [31, 50, 100]
},
'available': LGB_AVAILABLE
},
'CatBoost': {
'model': cb.CatBoostRegressor if CB_AVAILABLE else None,
'params': {
'iterations': [50, 100, 200],
'depth': [3, 6, 9],
'learning_rate': [0.01, 0.1, 0.2],
'l2_leaf_reg': [1, 3, 5]
},
'available': CB_AVAILABLE
},
'PLS': {
'model': PLSRegression,
'params': {
'n_components': [2, 3, 5, 7, 10]
},
'available': True
}
}
# 预处理方法列表
self.preprocessing_methods = [
"None", "MMS", "SS", "CT", "SNV", "MA", "SG", "MSC", "D1", "D2", "DT", "WVAE"
]
# 样本划分方法列表
self.split_methods = ["random", "spxy", "ks"]
def load_data(self, csv_path: str, target_column_name: str = None, target_column: int = None, feature_start_column: int = 13) -> Tuple[pd.DataFrame, pd.Series]:
"""
加载CSV数据
Args:
csv_path: CSV文件路径
target_column_name: 目标值列名(优先使用)
target_column: 目标值列索引(当列名不存在时使用)
feature_start_column: 特征开始列索引
Returns:
X: 特征数据
y: 目标值数据
"""
data = pd.read_csv(csv_path)
# 根据列名或列索引提取目标值
if target_column_name and target_column_name in data.columns:
print(f"使用列名 '{target_column_name}' 作为目标值")
y = data[target_column_name]
target_col_index = data.columns.get_loc(target_column_name)
elif target_column is not None:
print(f"使用列索引 {target_column} 作为目标值")
y = data.iloc[:, target_column]
target_col_index = target_column
else:
raise ValueError("必须指定 target_column_name 或 target_column")
# 提取特征数据
X = data.iloc[:, feature_start_column:]
# 去除y值为空的行
mask = ~y.isna()
data_cleaned = data[mask]
if target_column_name and target_column_name in data.columns:
y = data_cleaned[target_column_name]
else:
y = data_cleaned.iloc[:, target_col_index]
X = data_cleaned.iloc[:, feature_start_column:]
print(f"数据加载完成:")
print(f" 目标列: {target_column_name if target_column_name else f'索引{target_col_index}'}")
print(f" 样本数量: {X.shape[0]}")
print(f" 特征数量: {X.shape[1]}")
print(f" 目标值范围: {y.min():.4f} ~ {y.max():.4f}")
print(f" 目标值均值: {y.mean():.4f}")
return X, y
def preprocess_data(self, X: pd.DataFrame, method: str) -> np.ndarray:
"""
数据预处理
Args:
X: 原始特征数据
method: 预处理方法
Returns:
预处理后的数据
"""
print(f"应用预处理方法: {method}")
# 如果方法为None直接返回原始数据
if method == "None" or method is None:
print("跳过预处理,使用原始数据")
return X.values
try:
X_processed = Preprocessing(method, X)
# 确保返回的是numpy数组
if isinstance(X_processed, pd.DataFrame):
X_processed = X_processed.values
print(f"预处理完成,数据形状: {X_processed.shape}")
return X_processed
except Exception as e:
print(f"预处理失败: {e}")
print("使用原始数据")
return X.values
def random(self, data, label, test_ratio=0.2, random_state=123):
"""随机划分数据集"""
X_train, X_test, y_train, y_test = train_test_split(
data, label, test_size=test_ratio, random_state=random_state
)
return X_train, X_test, y_train, y_test
def spxy(self, data, label, test_size=0.2):
"""SPXY算法划分数据集"""
# 确保 data 和 label 是 NumPy 数组
data = data.to_numpy() if isinstance(data, pd.DataFrame) else data
label = label.to_numpy() if isinstance(label, pd.Series) else label
# 备份原始数据和标签
x_backup = data
y_backup = label
M = data.shape[0]
N = round((1 - test_size) * M)
samples = np.arange(M)
# 归一化标签数据
label = (label - np.mean(label)) / np.std(label)
D = np.zeros((M, M))
Dy = np.zeros((M, M))
# 计算样本之间的距离
for i in range(M - 1):
xa = data[i, :]
ya = label[i]
for j in range((i + 1), M):
xb = data[j, :]
yb = label[j]
D[i, j] = np.linalg.norm(xa - xb)
Dy[i, j] = np.linalg.norm(ya - yb)
# 距离归一化
Dmax = np.max(D)
Dymax = np.max(Dy)
D = D / Dmax + Dy / Dymax
# 找到最远的两个点
maxD = D.max(axis=0)
index_row = D.argmax(axis=0)
index_column = maxD.argmax()
m = np.zeros(N, dtype=int)
m[0] = index_row[index_column]
m[1] = index_column
dminmax = np.zeros(N)
dminmax[1] = D[m[0], m[1]]
# 根据距离选择训练集
for i in range(2, N):
pool = np.delete(samples, m[:i])
dmin = np.zeros(M - i)
for j in range(M - i):
indexa = pool[j]
d = np.zeros(i)
for k in range(i):
indexb = m[k]
if indexa < indexb:
d[k] = D[indexa, indexb]
else:
d[k] = D[indexb, indexa]
dmin[j] = np.min(d)
dminmax[i] = np.max(dmin)
index = np.argmax(dmin)
m[i] = pool[index]
m_complement = np.delete(samples, m)
# 划分训练集和测试集
X_train = data[m, :]
y_train = y_backup[m]
X_test = data[m_complement, :]
y_test = y_backup[m_complement]
return X_train, X_test, y_train, y_test
def ks(self, data, label, test_size=0.2):
"""Kennard-Stone算法划分数据集"""
# 确保 data 和 label 是 NumPy 数组
data = data.to_numpy() if isinstance(data, pd.DataFrame) else data
label = label.to_numpy() if isinstance(label, pd.Series) else label
M = data.shape[0]
N = round((1 - test_size) * M)
samples = np.arange(M)
D = np.zeros((M, M))
for i in range((M - 1)):
xa = data[i, :]
for j in range((i + 1), M):
xb = data[j, :]
D[i, j] = np.linalg.norm(xa - xb)
maxD = np.max(D, axis=0)
index_row = np.argmax(D, axis=0)
index_column = np.argmax(maxD)
m = np.zeros(N)
m[0] = np.array(index_row[index_column])
m[1] = np.array(index_column)
m = m.astype(int)
dminmax = np.zeros(N)
dminmax[1] = D[m[0], m[1]]
for i in range(2, N):
pool = np.delete(samples, m[:i])
dmin = np.zeros((M - i))
for j in range((M - i)):
indexa = pool[j]
d = np.zeros(i)
for k in range(i):
indexb = m[k]
if indexa < indexb:
d[k] = D[indexa, indexb]
else:
d[k] = D[indexb, indexa]
dmin[j] = np.min(d)
dminmax[i] = np.max(dmin)
index = np.argmax(dmin)
m[i] = pool[index]
m_complement = np.delete(np.arange(data.shape[0]), m)
X_train = data[m, :]
y_train = label[m]
X_test = data[m_complement, :]
y_test = label[m_complement]
return X_train, X_test, y_train, y_test
def split_data(self, X: np.ndarray, y: pd.Series, method: str = "random",
test_size: float = 0.2, random_state: int = 42) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
"""
根据指定方法划分数据集
"""
print(f"使用 {method} 方法划分数据集")
if method == "random":
return self.random(X, y, test_ratio=test_size, random_state=random_state)
elif method == "spxy":
return self.spxy(X, y, test_size=test_size)
elif method == "ks":
return self.ks(X, y, test_size=test_size)
else:
raise ValueError(f"不支持的划分方法: {method}. 支持的方法: {self.split_methods}")
def plot_scatter_with_confidence(self, y_train, y_pred_train, y_test, y_pred_test,
r2_train, mae_train, r2_test, mae_test,
folder_name, split_method, preprocess_method, model_name,
save_path):
"""
绘制带置信区间的散点图,模仿提供的代码样式
参数:
- y_train, y_pred_train: 训练集的真实值和预测值
- y_test, y_pred_test: 测试集的真实值和预测值
- r2_train, mae_train: 训练集的R²和MAE指标
- r2_test, mae_test: 测试集的R²和MAE指标
- folder_name: 文件夹名称
- split_method: 数据划分方法
- preprocess_method: 预处理方法
- model_name: 模型名称
- save_path: 保存路径
"""
# scale_factor 用于放大置信区间
scale_factor = 1.5 # 调整这个值,越大置信区间越宽 scale_factor = 1 是理论上的标准置信区间宽度
confidence = 0.95 # 95% 的置信水平
# 拟合训练集线
z_train = np.polyfit(y_train, y_pred_train, 1)
p_train = np.poly1d(z_train)
predicted_values_train = p_train(y_train)
residuals_train = y_pred_train - predicted_values_train
mean_error_train = np.mean(residuals_train**2)
t_value_train = stats.t.ppf((1 + confidence) / 2., len(y_train) - 1)
ci_train = t_value_train * scale_factor * np.sqrt(mean_error_train) * np.sqrt(1 / len(y_train) + (y_train - np.mean(y_train))**2 / np.sum((y_train - np.mean(y_train))**2))
x_extended_train = np.linspace(min(y_train), max(y_train), 100)
predicted_extended_train = p_train(x_extended_train)
ci_extended_train = t_value_train * scale_factor * np.sqrt(mean_error_train) * np.sqrt(1 / len(y_train) + (x_extended_train - np.mean(y_train))**2 / np.sum((y_train - np.mean(y_train))**2))
# 拟合测试集线
z_test = np.polyfit(y_test, y_pred_test, 1)
p_test = np.poly1d(z_test)
predicted_values_test = p_test(y_test)
residuals_test = y_pred_test - predicted_values_test
mean_error_test = np.mean(residuals_test**2)
t_value_test = stats.t.ppf((1 + confidence) / 2., len(y_test) - 1)
ci_test = t_value_test * scale_factor * np.sqrt(mean_error_test) * np.sqrt(1 / len(y_test) + (y_test - np.mean(y_test))**2 / np.sum((y_test - np.mean(y_test))**2))
x_extended_test = np.linspace(min(y_test), max(y_test), 100)
predicted_extended_test = p_test(x_extended_test)
ci_extended_test = t_value_test * scale_factor * np.sqrt(mean_error_test) * np.sqrt(1 / len(y_test) + (x_extended_test - np.mean(y_test))**2 / np.sum((y_test - np.mean(y_test))**2))
# 设置新的配色方案
train_color = '#1f77b4' # 训练集主色:蓝色系
test_color = '#ff7f0e' # 测试集主色:橙色系
confidence_train_color = '#aec7e8' # 训练集置信区间浅蓝色
confidence_test_color = '#ffbb78' # 测试集置信区间浅橙色
# 设置图形大小和分布
fig = plt.figure(figsize=(10, 8), dpi=300) # 降低dpi以提高兼容性
gs = fig.add_gridspec(4, 4, hspace=0.3, wspace=0.3)
ax_main = fig.add_subplot(gs[1:, :-1]) # 主图
ax_hist_x = fig.add_subplot(gs[0, :-1], sharex=ax_main) # 上方的直方图
ax_hist_y = fig.add_subplot(gs[1:, -1], sharey=ax_main) # 右侧的直方图
# 绘制训练集
ax_main.scatter(y_train, y_pred_train, color=train_color, label="训练集预测值", alpha=0.6)
ax_main.plot(y_train, p_train(y_train), color=train_color, alpha=0.9,
label=f"训练集拟合线\n$R^2$ = {r2_train:.2f}, MAE = {mae_train:.2f}")
ax_main.fill_between(x_extended_train, predicted_extended_train - ci_extended_train,
predicted_extended_train + ci_extended_train,
color=confidence_train_color, alpha=0.5, label="训练集95%置信区间")
# 绘制测试集
ax_main.scatter(y_test, y_pred_test, color=test_color, label="测试集预测值", alpha=0.6)
ax_main.plot(y_test, p_test(y_test), color=test_color, alpha=0.9,
label=f"测试集拟合线\n$R^2$ = {r2_test:.2f}, MAE = {mae_test:.2f}")
ax_main.fill_between(x_extended_test, predicted_extended_test - ci_extended_test,
predicted_extended_test + ci_extended_test,
color=confidence_test_color, alpha=0.5, label="测试集95%置信区间")
# 添加参考线
ax_main.plot([min(y_train.min(), y_test.min()), max(y_train.max(), y_test.max())],
[min(y_train.min(), y_test.min()), max(y_train.max(), y_test.max())],
color='grey', linestyle='--', alpha=0.6, label="1:1 参考线")
# 设置主图
ax_main.set_xlabel("观测值", fontsize=12)
ax_main.set_ylabel("预测值", fontsize=12)
ax_main.legend(loc="upper left", fontsize=10)
ax_main.grid(True, alpha=0.3)
# 绘制上方的直方图 (真实值的分布)
ax_hist_x.hist(y_train, bins=20, color=train_color, alpha=0.7, edgecolor='black', label="训练集观测值分布")
ax_hist_x.hist(y_test, bins=20, color=test_color, alpha=0.7, edgecolor='black', label="测试集观测值分布")
ax_hist_x.tick_params(labelbottom=False) # 隐藏 x 轴的标签
ax_hist_x.set_ylabel("频次", fontsize=10)
ax_hist_x.legend(fontsize=8)
# 绘制右侧的直方图 (预测值的分布)
ax_hist_y.hist(y_pred_train, bins=20, orientation='horizontal', color=train_color, alpha=0.7, edgecolor='black')
ax_hist_y.hist(y_pred_test, bins=20, orientation='horizontal', color=test_color, alpha=0.7, edgecolor='black')
ax_hist_y.set_xlabel("频次", fontsize=10)
ax_hist_y.tick_params(labelleft=False) # 隐藏 y 轴的标签
# 添加标题
title = f'{folder_name} - 最佳模型预测效果对比图\n'
title += f'{split_method}_{preprocess_method}_{model_name}'
fig.suptitle(title, fontsize=14, fontweight='bold')
# 保存和展示图像
plt.tight_layout()
plt.savefig(save_path, format='png', bbox_inches='tight', dpi=300)
print(f"散点图已保存至: {save_path}")
def get_best_model_from_summary(self, artifacts_dir: Path, metric: str = 'test_r2', target_column_name: str = None) -> Tuple[str, str, Dict]:
"""
从训练摘要中获取最佳模型信息
Args:
artifacts_dir: 模型目录
metric: 评估指标
target_column_name: 目标列名(用于构建文件路径)
Returns:
preprocess_method: 预处理方法
model_name: 模型名称
best_result: 最佳模型结果信息
"""
# 清理目标列名,移除可能的特殊字符
if target_column_name:
safe_target_name = "".join(c for c in target_column_name if c.isalnum() or c in ('-', '_')).rstrip()
# 尝试加载以目标列名为前缀的详细结果文件
detailed_path = artifacts_dir / f"{safe_target_name}_detailed_results.csv"
summary_path = artifacts_dir / f"{safe_target_name}_training_summary.csv"
else:
# 兼容旧版本,使用固定文件名
detailed_path = artifacts_dir / "detailed_results.csv"
summary_path = artifacts_dir / "training_summary.csv"
summary_df = None
# 优先使用详细结果文件
if detailed_path.exists():
print(f"使用详细结果文件: {detailed_path}")
summary_df = pd.read_csv(detailed_path)
# 将中文列名映射到英文
metric_mapping = {
'test_r2': '测试集R²',
'train_r2': '训练集R²',
'test_rmse': '测试集RMSE',
'train_rmse': '训练集RMSE',
'cv_mean': 'CV均值'
}
if metric in metric_mapping and metric_mapping[metric] in summary_df.columns:
metric_col = metric_mapping[metric]
else:
metric_col = metric
elif summary_path.exists():
print(f"使用训练摘要文件: {summary_path}")
summary_df = pd.read_csv(summary_path)
metric_col = metric
else:
# 如果使用了目标列名前缀的文件不存在,尝试查找旧版本的文件
if target_column_name:
old_detailed_path = artifacts_dir / "detailed_results.csv"
old_summary_path = artifacts_dir / "training_summary.csv"
if old_detailed_path.exists():
print(f"使用旧版本详细结果文件: {old_detailed_path}")
summary_df = pd.read_csv(old_detailed_path)
# 将中文列名映射到英文
metric_mapping = {
'test_r2': '测试集R²',
'train_r2': '训练集R²',
'test_rmse': '测试集RMSE',
'train_rmse': '训练集RMSE',
'cv_mean': 'CV均值'
}
if metric in metric_mapping and metric_mapping[metric] in summary_df.columns:
metric_col = metric_mapping[metric]
else:
metric_col = metric
elif old_summary_path.exists():
print(f"使用旧版本训练摘要文件: {old_summary_path}")
summary_df = pd.read_csv(old_summary_path)
metric_col = metric
else:
raise FileNotFoundError(f"训练摘要文件不存在: {summary_path}{detailed_path}{old_summary_path}{old_detailed_path}")
else:
raise FileNotFoundError(f"训练摘要文件不存在: {summary_path}{detailed_path}")
if summary_df.empty:
raise ValueError("训练摘要为空")
# 检查指标列是否存在
if metric_col not in summary_df.columns:
available_cols = list(summary_df.columns)
raise ValueError(f"指标 '{metric_col}' 不存在。可用列: {available_cols}")
# 获取最佳模型对于R²等指标值越大越好
if 'r2' in metric.lower() or 'score' in metric.lower():
best_idx = summary_df[metric_col].idxmax()
else: # 对于RMSE、MAE等值越小越好
best_idx = summary_df[metric_col].idxmin()
best_row = summary_df.loc[best_idx]
# 根据文件类型解析模型信息
if '划分方法' in summary_df.columns:
# 详细结果文件格式(中文列名)
split_method = best_row['划分方法']
preprocess_method = best_row['预处理方法']
model_name = best_row['建模方法']
best_combination = f"{split_method}_{preprocess_method}_{model_name}"
else:
# 简化结果文件格式(英文列名)
best_combination = best_row['combination']
# 解析组合名称(格式: split_method_preprocess_method_model_name
parts = best_combination.split('_')
if len(parts) < 3:
raise ValueError(f"无效的模型组合名称格式: {best_combination}")
split_method = parts[0]
preprocess_method = parts[1]
model_name = '_'.join(parts[2:])
print(f"最佳模型组合: {best_combination}")
print(f" 划分方法: {split_method}")
print(f" 预处理方法: {preprocess_method}")
print(f" 模型名称: {model_name}")
print(f" {metric_col}: {best_row[metric_col]:.4f}")
# 构建模型文件前缀
model_file_prefix = f"{split_method}_{preprocess_method}"
# 构建结果信息
best_result = {
'combination': best_combination,
'split_method': split_method,
'preprocess_method': preprocess_method,
'model_name': model_name,
'metric_value': best_row[metric_col],
'model_file_prefix': model_file_prefix
}
# 尝试获取更多指标信息
for col in summary_df.columns:
if col not in ['combination', '划分方法', '预处理方法', '建模方法', '最佳参数']:
try:
best_result[col] = best_row[col]
except:
pass
return model_file_prefix, model_name, best_result
def load_model(self, artifacts_dir: Path, preprocess_method: str, model_name: str, target_column_name: str = None):
"""
加载保存的模型
Args:
artifacts_dir: 模型目录
preprocess_method: 预处理方法名称
model_name: 模型名称
target_column_name: 目标列名(用于构建文件路径)
Returns:
加载的模型数据
"""
if target_column_name:
# 清理目标列名,移除可能的特殊字符
safe_target_name = "".join(c for c in target_column_name if c.isalnum() or c in ('-', '_')).rstrip()
# 尝试加载以目标列名为前缀的模型文件
filename = f"{safe_target_name}_{preprocess_method}_{model_name}.joblib"
filepath = artifacts_dir / filename
if filepath.exists():
print(f"加载模型文件: {filepath}")
return joblib.load(filepath)
# 如果带前缀的文件不存在,尝试加载旧版本的文件
old_filename = f"{preprocess_method}_{model_name}.joblib"
old_filepath = artifacts_dir / old_filename
if old_filepath.exists():
print(f"加载旧版本模型文件: {old_filepath}")
return joblib.load(old_filepath)
raise FileNotFoundError(f"模型文件不存在: {filepath}{old_filepath}")
else:
# 兼容旧版本,使用固定文件名
filename = f"{preprocess_method}_{model_name}.joblib"
filepath = artifacts_dir / filename
if not filepath.exists():
raise FileNotFoundError(f"模型文件不存在: {filepath}")
return joblib.load(filepath)
def plot_best_model_scatter(self, artifacts_dir: str, csv_path: str, output_dir: str,
folder_name: str, metric: str = 'test_r2',
target_column: int = None, feature_start_column: int = 13,
test_size: float = 0.2, random_state: int = 42):
"""
绘制最佳模型的散点图
Args:
artifacts_dir: 模型目录
csv_path: 原始CSV数据文件路径
output_dir: 输出目录
folder_name: 文件夹名称(用作图片名称和目标列名)
metric: 评估指标
target_column: 目标值列索引如果为None则使用folder_name作为列名
feature_start_column: 特征开始列索引
test_size: 测试集比例
random_state: 随机种子
"""
artifacts_path = Path(artifacts_dir)
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
try:
print(f"\n{'='*60}")
print(f"处理文件夹: {folder_name}")
print(f"{'='*60}")
# 获取最佳模型信息
model_file_prefix, model_name, best_result = self.get_best_model_from_summary(
artifacts_path, metric, folder_name
)
# 加载数据 - 优先使用文件夹名称作为目标列名
X_raw, y_true = self.load_data(csv_path, target_column_name=folder_name, target_column=target_column, feature_start_column=feature_start_column)
# 获取最佳模型的预处理方法
actual_preprocess_method = best_result['preprocess_method']
split_method = best_result['split_method']
# 加载最佳模型
best_model_data = self.load_model(artifacts_path, model_file_prefix, model_name, folder_name)
best_model = best_model_data['model']
# 应用相同的数据预处理
X_processed = self.preprocess_data(X_raw, actual_preprocess_method)
# 使用相同的数据分割方法
X_train, X_test, y_train, y_test = self.split_data(
X_processed, y_true, method=split_method,
test_size=test_size, random_state=random_state
)
# 预测训练集和测试集
y_pred_train = best_model.predict(X_train)
y_pred_test = best_model.predict(X_test)
# 计算评估指标
train_r2 = r2_score(y_train, y_pred_train)
test_r2 = r2_score(y_test, y_pred_test)
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
train_mae = mean_absolute_error(y_train, y_pred_train)
test_mae = mean_absolute_error(y_test, y_pred_test)
# 绘制带置信区间的散点图(模仿提供的代码样式)
self.plot_scatter_with_confidence(
y_train, y_pred_train, y_test, y_pred_test,
train_r2, train_mae, test_r2, test_mae,
folder_name, split_method, actual_preprocess_method, model_name,
output_path / f"{folder_name}_scatter_with_confidence.png"
)
plt.close() # 关闭图形以释放内存
return {
'status': 'success',
'save_path': str(output_path / f"{folder_name}_scatter_with_confidence.png"),
'best_result': best_result,
'metrics': {
'train_r2': train_r2,
'test_r2': test_r2,
'train_rmse': train_rmse,
'test_rmse': test_rmse,
'train_mae': train_mae,
'test_mae': test_mae
}
}
except Exception as e:
print(f"处理文件夹 {folder_name} 失败: {e}")
return {
'status': 'error',
'error': str(e)
}
def batch_plot_scatter(self, models_root_dir: str, csv_path: str, output_dir: str,
metric: str = 'test_r2', target_column: int = None,
feature_start_column: int = 13, test_size: float = 0.2,
random_state: int = 42):
"""
批量处理多个子文件夹中的模型并绘制散点图
Args:
models_root_dir: 包含多个子文件夹的根目录
csv_path: 原始CSV数据文件路径
output_dir: 输出目录
metric: 评估指标
target_column: 目标值列索引如果为None则使用文件夹名称作为列名
feature_start_column: 特征开始列索引
test_size: 测试集比例
random_state: 随机种子
"""
models_root = Path(models_root_dir)
# 查找所有子文件夹
subdirs = [d for d in models_root.iterdir() if d.is_dir()]
if not subdirs:
print(f"在目录 {models_root_dir} 中未找到子文件夹")
return {}
print("=" * 80)
print("批量散点图绘制任务")
print("=" * 80)
print(f"模型根目录: {models_root_dir}")
print(f"数据文件: {csv_path}")
print(f"输出目录: {output_dir}")
print(f"评估指标: {metric}")
print(f"找到 {len(subdirs)} 个模型子文件夹")
print("=" * 80)
all_results = {}
for subdir in subdirs:
folder_name = subdir.name
result = self.plot_best_model_scatter(
artifacts_dir=str(subdir),
csv_path=csv_path,
output_dir=output_dir,
folder_name=folder_name,
metric=metric,
target_column=target_column,
feature_start_column=feature_start_column,
test_size=test_size,
random_state=random_state
)
all_results[folder_name] = result
print(f"\n{'='*80}")
print(f"批量散点图绘制完成,共处理 {len(subdirs)} 个模型文件夹")
print(f"{'='*80}")
# 打印汇总信息
print("\n汇总结果:")
success_count = 0
for folder_name, result in all_results.items():
if result['status'] == 'success':
metrics = result['metrics']
print(f"{folder_name}: 测试集R²={metrics['test_r2']:.4f}, "
f"RMSE={metrics['test_rmse']:.4f}")
success_count += 1
else:
print(f"{folder_name}: 失败 - {result['error']}")
print(f"\n成功处理: {success_count}/{len(subdirs)} 个文件夹")
print(f"输出目录: {output_dir}")
return all_results
def main():
"""主函数示例"""
# 创建批量散点图绘制实例
scatter_batch = WaterQualityScatterBatch()
# 配置路径
models_root_dir = r"E:\code\WQ\yaobao925\qvchuyaoban" # 包含多个子文件夹的根目录
csv_path = r"E:\code\WQ\yaobao925\data\qvyaoban\data.csv" # 原始数据文件
output_dir = r"E:\code\WQ\yaobao925\plot\qvyaoban_sctter" # 散点图输出目录
# 批量绘制散点图
results = scatter_batch.batch_plot_scatter(
models_root_dir=models_root_dir,
csv_path=csv_path,
output_dir=output_dir,
metric='test_r2', # 评估指标
target_column=None, # 使用文件夹名称作为目标列名
feature_start_column=13, # 特征开始列索引
test_size=0.2, # 测试集比例
random_state=42 # 随机种子
)
print("\n任务完成!")
if __name__ == "__main__":
main()

22
src/core/type_define.py Normal file
View File

@ -0,0 +1,22 @@
from enum import Enum, unique
class FlareModel(Enum):
otsu = 0
threshold = 1
img = 2
class ImgType(Enum):
ref = 0
content = 1
# @unique
class CoorType(Enum):
latlong = 0
utm = 1
class PointPosStrategy(Enum):
nearest_single = 0
four_quadrant = 1

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

242
src/gui/STYLES_README.md Normal file
View File

@ -0,0 +1,242 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
水质参数反演分析系统 - UI样式系统文档
Modern UI Stylesheet System Documentation
================================================================================
1. 概述
------
本系统采用现代化的扁平设计风格,提供了一套完整的样式表和主题管理系统。
所有UI组件都遵循统一的设计规范确保整体的视觉一致性和用户体验。
2. 颜色系统
-----------
主要颜色定义在 ModernStylesheet.COLORS 中:
- main_bg (#F0F0F0):主窗口背景,浅灰色
- panel_bg (#FFFFFF):面板/容器背景,纯白色
- text_primary (#000000):主文字颜色,黑色
- text_secondary (#666666):辅助文字颜色,灰色
- border (#D0D0D0):边框颜色,浅灰
- border_light (#E8E8E8):浅边框颜色
- accent (#007BFF):强调色,蓝色
- success (#28A745):成功绿
- error (#DC3545):错误红
- warning (#FFC107):警告黄
- hover (#E8E8E8):悬停背景色
- selected (#0056B3):选中色
3. 按钮样式
-----------
系统提供了四种预定义的按钮样式:
a) 普通按钮normal
background-color: 白色
border: 1px 灰色边框
border-radius: 7px
用法: ModernStylesheet.get_button_stylesheet('normal')
b) 主按钮primary- 蓝色
background-color: 蓝色 (#007BFF)
color: 白色
border-radius: 7px
用法: ModernStylesheet.get_button_stylesheet('primary')
c) 成功按钮success- 绿色
background-color: 绿色 (#28A745)
color: 白色
border-radius: 7px
用法: ModernStylesheet.get_button_stylesheet('success')
常用于:独立运行、确认操作等
d) 危险按钮danger- 红色
background-color: 红色 (#DC3545)
color: 白色
border-radius: 7px
用法: ModernStylesheet.get_button_stylesheet('danger')
常用于:停止、删除操作等
示例代码:
---------
from src.gui.styles import ModernStylesheet
# 创建成功按钮
run_btn = QPushButton("运行")
run_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('success'))
# 创建危险按钮
stop_btn = QPushButton("停止")
stop_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('danger'))
4. 输入框样式
-------------
所有输入框QLineEdit, QComboBox, QSpinBox等都采用统一样式
- background-color: 白色
- border: 1px 灰色边框
- border-radius: 5-10px圆角
- 焦点时边框变为蓝色accent color
5. 分组框QGroupBox
----------------------
分组框采用简洁的设计:
- background-color: 白色
- border: 无or仅下边框1px 浅灰)
- border-radius: 0
- 内边距: 9px
6. 复选框和单选框
-----------------
复选框和单选框保持默认样式,具有以下特性:
- 大小: 16x16px
- 选中时蓝色背景accent color
- 边框: 1px 灰色
7. 应用样式表
--------------
主样式表通过 apply_stylesheet() 方法应用到整个应用:
self.setStyleSheet(ModernStylesheet.get_main_stylesheet())
专用样式表可按需应用:
# 工具栏样式
toolbar_widget.setStyleSheet(ModernStylesheet.get_toolbar_stylesheet())
# 边栏样式
sidebar_widget.setStyleSheet(ModernStylesheet.get_sidebar_stylesheet())
8. 布局特点
-----------
应用的主要布局特点:
a) 顶部工具栏
- 白色背景
- 下边框 1px 浅灰
- 包含logo、模块切换按钮、窗口控制按钮
b) 左侧导航栏(宽度:~280px
- 白色背景
- 右边框 1px 浅灰
- 包含步骤列表、运行/停止按钮
- 步骤列表支持选中状态显示
c) 右侧内容区
- 浅灰背景
- 包含标签页、日志区、进度条
- 标签页支持切换
d) 底部状态栏
- 白色背景
- 上边框 1px 浅灰
- 显示当前状态和进度信息
9. 自定义样式
--------------
如需为某个组件应用自定义样式,建议:
a) 简单修改(如颜色):
widget.setStyleSheet(f"background-color: {ModernStylesheet.COLORS['panel_bg']};")
b) 复杂修改:
添加新方法到 ModernStylesheet 类
@staticmethod
def get_custom_stylesheet():
return "..."
c) 一次性样式:
直接在widget.setStyleSheet中定义
注意:保持与整体风格的一致性
10. 最佳实践
--------------
✓ DO:
- 使用 ModernStylesheet 中的颜色常量
- 使用预定义的样式表方法
- 维持一致的间距和圆角半径
- 使用适当的按钮类型success/danger等
- 为交互元素提供hover和pressed状态反馈
✗ DON'T:
- 直接使用硬编码颜色值
- 混合不同的设计风格
- 过度使用渐变和阴影
- 忽视focus和disabled状态
- 使用过小或过大的字体
11. 响应式设计
---------------
系统支持基本的响应式布局:
- 导航栏最大宽度: 280px
- 内容区域自动调整
- 步骤面板自动滚动
12. 字体设置
-----------
主要字体:
- 界面标题Arial, 13-14pt, Bold
- 普通文本:系统默认, 11-12pt
- 等宽字体日志Courier New, 10pt, Monospace
13. 性能优化
-----------
- 样式表在应用启动时加载
- 避免频繁修改样式表
- 使用CSS类而非硬编码样式
- 合理使用selector优化渲染
14. 常见问题
-----------
Q: 如何改变全局字体大小?
A: 修改 ModernStylesheet 类中的样式表定义
Q: 如何添加新的按钮类型?
A: 在 ModernStylesheet 类中添加新方法
Q: 如何支持深色模式?
A: 创建新的样式类,定义深色配色方案
Q: 标签页标签栏消失了怎么办?
A: 检查QTabBar::tab的height设置确保高度大于0
================================================================================
更新历史:
- v1.0: 初始版本,实现现代化扁平设计风格
- v1.1: 改进导航栏和日志区域样式
- v1.2: 添加专用样式表方法(工具栏、边栏等)
================================================================================
"""
# 快速参考表
QUICK_REFERENCE = """
┌─ 快速参考 ─────────────────────────────────────────────────────────────┐
│ │
│ 按钮样式: │
│ 正常: ModernStylesheet.get_button_stylesheet('normal') │
│ 主要: ModernStylesheet.get_button_stylesheet('primary') │
│ 成功: ModernStylesheet.get_button_stylesheet('success') │
│ 危险: ModernStylesheet.get_button_stylesheet('danger') │
│ │
│ 颜色引用: │
│ ModernStylesheet.COLORS['main_bg'] # #F0F0F0 浅灰 │
│ ModernStylesheet.COLORS['panel_bg'] # #FFFFFF 白色 │
│ ModernStylesheet.COLORS['text_primary'] # #000000 黑色 │
│ ModernStylesheet.COLORS['accent'] # #007BFF 蓝色 │
│ ModernStylesheet.COLORS['success'] # #28A745 绿色 │
│ ModernStylesheet.COLORS['error'] # #DC3545 红色 │
│ │
│ 样式表应用: │
│ self.setStyleSheet(ModernStylesheet.get_main_stylesheet()) │
│ widget.setStyleSheet(ModernStylesheet.get_toolbar_stylesheet()) │
│ widget.setStyleSheet(ModernStylesheet.get_sidebar_stylesheet()) │
│ │
└───────────────────────────────────────────────────────────────────────┘
"""
if __name__ == "__main__":
print(QUICK_REFERENCE)

1
src/gui/__init__.py Normal file
View File

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

570
src/gui/styles.py Normal file
View File

@ -0,0 +1,570 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
现代化样式表和主题管理模块
Modern Stylesheet and Theme Management Module
"""
class ModernStylesheet:
"""现代化样式表集合"""
# 颜色定义
COLORS = {
'main_bg': '#F0F0F0', # 主窗口背景:浅灰
'panel_bg': '#FFFFFF', # 面板/容器背景:白色
'text_primary': '#000000', # 主文字:黑色
'text_secondary': '#666666', # 辅助文字:灰色
'border': '#D0D0D0', # 边框:浅灰
'border_light': '#E8E8E8', # 浅边框
'accent': '#007BFF', # 强调色:蓝色
'success': '#28A745', # 成功绿
'error': '#DC3545', # 错误红
'warning': '#FFC107', # 警告黄
'hover': '#E8E8E8', # 悬停背景
'selected': '#0056B3', # 选中色
}
@staticmethod
def get_main_stylesheet():
"""获取主样式表"""
return f"""
/* 主窗口 */
QMainWindow {{
background-color: {ModernStylesheet.COLORS['main_bg']};
}}
/* 中央部件和容器 */
QWidget {{
background-color: {ModernStylesheet.COLORS['main_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
}}
/* 分组框 */
QGroupBox {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
font-weight: bold;
border: 0px;
margin-top: 10px;
padding-top: 15px;
padding-left: 9px;
padding-right: 9px;
padding-bottom: 9px;
border-bottom: 1px solid {ModernStylesheet.COLORS['border_light']};
}}
QGroupBox::title {{
subcontrol-origin: margin;
subcontrol-position: top left;
padding: 0 5px;
font-size: 12px;
font-weight: bold;
color: {ModernStylesheet.COLORS['text_primary']};
}}
/* 按钮 */
QPushButton {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
border: 1px solid {ModernStylesheet.COLORS['border']};
border-radius: 7px;
padding: 3px 5px;
min-height: 25px;
max-height: 33px;
font-size: 12px;
font-weight: normal;
outline: none;
}}
QPushButton:hover {{
background-color: {ModernStylesheet.COLORS['hover']};
border: 1px solid {ModernStylesheet.COLORS['border']};
}}
QPushButton:pressed {{
background-color: {ModernStylesheet.COLORS['border_light']};
}}
QPushButton:disabled {{
background-color: {ModernStylesheet.COLORS['hover']};
color: {ModernStylesheet.COLORS['text_secondary']};
border: 1px solid {ModernStylesheet.COLORS['border_light']};
}}
QPushButton:focus {{
outline: none;
}}
/* 输入框 */
QLineEdit {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
border: 1px solid {ModernStylesheet.COLORS['border']};
border-radius: 10px;
padding: 5px 8px;
min-height: 20px;
selection-background-color: {ModernStylesheet.COLORS['selected']};
selection-color: white;
}}
QLineEdit:focus {{
border: 1px solid {ModernStylesheet.COLORS['accent']};
background-color: {ModernStylesheet.COLORS['panel_bg']};
}}
/* 下拉框 */
QComboBox {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
border: 1px solid {ModernStylesheet.COLORS['border']};
border-radius: 5px;
padding: 5px 8px;
min-height: 25px;
selection-background-color: {ModernStylesheet.COLORS['selected']};
}}
QComboBox:focus {{
border: 1px solid {ModernStylesheet.COLORS['accent']};
}}
QComboBox::drop-down {{
border: 0px;
padding-right: 5px;
}}
QComboBox QAbstractItemView {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
selection-background-color: {ModernStylesheet.COLORS['selected']};
selection-color: white;
border: 1px solid {ModernStylesheet.COLORS['border']};
}}
/* 数值输入框 */
QSpinBox, QDoubleSpinBox {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
border: 1px solid {ModernStylesheet.COLORS['border']};
border-radius: 5px;
padding: 5px 8px;
min-height: 25px;
}}
QSpinBox:focus, QDoubleSpinBox:focus {{
border: 1px solid {ModernStylesheet.COLORS['accent']};
}}
QSpinBox::up-button, QDoubleSpinBox::up-button {{
border: 0px;
padding-right: 5px;
}}
QSpinBox::down-button, QDoubleSpinBox::down-button {{
border: 0px;
padding-right: 5px;
}}
/* 复选框 */
QCheckBox {{
color: {ModernStylesheet.COLORS['text_primary']};
spacing: 5px;
}}
QCheckBox::indicator {{
width: 16px;
height: 16px;
border: 1px solid {ModernStylesheet.COLORS['border']};
border-radius: 3px;
background-color: {ModernStylesheet.COLORS['panel_bg']};
}}
QCheckBox::indicator:checked {{
background-color: {ModernStylesheet.COLORS['accent']};
border: 1px solid {ModernStylesheet.COLORS['accent']};
}}
/* 单选框 */
QRadioButton {{
color: {ModernStylesheet.COLORS['text_primary']};
spacing: 5px;
}}
QRadioButton::indicator {{
width: 16px;
height: 16px;
border: 1px solid {ModernStylesheet.COLORS['border']};
border-radius: 8px;
background-color: {ModernStylesheet.COLORS['panel_bg']};
}}
QRadioButton::indicator:checked {{
background: qradial(circle, {ModernStylesheet.COLORS['accent']} 0%, {ModernStylesheet.COLORS['accent']} 40%, {ModernStylesheet.COLORS['panel_bg']} 60%);
border: 1px solid {ModernStylesheet.COLORS['accent']};
}}
/* 文本编辑框 */
QTextEdit {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
border: 1px solid {ModernStylesheet.COLORS['border']};
border-radius: 5px;
padding: 5px;
selection-background-color: {ModernStylesheet.COLORS['selected']};
selection-color: white;
}}
QTextEdit:focus {{
border: 1px solid {ModernStylesheet.COLORS['accent']};
}}
/* 列表部件 */
QListWidget {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
border: 1px solid {ModernStylesheet.COLORS['border']};
border-radius: 5px;
outline: none;
}}
QListWidget::item {{
padding: 6px;
border: 0px;
}}
QListWidget::item:hover {{
background-color: {ModernStylesheet.COLORS['hover']};
}}
QListWidget::item:selected {{
background-color: {ModernStylesheet.COLORS['selected']};
color: white;
}}
/* 滚动区域 */
QScrollArea {{
background-color: {ModernStylesheet.COLORS['main_bg']};
border: 0px;
}}
/* 滚动条 */
QScrollBar:vertical {{
background-color: {ModernStylesheet.COLORS['main_bg']};
width: 12px;
border: 0px;
}}
QScrollBar::handle:vertical {{
background-color: {ModernStylesheet.COLORS['border']};
border-radius: 6px;
min-height: 20px;
}}
QScrollBar::handle:vertical:hover {{
background-color: {ModernStylesheet.COLORS['text_secondary']};
}}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{
border: 0px;
background-color: transparent;
}}
QScrollBar:horizontal {{
background-color: {ModernStylesheet.COLORS['main_bg']};
height: 12px;
border: 0px;
}}
QScrollBar::handle:horizontal {{
background-color: {ModernStylesheet.COLORS['border']};
border-radius: 6px;
min-width: 20px;
}}
QScrollBar::handle:horizontal:hover {{
background-color: {ModernStylesheet.COLORS['text_secondary']};
}}
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {{
border: 0px;
background-color: transparent;
}}
/* 进度条 */
QProgressBar {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
border: 1px solid {ModernStylesheet.COLORS['border']};
border-radius: 5px;
padding: 2px;
text-align: center;
height: 20px;
}}
QProgressBar::chunk {{
background-color: {ModernStylesheet.COLORS['success']};
border-radius: 3px;
}}
/* 标签 */
QLabel {{
color: {ModernStylesheet.COLORS['text_primary']};
background-color: transparent;
}}
/* 标签栏 */
QTabBar::tab {{
background-color: {ModernStylesheet.COLORS['main_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
border: 1px solid {ModernStylesheet.COLORS['border']};
border-bottom: 0px;
padding: 8px 12px;
margin-right: 2px;
border-radius: 5px 5px 0px 0px;
}}
QTabBar::tab:selected {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
border: 1px solid {ModernStylesheet.COLORS['border']};
border-bottom: 2px solid {ModernStylesheet.COLORS['accent']};
color: {ModernStylesheet.COLORS['accent']};
}}
QTabBar::tab:hover {{
background-color: {ModernStylesheet.COLORS['hover']};
}}
QTabWidget::pane {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
border: 1px solid {ModernStylesheet.COLORS['border']};
border-top: 0px;
border-radius: 0px 0px 5px 5px;
}}
/* 菜单栏 */
QMenuBar {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
border-bottom: 1px solid {ModernStylesheet.COLORS['border_light']};
padding: 2px;
}}
QMenuBar::item:selected {{
background-color: {ModernStylesheet.COLORS['hover']};
}}
/* 菜单 */
QMenu {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
border: 1px solid {ModernStylesheet.COLORS['border']};
padding: 4px 0px;
border-radius: 5px;
}}
QMenu::item:selected {{
background-color: {ModernStylesheet.COLORS['hover']};
padding-left: 20px;
}}
QMenu::separator {{
height: 1px;
background-color: {ModernStylesheet.COLORS['border_light']};
margin: 4px 0px;
}}
/* 状态栏 */
QStatusBar {{
background-color: {ModernStylesheet.COLORS['panel_bg']};
color: {ModernStylesheet.COLORS['text_primary']};
border-top: 1px solid {ModernStylesheet.COLORS['border_light']};
}}
/* 框架 */
QFrame {{
background-color: {ModernStylesheet.COLORS['main_bg']};
border: 0px;
}}
/* 对话框 */
QDialog {{
background-color: {ModernStylesheet.COLORS['main_bg']};
}}
/* 消息框 */
QMessageBox {{
background-color: {ModernStylesheet.COLORS['main_bg']};
}}
QMessageBox QLabel {{
color: {ModernStylesheet.COLORS['text_primary']};
}}
"""
@staticmethod
def get_button_stylesheet(style_type='normal'):
"""获取特定样式的按钮样式表"""
colors = ModernStylesheet.COLORS
if style_type == 'primary':
# 蓝色主按钮
return f"""
QPushButton {{
background-color: {colors['accent']};
color: white;
border: 1px solid {colors['accent']};
border-radius: 7px;
padding: 3px 5px;
min-height: 25px;
max-height: 33px;
font-weight: bold;
}}
QPushButton:hover {{
background-color: #0056b3;
border: 1px solid #0056b3;
}}
QPushButton:pressed {{
background-color: #003d82;
}}
QPushButton:disabled {{
background-color: {colors['hover']};
color: {colors['text_secondary']};
border: 1px solid {colors['border_light']};
}}
"""
elif style_type == 'success':
# 绿色成功按钮
return f"""
QPushButton {{
background-color: {colors['success']};
color: white;
border: 1px solid {colors['success']};
border-radius: 7px;
padding: 3px 5px;
min-height: 25px;
max-height: 33px;
font-weight: bold;
}}
QPushButton:hover {{
background-color: #218838;
border: 1px solid #218838;
}}
QPushButton:pressed {{
background-color: #1a6c28;
}}
QPushButton:disabled {{
background-color: {colors['hover']};
color: {colors['text_secondary']};
border: 1px solid {colors['border_light']};
}}
"""
elif style_type == 'danger':
# 红色危险按钮
return f"""
QPushButton {{
background-color: {colors['error']};
color: white;
border: 1px solid {colors['error']};
border-radius: 7px;
padding: 3px 5px;
min-height: 25px;
max-height: 33px;
font-weight: bold;
}}
QPushButton:hover {{
background-color: #c82333;
border: 1px solid #c82333;
}}
QPushButton:pressed {{
background-color: #9a1a24;
}}
QPushButton:disabled {{
background-color: {colors['hover']};
color: {colors['text_secondary']};
border: 1px solid {colors['border_light']};
}}
"""
else: # normal/default
return f"""
QPushButton {{
background-color: {colors['panel_bg']};
color: {colors['text_primary']};
border: 1px solid {colors['border']};
border-radius: 7px;
padding: 3px 5px;
min-height: 25px;
max-height: 33px;
}}
QPushButton:hover {{
background-color: {colors['hover']};
border: 1px solid {colors['border']};
}}
QPushButton:pressed {{
background-color: {colors['border_light']};
}}
QPushButton:disabled {{
background-color: {colors['hover']};
color: {colors['text_secondary']};
border: 1px solid {colors['border_light']};
}}
"""
@staticmethod
def get_toolbar_stylesheet():
"""获取顶部工具栏样式表"""
colors = ModernStylesheet.COLORS
return f"""
QWidget {{
background-color: {colors['panel_bg']};
border-bottom: 1px solid {colors['border_light']};
}}
QLabel {{
color: {colors['text_primary']};
}}
QPushButton {{
background-color: {colors['panel_bg']};
color: {colors['text_primary']};
border: 1px solid {colors['border']};
border-radius: 5px;
padding: 5px 10px;
min-height: 25px;
}}
QPushButton:hover {{
background-color: {colors['hover']};
}}
"""
@staticmethod
def get_sidebar_stylesheet():
"""获取左侧边栏样式表"""
colors = ModernStylesheet.COLORS
return f"""
QWidget {{
background-color: {colors['panel_bg']};
border-right: 1px solid {colors['border_light']};
}}
QLabel {{
color: {colors['text_primary']};
font-weight: bold;
}}
QListWidget {{
background-color: {colors['panel_bg']};
border: 0px;
border-right: 1px solid {colors['border_light']};
}}
QListWidget::item {{
padding: 8px;
border-left: 3px solid transparent;
}}
QListWidget::item:hover {{
background-color: {colors['hover']};
}}
QListWidget::item:selected {{
background-color: transparent;
color: {colors['accent']};
border-left: 3px solid {colors['accent']};
font-weight: bold;
}}
"""

6300
src/gui/water_quality_gui.py Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,15 @@
ENVI
description = {
work_dir\2_glint\severe_glint_area.dat}
samples = 11363
lines = 10408
bands = 1
header offset = 0
file type = ENVI Standard
data type = 4
interleave = bsq
byte order = 0
map info = {UTM, 1, 1, 600742.055, 4613386.65, 0.2, 0.2, 51, North,WGS-84}
coordinate system string = {PROJCS["WGS_1984_UTM_Zone_51N",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",123.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]]}
band names = {
Band 1}

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,1002.515991,0.11209397634185636,y = -0.005956 + 0.001186*x,134,10.960663313432837,3.9096921347220377,0.007041335820895523,0.0138473135041692
logarithmic,Chlorophyll,1002.515991,0.09022914646608904,y = -0.019813 + 0.011526*ln(x),134,10.960663313432837,3.9096921347220377,0.007041335820895523,0.0138473135041692
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 1002.515991 0.11209397634185636 y = -0.005956 + 0.001186*x 134 10.960663313432837 3.9096921347220377 0.007041335820895523 0.0138473135041692
3 logarithmic Chlorophyll 1002.515991 0.09022914646608904 y = -0.019813 + 0.011526*ln(x) 134 10.960663313432837 3.9096921347220377 0.007041335820895523 0.0138473135041692

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,1007.041016,0.13129585788396014,y = -0.007873 + 0.001537*x,134,10.960663313432837,3.9096921347220377,0.008974216417910446,0.016584272713125302
logarithmic,Chlorophyll,1007.041016,0.10398887849221805,y = -0.025553 + 0.014819*ln(x),134,10.960663313432837,3.9096921347220377,0.008974216417910446,0.016584272713125302
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 1007.041016 0.13129585788396014 y = -0.007873 + 0.001537*x 134 10.960663313432837 3.9096921347220377 0.008974216417910446 0.016584272713125302
3 logarithmic Chlorophyll 1007.041016 0.10398887849221805 y = -0.025553 + 0.014819*ln(x) 134 10.960663313432837 3.9096921347220377 0.008974216417910446 0.016584272713125302

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,1011.56897,0.11897246869922418,y = -0.007866 + 0.001621*x,134,10.960663313432837,3.9096921347220377,0.00990283582089552,0.01837518499092342
logarithmic,Chlorophyll,1011.56897,0.09605697495450882,y = -0.026865 + 0.015780*ln(x),134,10.960663313432837,3.9096921347220377,0.00990283582089552,0.01837518499092342
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 1011.56897 0.11897246869922418 y = -0.007866 + 0.001621*x 134 10.960663313432837 3.9096921347220377 0.00990283582089552 0.01837518499092342
3 logarithmic Chlorophyll 1011.56897 0.09605697495450882 y = -0.026865 + 0.015780*ln(x) 134 10.960663313432837 3.9096921347220377 0.00990283582089552 0.01837518499092342

View File

@ -0,0 +1,5 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,374.285004,0.0577461915301245,y = 0.009707 + 0.000311*x,134,10.960663313432837,3.9096921347220377,0.013112298507462688,0.005054260878733534
logarithmic,Chlorophyll,374.285004,0.052490162787109385,y = 0.005636 + 0.003209*ln(x),134,10.960663313432837,3.9096921347220377,0.013112298507462688,0.005054260878733534
exponential,Chlorophyll,374.285004,0.030557192829324564,y = 0.010822 * exp(0.013060*x),134,10.960663313432837,3.9096921347220377,0.013112298507462688,0.005054260878733534
power,Chlorophyll,374.285004,0.02576326804736484,y = 0.009209 * x^0.130700,134,10.960663313432837,3.9096921347220377,0.013112298507462688,0.005054260878733534
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 374.285004 0.0577461915301245 y = 0.009707 + 0.000311*x 134 10.960663313432837 3.9096921347220377 0.013112298507462688 0.005054260878733534
3 logarithmic Chlorophyll 374.285004 0.052490162787109385 y = 0.005636 + 0.003209*ln(x) 134 10.960663313432837 3.9096921347220377 0.013112298507462688 0.005054260878733534
4 exponential Chlorophyll 374.285004 0.030557192829324564 y = 0.010822 * exp(0.013060*x) 134 10.960663313432837 3.9096921347220377 0.013112298507462688 0.005054260878733534
5 power Chlorophyll 374.285004 0.02576326804736484 y = 0.009209 * x^0.130700 134 10.960663313432837 3.9096921347220377 0.013112298507462688 0.005054260878733534

View File

@ -0,0 +1,5 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
logarithmic,Chlorophyll,378.311005,0.008061439581006025,y = 0.013092 + 0.001044*ln(x),134,10.960663313432837,3.9096921347220377,0.01552370895522388,0.00419444858565235
linear,Chlorophyll,378.311005,0.008052879252108514,y = 0.014468 + 0.000096*x,134,10.960663313432837,3.9096921347220377,0.01552370895522388,0.00419444858565235
power,Chlorophyll,378.311005,-0.016155019039159058,y = 0.015641 * x^-0.016124,134,10.960663313432837,3.9096921347220377,0.01552370895522388,0.00419444858565235
exponential,Chlorophyll,378.311005,-0.01708357282563666,y = 0.015362 * exp(-0.001784*x),134,10.960663313432837,3.9096921347220377,0.01552370895522388,0.00419444858565235
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 logarithmic Chlorophyll 378.311005 0.008061439581006025 y = 0.013092 + 0.001044*ln(x) 134 10.960663313432837 3.9096921347220377 0.01552370895522388 0.00419444858565235
3 linear Chlorophyll 378.311005 0.008052879252108514 y = 0.014468 + 0.000096*x 134 10.960663313432837 3.9096921347220377 0.01552370895522388 0.00419444858565235
4 power Chlorophyll 378.311005 -0.016155019039159058 y = 0.015641 * x^-0.016124 134 10.960663313432837 3.9096921347220377 0.01552370895522388 0.00419444858565235
5 exponential Chlorophyll 378.311005 -0.01708357282563666 y = 0.015362 * exp(-0.001784*x) 134 10.960663313432837 3.9096921347220377 0.01552370895522388 0.00419444858565235

View File

@ -0,0 +1,5 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,382.341003,0.010983531384569756,y = 0.013856 + 0.000202*x,134,10.960663313432837,3.9096921347220377,0.016074067164179102,0.007548431031201279
logarithmic,Chlorophyll,382.341003,0.010636805221273526,y = 0.011048 + 0.002157*ln(x),134,10.960663313432837,3.9096921347220377,0.016074067164179102,0.007548431031201279
power,Chlorophyll,382.341003,-0.007234268459601845,y = 0.015174 * x^0.006143,134,10.960663313432837,3.9096921347220377,0.016074067164179102,0.007548431031201279
exponential,Chlorophyll,382.341003,-0.008026222697967267,y = 0.015380 * exp(0.000073*x),134,10.960663313432837,3.9096921347220377,0.016074067164179102,0.007548431031201279
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 382.341003 0.010983531384569756 y = 0.013856 + 0.000202*x 134 10.960663313432837 3.9096921347220377 0.016074067164179102 0.007548431031201279
3 logarithmic Chlorophyll 382.341003 0.010636805221273526 y = 0.011048 + 0.002157*ln(x) 134 10.960663313432837 3.9096921347220377 0.016074067164179102 0.007548431031201279
4 power Chlorophyll 382.341003 -0.007234268459601845 y = 0.015174 * x^0.006143 134 10.960663313432837 3.9096921347220377 0.016074067164179102 0.007548431031201279
5 exponential Chlorophyll 382.341003 -0.008026222697967267 y = 0.015380 * exp(0.000073*x) 134 10.960663313432837 3.9096921347220377 0.016074067164179102 0.007548431031201279

View File

@ -0,0 +1,5 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
logarithmic,Chlorophyll,386.373993,0.004476522091747537,y = 0.013943 + 0.001356*ln(x),134,10.960663313432837,3.9096921347220377,0.017102343283582087,0.007312955327938564
linear,Chlorophyll,386.373993,0.003937809502393641,y = 0.015816 + 0.000117*x,134,10.960663313432837,3.9096921347220377,0.017102343283582087,0.007312955327938564
power,Chlorophyll,386.373993,-0.013451645087448227,y = 0.018099 * x^-0.040970,134,10.960663313432837,3.9096921347220377,0.017102343283582087,0.007312955327938564
exponential,Chlorophyll,386.373993,-0.01526209268366463,y = 0.017374 * exp(-0.004977*x),134,10.960663313432837,3.9096921347220377,0.017102343283582087,0.007312955327938564
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 logarithmic Chlorophyll 386.373993 0.004476522091747537 y = 0.013943 + 0.001356*ln(x) 134 10.960663313432837 3.9096921347220377 0.017102343283582087 0.007312955327938564
3 linear Chlorophyll 386.373993 0.003937809502393641 y = 0.015816 + 0.000117*x 134 10.960663313432837 3.9096921347220377 0.017102343283582087 0.007312955327938564
4 power Chlorophyll 386.373993 -0.013451645087448227 y = 0.018099 * x^-0.040970 134 10.960663313432837 3.9096921347220377 0.017102343283582087 0.007312955327938564
5 exponential Chlorophyll 386.373993 -0.01526209268366463 y = 0.017374 * exp(-0.004977*x) 134 10.960663313432837 3.9096921347220377 0.017102343283582087 0.007312955327938564

View File

@ -0,0 +1,5 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
logarithmic,Chlorophyll,390.410004,0.0033869580773776553,y = 0.014722 + 0.001210*ln(x),134,10.960663313432837,3.9096921347220377,0.017540880597014925,0.00750323608895778
linear,Chlorophyll,390.410004,0.0026484411391527463,y = 0.016458 + 0.000099*x,134,10.960663313432837,3.9096921347220377,0.017540880597014925,0.00750323608895778
power,Chlorophyll,390.410004,-0.01625121404032659,y = 0.019411 * x^-0.060440,134,10.960663313432837,3.9096921347220377,0.017540880597014925,0.00750323608895778
exponential,Chlorophyll,390.410004,-0.018540174411018073,y = 0.018243 * exp(-0.007186*x),134,10.960663313432837,3.9096921347220377,0.017540880597014925,0.00750323608895778
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 logarithmic Chlorophyll 390.410004 0.0033869580773776553 y = 0.014722 + 0.001210*ln(x) 134 10.960663313432837 3.9096921347220377 0.017540880597014925 0.00750323608895778
3 linear Chlorophyll 390.410004 0.0026484411391527463 y = 0.016458 + 0.000099*x 134 10.960663313432837 3.9096921347220377 0.017540880597014925 0.00750323608895778
4 power Chlorophyll 390.410004 -0.01625121404032659 y = 0.019411 * x^-0.060440 134 10.960663313432837 3.9096921347220377 0.017540880597014925 0.00750323608895778
5 exponential Chlorophyll 390.410004 -0.018540174411018073 y = 0.018243 * exp(-0.007186*x) 134 10.960663313432837 3.9096921347220377 0.017540880597014925 0.00750323608895778

View File

@ -0,0 +1,5 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
logarithmic,Chlorophyll,394.450012,0.0016938639111105935,y = 0.015234 + 0.000830*ln(x),134,10.960663313432837,3.9096921347220377,0.01716890298507463,0.007280847323239202
linear,Chlorophyll,394.450012,0.0010649475553690113,y = 0.016503 + 0.000061*x,134,10.960663313432837,3.9096921347220377,0.01716890298507463,0.007280847323239202
power,Chlorophyll,394.450012,-0.023745377413006752,y = 0.020435 * x^-0.094840,134,10.960663313432837,3.9096921347220377,0.01716890298507463,0.007280847323239202
exponential,Chlorophyll,394.450012,-0.02617627918721488,y = 0.018415 * exp(-0.010666*x),134,10.960663313432837,3.9096921347220377,0.01716890298507463,0.007280847323239202
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 logarithmic Chlorophyll 394.450012 0.0016938639111105935 y = 0.015234 + 0.000830*ln(x) 134 10.960663313432837 3.9096921347220377 0.01716890298507463 0.007280847323239202
3 linear Chlorophyll 394.450012 0.0010649475553690113 y = 0.016503 + 0.000061*x 134 10.960663313432837 3.9096921347220377 0.01716890298507463 0.007280847323239202
4 power Chlorophyll 394.450012 -0.023745377413006752 y = 0.020435 * x^-0.094840 134 10.960663313432837 3.9096921347220377 0.01716890298507463 0.007280847323239202
5 exponential Chlorophyll 394.450012 -0.02617627918721488 y = 0.018415 * exp(-0.010666*x) 134 10.960663313432837 3.9096921347220377 0.01716890298507463 0.007280847323239202

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
logarithmic,Chlorophyll,398.493011,0.000857763974499659,y = 0.015092 + 0.000573*ln(x),134,10.960663313432837,3.9096921347220377,0.016425865671641792,0.00705544097312418
linear,Chlorophyll,398.493011,0.0003890186261535922,y = 0.016036 + 0.000036*x,134,10.960663313432837,3.9096921347220377,0.016425865671641792,0.00705544097312418
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 logarithmic Chlorophyll 398.493011 0.000857763974499659 y = 0.015092 + 0.000573*ln(x) 134 10.960663313432837 3.9096921347220377 0.016425865671641792 0.00705544097312418
3 linear Chlorophyll 398.493011 0.0003890186261535922 y = 0.016036 + 0.000036*x 134 10.960663313432837 3.9096921347220377 0.016425865671641792 0.00705544097312418

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
logarithmic,Chlorophyll,402.539001,0.00048016503667658306,y = 0.015505 + 0.000443*ln(x),134,10.960663313432837,3.9096921347220377,0.01653574626865672,0.007288381669035372
linear,Chlorophyll,402.539001,0.0001313248786827259,y = 0.016302 + 0.000021*x,134,10.960663313432837,3.9096921347220377,0.01653574626865672,0.007288381669035372
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 logarithmic Chlorophyll 402.539001 0.00048016503667658306 y = 0.015505 + 0.000443*ln(x) 134 10.960663313432837 3.9096921347220377 0.01653574626865672 0.007288381669035372
3 linear Chlorophyll 402.539001 0.0001313248786827259 y = 0.016302 + 0.000021*x 134 10.960663313432837 3.9096921347220377 0.01653574626865672 0.007288381669035372

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
logarithmic,Chlorophyll,406.588989,0.00016428918964617178,y = 0.015242 + 0.000242*ln(x),134,10.960663313432837,3.9096921347220377,0.015806723880597017,0.006825478500958131
linear,Chlorophyll,406.588989,1.6937017762730378e-06,y = 0.015782 + 0.000002*x,134,10.960663313432837,3.9096921347220377,0.015806723880597017,0.006825478500958131
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 logarithmic Chlorophyll 406.588989 0.00016428918964617178 y = 0.015242 + 0.000242*ln(x) 134 10.960663313432837 3.9096921347220377 0.015806723880597017 0.006825478500958131
3 linear Chlorophyll 406.588989 1.6937017762730378e-06 y = 0.015782 + 0.000002*x 134 10.960663313432837 3.9096921347220377 0.015806723880597017 0.006825478500958131

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
logarithmic,Chlorophyll,410.641998,0.00010695507791136372,y = 0.014822 + 0.000189*ln(x),134,10.960663313432837,3.9096921347220377,0.015261298507462688,0.0065848636688817614
linear,Chlorophyll,410.641998,2.2039578226884515e-06,y = 0.015289 + -0.000003*x,134,10.960663313432837,3.9096921347220377,0.015261298507462688,0.0065848636688817614
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 logarithmic Chlorophyll 410.641998 0.00010695507791136372 y = 0.014822 + 0.000189*ln(x) 134 10.960663313432837 3.9096921347220377 0.015261298507462688 0.0065848636688817614
3 linear Chlorophyll 410.641998 2.2039578226884515e-06 y = 0.015289 + -0.000003*x 134 10.960663313432837 3.9096921347220377 0.015261298507462688 0.0065848636688817614

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,414.699005,9.23718683270014e-05,y = 0.014978 + -0.000015*x,134,10.960663313432837,3.9096921347220377,0.014808014925373135,0.006298696608817295
logarithmic,Chlorophyll,414.699005,1.0794055052776308e-05,y = 0.014674 + 0.000057*ln(x),134,10.960663313432837,3.9096921347220377,0.014808014925373135,0.006298696608817295
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 414.699005 9.23718683270014e-05 y = 0.014978 + -0.000015*x 134 10.960663313432837 3.9096921347220377 0.014808014925373135 0.006298696608817295
3 logarithmic Chlorophyll 414.699005 1.0794055052776308e-05 y = 0.014674 + 0.000057*ln(x) 134 10.960663313432837 3.9096921347220377 0.014808014925373135 0.006298696608817295

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,418.759003,0.0002645667637589666,y = 0.014364 + -0.000025*x,134,10.960663313432837,3.9096921347220377,0.014088052238805972,0.006051440804527788
logarithmic,Chlorophyll,418.759003,7.794051727350038e-06,y = 0.014197 + -0.000047*ln(x),134,10.960663313432837,3.9096921347220377,0.014088052238805972,0.006051440804527788
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 418.759003 0.0002645667637589666 y = 0.014364 + -0.000025*x 134 10.960663313432837 3.9096921347220377 0.014088052238805972 0.006051440804527788
3 logarithmic Chlorophyll 418.759003 7.794051727350038e-06 y = 0.014197 + -0.000047*ln(x) 134 10.960663313432837 3.9096921347220377 0.014088052238805972 0.006051440804527788

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,422.821991,0.0008115657894584016,y = 0.014105 + -0.000042*x,134,10.960663313432837,3.9096921347220377,0.013641977611940298,0.005799322545956216
logarithmic,Chlorophyll,422.821991,0.0001768579797475356,y = 0.014140 + -0.000214*ln(x),134,10.960663313432837,3.9096921347220377,0.013641977611940298,0.005799322545956216
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 422.821991 0.0008115657894584016 y = 0.014105 + -0.000042*x 134 10.960663313432837 3.9096921347220377 0.013641977611940298 0.005799322545956216
3 logarithmic Chlorophyll 422.821991 0.0001768579797475356 y = 0.014140 + -0.000214*ln(x) 134 10.960663313432837 3.9096921347220377 0.013641977611940298 0.005799322545956216

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,426.889008,0.0013073210454338513,y = 0.014170 + -0.000053*x,134,10.960663313432837,3.9096921347220377,0.013589074626865672,0.005728319043930576
logarithmic,Chlorophyll,426.889008,0.0004182937928386421,y = 0.014345 + -0.000325*ln(x),134,10.960663313432837,3.9096921347220377,0.013589074626865672,0.005728319043930576
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 426.889008 0.0013073210454338513 y = 0.014170 + -0.000053*x 134 10.960663313432837 3.9096921347220377 0.013589074626865672 0.005728319043930576
3 logarithmic Chlorophyll 426.889008 0.0004182937928386421 y = 0.014345 + -0.000325*ln(x) 134 10.960663313432837 3.9096921347220377 0.013589074626865672 0.005728319043930576

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,430.959015,0.002347137019542922,y = 0.013733 + -0.000067*x,134,10.960663313432837,3.9096921347220377,0.012997753731343284,0.005410808768465578
logarithmic,Chlorophyll,430.959015,0.0010658686661263461,y = 0.014138 + -0.000489*ln(x),134,10.960663313432837,3.9096921347220377,0.012997753731343284,0.005410808768465578
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 430.959015 0.002347137019542922 y = 0.013733 + -0.000067*x 134 10.960663313432837 3.9096921347220377 0.012997753731343284 0.005410808768465578
3 logarithmic Chlorophyll 430.959015 0.0010658686661263461 y = 0.014138 + -0.000489*ln(x) 134 10.960663313432837 3.9096921347220377 0.012997753731343284 0.005410808768465578

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,435.032013,0.0024283085040497365,y = 0.013179 + -0.000069*x,134,10.960663313432837,3.9096921347220377,0.012427850746268653,0.005435180040005626
logarithmic,Chlorophyll,435.032013,0.0011266238526388417,y = 0.013606 + -0.000506*ln(x),134,10.960663313432837,3.9096921347220377,0.012427850746268653,0.005435180040005626
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 435.032013 0.0024283085040497365 y = 0.013179 + -0.000069*x 134 10.960663313432837 3.9096921347220377 0.012427850746268653 0.005435180040005626
3 logarithmic Chlorophyll 435.032013 0.0011266238526388417 y = 0.013606 + -0.000506*ln(x) 134 10.960663313432837 3.9096921347220377 0.012427850746268653 0.005435180040005626

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,439.109009,0.0042064615418079265,y = 0.013397 + -0.000086*x,134,10.960663313432837,3.9096921347220377,0.012450895522388062,0.005205005715323194
logarithmic,Chlorophyll,439.109009,0.002294686396103418,y = 0.014061 + -0.000691*ln(x),134,10.960663313432837,3.9096921347220377,0.012450895522388062,0.005205005715323194
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 439.109009 0.0042064615418079265 y = 0.013397 + -0.000086*x 134 10.960663313432837 3.9096921347220377 0.012450895522388062 0.005205005715323194
3 logarithmic Chlorophyll 439.109009 0.002294686396103418 y = 0.014061 + -0.000691*ln(x) 134 10.960663313432837 3.9096921347220377 0.012450895522388062 0.005205005715323194

View File

@ -0,0 +1,3 @@
regression_method,x_variable,y_variable,r_squared,equation,sample_size,x_mean,x_std,y_mean,y_std
linear,Chlorophyll,443.190002,0.004695213661859765,y = 0.013279 + -0.000091*x,134,10.960663313432837,3.9096921347220377,0.012285432835820896,0.00517286197733264
logarithmic,Chlorophyll,443.190002,0.0026235944054925353,y = 0.013996 + -0.000734*ln(x),134,10.960663313432837,3.9096921347220377,0.012285432835820896,0.00517286197733264
1 regression_method x_variable y_variable r_squared equation sample_size x_mean x_std y_mean y_std
2 linear Chlorophyll 443.190002 0.004695213661859765 y = 0.013279 + -0.000091*x 134 10.960663313432837 3.9096921347220377 0.012285432835820896 0.00517286197733264
3 logarithmic Chlorophyll 443.190002 0.0026235944054925353 y = 0.013996 + -0.000734*ln(x) 134 10.960663313432837 3.9096921347220377 0.012285432835820896 0.00517286197733264

Some files were not shown because too many files have changed in this diff Show More