Initial commit of WQ_GUI
This commit is contained in:
1
src/__init__.py
Normal file
1
src/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
1
src/core/__init__.py
Normal file
1
src/core/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
367
src/core/glint_removal/Goodman.py
Normal file
367
src/core/glint_removal/Goodman.py
Normal 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数组或None,1表示水域,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
|
||||
290
src/core/glint_removal/Hedley.py
Normal file
290
src/core/glint_removal/Hedley.py
Normal 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数组或None,1表示水域,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
|
||||
313
src/core/glint_removal/Kutser.py
Normal file
313
src/core/glint_removal/Kutser.py
Normal 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数组或None,1表示水域,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
|
||||
572
src/core/glint_removal/SUGAR.py
Normal file
572
src/core/glint_removal/SUGAR.py
Normal 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数组或None,1表示水域,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应该已自动创建")
|
||||
1
src/core/glint_removal/__init__.py
Normal file
1
src/core/glint_removal/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
926
src/core/glint_removal/get_spectral-test.py
Normal file
926
src/core/glint_removal/get_spectral-test.py
Normal 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)
|
||||
|
||||
785
src/core/glint_removal/get_spectral.py
Normal file
785
src/core/glint_removal/get_spectral.py
Normal 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)
|
||||
|
||||
1
src/core/modeling/__init__.py
Normal file
1
src/core/modeling/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
124
src/core/modeling/best_R2.py
Normal file
124
src/core/modeling/best_R2.py
Normal 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")
|
||||
1134
src/core/modeling/modeling_batch.py
Normal file
1134
src/core/modeling/modeling_batch.py
Normal file
File diff suppressed because it is too large
Load Diff
392
src/core/modeling/regression.py
Normal file
392
src/core/modeling/regression.py
Normal 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()
|
||||
382
src/core/non_empirical_model_correction.py
Normal file
382
src/core/non_empirical_model_correction.py
Normal 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
|
||||
|
||||
253
src/core/non_empirical_retrieval.py
Normal file
253
src/core/non_empirical_retrieval.py
Normal 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)
|
||||
1
src/core/prediction/__init__.py
Normal file
1
src/core/prediction/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
1144
src/core/prediction/inference_batch.py
Normal file
1144
src/core/prediction/inference_batch.py
Normal file
File diff suppressed because it is too large
Load Diff
894
src/core/prediction/sctter_batch.py
Normal file
894
src/core/prediction/sctter_batch.py
Normal 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
22
src/core/type_define.py
Normal 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
|
||||
2671
src/core/water_quality_inversion_pipeline.py
Normal file
2671
src/core/water_quality_inversion_pipeline.py
Normal file
File diff suppressed because it is too large
Load Diff
4299
src/core/water_quality_inversion_pipeline_GUI.py
Normal file
4299
src/core/water_quality_inversion_pipeline_GUI.py
Normal file
File diff suppressed because it is too large
Load Diff
242
src/gui/STYLES_README.md
Normal file
242
src/gui/STYLES_README.md
Normal 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
1
src/gui/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
570
src/gui/styles.py
Normal file
570
src/gui/styles.py
Normal 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
6300
src/gui/water_quality_gui.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/gui/work_dir/2_glint/severe_glint_area.dat
Normal file
BIN
src/gui/work_dir/2_glint/severe_glint_area.dat
Normal file
Binary file not shown.
15
src/gui/work_dir/2_glint/severe_glint_area.hdr
Normal file
15
src/gui/work_dir/2_glint/severe_glint_area.hdr
Normal 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}
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,447.27301,0.005169120886448608,y = 0.013250 + -0.000094*x,134,10.960663313432837,3.9096921347220377,0.012221335820895522,0.005101097814158477
|
||||
logarithmic,Chlorophyll,447.27301,0.002968315833349222,y = 0.014016 + -0.000770*ln(x),134,10.960663313432837,3.9096921347220377,0.012221335820895522,0.005101097814158477
|
||||
|
@ -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,451.360992,0.004863772601295668,y = 0.013262 + -0.000092*x,134,10.960663313432837,3.9096921347220377,0.012257104477611941,0.00513769471108476
|
||||
logarithmic,Chlorophyll,451.360992,0.002739591016255649,y = 0.013993 + -0.000745*ln(x),134,10.960663313432837,3.9096921347220377,0.012257104477611941,0.00513769471108476
|
||||
|
@ -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,455.450989,0.005525011318207929,y = 0.013206 + -0.000097*x,134,10.960663313432837,3.9096921347220377,0.012144328358208955,0.005093595896892254
|
||||
logarithmic,Chlorophyll,455.450989,0.0032249891993542112,y = 0.014012 + -0.000802*ln(x),134,10.960663313432837,3.9096921347220377,0.012144328358208955,0.005093595896892254
|
||||
|
@ -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,459.545013,0.005303969126716268,y = 0.013029 + -0.000095*x,134,10.960663313432837,3.9096921347220377,0.011992097014925374,0.005076948054716495
|
||||
logarithmic,Chlorophyll,459.545013,0.0030599507272712767,y = 0.013805 + -0.000778*ln(x),134,10.960663313432837,3.9096921347220377,0.011992097014925374,0.005076948054716495
|
||||
|
@ -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,463.641998,0.005309017626029533,y = 0.013098 + -0.000096*x,134,10.960663313432837,3.9096921347220377,0.012043410447761194,0.0051616384058770694
|
||||
logarithmic,Chlorophyll,463.641998,0.00309369061071596,y = 0.013897 + -0.000796*ln(x),134,10.960663313432837,3.9096921347220377,0.012043410447761194,0.0051616384058770694
|
||||
|
@ -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,467.743011,0.005910540579640466,y = 0.013129 + -0.000101*x,134,10.960663313432837,3.9096921347220377,0.012021395522388062,0.0051373056777988335
|
||||
logarithmic,Chlorophyll,467.743011,0.0035312797033351107,y = 0.013992 + -0.000846*ln(x),134,10.960663313432837,3.9096921347220377,0.012021395522388062,0.0051373056777988335
|
||||
|
@ -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,471.846985,0.006597953952974689,y = 0.013090 + -0.000107*x,134,10.960663313432837,3.9096921347220377,0.011916462686567163,0.005154508155317495
|
||||
logarithmic,Chlorophyll,471.846985,0.004055554987167143,y = 0.014036 + -0.000910*ln(x),134,10.960663313432837,3.9096921347220377,0.011916462686567163,0.005154508155317495
|
||||
|
@ -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,475.954987,0.006969427568661812,y = 0.013222 + -0.000110*x,134,10.960663313432837,3.9096921347220377,0.012010992537313433,0.005173190365687869
|
||||
logarithmic,Chlorophyll,475.954987,0.0043493646432547495,y = 0.014214 + -0.000945*ln(x),134,10.960663313432837,3.9096921347220377,0.012010992537313433,0.005173190365687869
|
||||
|
@ -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,480.065002,0.006908555626254476,y = 0.013165 + -0.000112*x,134,10.960663313432837,3.9096921347220377,0.011938738805970149,0.005260977465686793
|
||||
logarithmic,Chlorophyll,480.065002,0.004358699372388197,y = 0.014181 + -0.000962*ln(x),134,10.960663313432837,3.9096921347220377,0.011938738805970149,0.005260977465686793
|
||||
|
@ -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,484.179993,0.007786080902936532,y = 0.013479 + -0.000120*x,134,10.960663313432837,3.9096921347220377,0.01216315671641791,0.00531894585260112
|
||||
logarithmic,Chlorophyll,484.179993,0.00503315433665652,y = 0.014599 + -0.001046*ln(x),134,10.960663313432837,3.9096921347220377,0.01216315671641791,0.00531894585260112
|
||||
|
@ -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,488.296997,0.009003426521211222,y = 0.013413 + -0.000127*x,134,10.960663313432837,3.9096921347220377,0.01202031343283582,0.005235852382412684
|
||||
logarithmic,Chlorophyll,488.296997,0.0059888617317502835,y = 0.014636 + -0.001123*ln(x),134,10.960663313432837,3.9096921347220377,0.01202031343283582,0.005235852382412684
|
||||
|
@ -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,492.417999,0.009356273796731096,y = 0.013426 + -0.000130*x,134,10.960663313432837,3.9096921347220377,0.011996694029850746,0.00526918646504346
|
||||
logarithmic,Chlorophyll,492.417999,0.0063786563113004124,y = 0.014714 + -0.001166*ln(x),134,10.960663313432837,3.9096921347220377,0.011996694029850746,0.00526918646504346
|
||||
|
@ -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,496.542999,0.008454580487572083,y = 0.013491 + -0.000126*x,134,10.960663313432837,3.9096921347220377,0.012105947761194029,0.00537461785981684
|
||||
logarithmic,Chlorophyll,496.542999,0.005612309162053686,y = 0.014705 + -0.001116*ln(x),134,10.960663313432837,3.9096921347220377,0.012105947761194029,0.00537461785981684
|
||||
|
@ -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,500.67099,0.008930402290994066,y = 0.013947 + -0.000130*x,134,10.960663313432837,3.9096921347220377,0.012519723880597015,0.005386983290859261
|
||||
logarithmic,Chlorophyll,500.67099,0.006027433290726858,y = 0.015220 + -0.001159*ln(x),134,10.960663313432837,3.9096921347220377,0.012519723880597015,0.005386983290859261
|
||||
|
@ -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,504.802002,0.007851805368295328,y = 0.014159 + -0.000122*x,134,10.960663313432837,3.9096921347220377,0.012821291044776119,0.005386616665575906
|
||||
logarithmic,Chlorophyll,504.802002,0.005168584239575225,y = 0.015321 + -0.001073*ln(x),134,10.960663313432837,3.9096921347220377,0.012821291044776119,0.005386616665575906
|
||||
|
@ -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,508.936005,0.006500148302153508,y = 0.014525 + -0.000113*x,134,10.960663313432837,3.9096921347220377,0.013285626865671642,0.005484303548583865
|
||||
logarithmic,Chlorophyll,508.936005,0.004158811025431031,y = 0.015569 + -0.000980*ln(x),134,10.960663313432837,3.9096921347220377,0.013285626865671642,0.005484303548583865
|
||||
|
@ -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,513.073975,0.004917130582781315,y = 0.014969 + -0.000099*x,134,10.960663313432837,3.9096921347220377,0.013879231343283581,0.005544190630289309
|
||||
logarithmic,Chlorophyll,513.073975,0.0029641857181392783,y = 0.015828 + -0.000836*ln(x),134,10.960663313432837,3.9096921347220377,0.013879231343283581,0.005544190630289309
|
||||
exponential,Chlorophyll,513.073975,-0.01889032090850251,y = 0.016669 * exp(-0.020406*x),134,10.960663313432837,3.9096921347220377,0.013879231343283581,0.005544190630289309
|
||||
power,Chlorophyll,513.073975,-0.019715344472010177,y = 0.020892 * x^-0.192919,134,10.960663313432837,3.9096921347220377,0.013879231343283581,0.005544190630289309
|
||||
|
@ -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,517.216003,0.004276559761211218,y = 0.015763 + -0.000093*x,134,10.960663313432837,3.9096921347220377,0.014746992537313432,0.005540761628631703
|
||||
logarithmic,Chlorophyll,517.216003,0.00245772439289782,y = 0.016520 + -0.000761*ln(x),134,10.960663313432837,3.9096921347220377,0.014746992537313432,0.005540761628631703
|
||||
exponential,Chlorophyll,517.216003,-0.018892619920322984,y = 0.017382 * exp(-0.018434*x),134,10.960663313432837,3.9096921347220377,0.014746992537313432,0.005540761628631703
|
||||
power,Chlorophyll,517.216003,-0.0194484153066794,y = 0.021251 * x^-0.172972,134,10.960663313432837,3.9096921347220377,0.014746992537313432,0.005540761628631703
|
||||
|
@ -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,521.361023,0.002867645407956476,y = 0.015976 + -0.000077*x,134,10.960663313432837,3.9096921347220377,0.01513621641791045,0.0055935618166287285
|
||||
logarithmic,Chlorophyll,521.361023,0.001510103149844122,y = 0.016540 + -0.000602*ln(x),134,10.960663313432837,3.9096921347220377,0.01513621641791045,0.0055935618166287285
|
||||
exponential,Chlorophyll,521.361023,-0.013492164822140218,y = 0.017293 * exp(-0.014959*x),134,10.960663313432837,3.9096921347220377,0.01513621641791045,0.0055935618166287285
|
||||
power,Chlorophyll,521.361023,-0.01389692075440152,y = 0.020293 * x^-0.139034,134,10.960663313432837,3.9096921347220377,0.01513621641791045,0.0055935618166287285
|
||||
|
@ -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,525.508972,0.0016701377663735917,y = 0.016584 + -0.000059*x,134,10.960663313432837,3.9096921347220377,0.015935962686567166,0.005657732827939037
|
||||
logarithmic,Chlorophyll,525.508972,0.0007153098124390578,y = 0.016913 + -0.000419*ln(x),134,10.960663313432837,3.9096921347220377,0.015935962686567166,0.005657732827939037
|
||||
exponential,Chlorophyll,525.508972,-0.011873247394506237,y = 0.017736 * exp(-0.012223*x),134,10.960663313432837,3.9096921347220377,0.015935962686567166,0.005657732827939037
|
||||
power,Chlorophyll,525.508972,-0.01195937275095682,y = 0.020122 * x^-0.111667,134,10.960663313432837,3.9096921347220377,0.015935962686567166,0.005657732827939037
|
||||
|
@ -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,529.659973,0.001100615217659584,y = 0.016897 + -0.000048*x,134,10.960663313432837,3.9096921347220377,0.016371305970149252,0.005647344987370815
|
||||
logarithmic,Chlorophyll,529.659973,0.00038742510141320796,y = 0.017089 + -0.000308*ln(x),134,10.960663313432837,3.9096921347220377,0.016371305970149252,0.005647344987370815
|
||||
power,Chlorophyll,529.659973,-0.010602748616709512,y = 0.019984 * x^-0.095978,134,10.960663313432837,3.9096921347220377,0.016371305970149252,0.005647344987370815
|
||||
exponential,Chlorophyll,529.659973,-0.010676230097632189,y = 0.017950 * exp(-0.010610*x),134,10.960663313432837,3.9096921347220377,0.016371305970149252,0.005647344987370815
|
||||
|
@ -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,533.815002,0.0005274884679772329,y = 0.017331 + -0.000034*x,134,10.960663313432837,3.9096921347220377,0.016960171641791044,0.005752288800181051
|
||||
logarithmic,Chlorophyll,533.815002,9.639796014881963e-05,y = 0.017325 + -0.000156*ln(x),134,10.960663313432837,3.9096921347220377,0.016960171641791044,0.005752288800181051
|
||||
power,Chlorophyll,533.815002,-0.010942625292486463,y = 0.020200 * x^-0.085250,134,10.960663313432837,3.9096921347220377,0.016960171641791044,0.005752288800181051
|
||||
exponential,Chlorophyll,533.815002,-0.011375297319334177,y = 0.018394 * exp(-0.009577*x),134,10.960663313432837,3.9096921347220377,0.016960171641791044,0.005752288800181051
|
||||
|
@ -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,537.973999,0.010059827315010317,y = 0.018287 + -0.000095*x,134,10.960663313432837,3.9096921347220377,0.017245291044776116,0.003706413209287316
|
||||
logarithmic,Chlorophyll,537.973999,0.0058263736903666485,y = 0.019072 + -0.000784*ln(x),134,10.960663313432837,3.9096921347220377,0.017245291044776116,0.003706413209287316
|
||||
exponential,Chlorophyll,537.973999,-0.0006317634379562342,y = 0.018947 * exp(-0.009908*x),134,10.960663313432837,3.9096921347220377,0.017245291044776116,0.003706413209287316
|
||||
power,Chlorophyll,537.973999,-0.004088770616538007,y = 0.020915 * x^-0.089031,134,10.960663313432837,3.9096921347220377,0.017245291044776116,0.003706413209287316
|
||||
|
@ -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,542.13501,0.10592938275035968,y = 0.019264 + -0.000164*x,134,10.960663313432837,3.9096921347220377,0.017471380597014925,0.0019645221126944118
|
||||
exponential,Chlorophyll,542.13501,0.09468361275300774,y = 0.019618 * exp(-0.011250*x),134,10.960663313432837,3.9096921347220377,0.017471380597014925,0.0019645221126944118
|
||||
logarithmic,Chlorophyll,542.13501,0.07410535895090076,y = 0.020924 + -0.001482*ln(x),134,10.960663313432837,3.9096921347220377,0.017471380597014925,0.0019645221126944118
|
||||
power,Chlorophyll,542.13501,0.06336507350101761,y = 0.022043 * x^-0.102942,134,10.960663313432837,3.9096921347220377,0.017471380597014925,0.0019645221126944118
|
||||
|
@ -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,546.301025,0.12040522641720053,y = 0.019716 + -0.000169*x,134,10.960663313432837,3.9096921347220377,0.01786192537313433,0.0019057641301598596
|
||||
exponential,Chlorophyll,546.301025,0.11002711959899414,y = 0.020039 * exp(-0.011103*x),134,10.960663313432837,3.9096921347220377,0.01786192537313433,0.0019057641301598596
|
||||
logarithmic,Chlorophyll,546.301025,0.08587624549622919,y = 0.021467 + -0.001547*ln(x),134,10.960663313432837,3.9096921347220377,0.01786192537313433,0.0019057641301598596
|
||||
power,Chlorophyll,546.301025,0.07589213783045401,y = 0.022516 * x^-0.102241,134,10.960663313432837,3.9096921347220377,0.01786192537313433,0.0019057641301598596
|
||||
|
@ -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,550.468994,0.1269569921216488,y = 0.020121 + -0.000172*x,134,10.960663313432837,3.9096921347220377,0.01824102985074627,0.0018825013010254905
|
||||
exponential,Chlorophyll,550.468994,0.1175391734287099,y = 0.020411 * exp(-0.010816*x),134,10.960663313432837,3.9096921347220377,0.01824102985074627,0.0018825013010254905
|
||||
logarithmic,Chlorophyll,550.468994,0.0907980339917791,y = 0.021903 + -0.001572*ln(x),134,10.960663313432837,3.9096921347220377,0.01824102985074627,0.0018825013010254905
|
||||
power,Chlorophyll,550.468994,0.08162875081638976,y = 0.022867 * x^-0.099639,134,10.960663313432837,3.9096921347220377,0.01824102985074627,0.0018825013010254905
|
||||
|
@ -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,554.640991,0.1376426262180579,y = 0.020508 + -0.000178*x,134,10.960663313432837,3.9096921347220377,0.018552656716417912,0.0018796053138591957
|
||||
exponential,Chlorophyll,554.640991,0.12753098839026022,y = 0.020816 * exp(-0.011054*x),134,10.960663313432837,3.9096921347220377,0.018552656716417912,0.0018796053138591957
|
||||
logarithmic,Chlorophyll,554.640991,0.09810495477002423,y = 0.022354 + -0.001631*ln(x),134,10.960663313432837,3.9096921347220377,0.018552656716417912,0.0018796053138591957
|
||||
power,Chlorophyll,554.640991,0.08828430738543391,y = 0.023368 * x^-0.101637,134,10.960663313432837,3.9096921347220377,0.018552656716417912,0.0018796053138591957
|
||||
|
@ -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,558.815979,0.14438886343879165,y = 0.020984 + -0.000192*x,134,10.960663313432837,3.9096921347220377,0.018875544776119402,0.0019794547666891088
|
||||
exponential,Chlorophyll,558.815979,0.13400466793505816,y = 0.021319 * exp(-0.011710*x),134,10.960663313432837,3.9096921347220377,0.018875544776119402,0.0019794547666891088
|
||||
logarithmic,Chlorophyll,558.815979,0.10606464763119816,y = 0.023038 + -0.001786*ln(x),134,10.960663313432837,3.9096921347220377,0.018875544776119402,0.0019794547666891088
|
||||
power,Chlorophyll,558.815979,0.09560595602330613,y = 0.024181 * x^-0.109152,134,10.960663313432837,3.9096921347220377,0.018875544776119402,0.0019794547666891088
|
||||
|
@ -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,562.994995,0.13570684972920022,y = 0.021065 + -0.000193*x,134,10.960663313432837,3.9096921347220377,0.018946902985074628,0.002050816811679121
|
||||
exponential,Chlorophyll,562.994995,0.1251679621881412,y = 0.021424 * exp(-0.011845*x),134,10.960663313432837,3.9096921347220377,0.018946902985074628,0.002050816811679121
|
||||
logarithmic,Chlorophyll,562.994995,0.10005264191024388,y = 0.023135 + -0.001797*ln(x),134,10.960663313432837,3.9096921347220377,0.018946902985074628,0.002050816811679121
|
||||
power,Chlorophyll,562.994995,0.08944628510214314,y = 0.024352 * x^-0.110685,134,10.960663313432837,3.9096921347220377,0.018946902985074628,0.002050816811679121
|
||||
|
@ -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,567.177002,0.03705163102869502,y = 0.020812 + -0.000155*x,134,10.960663313432837,3.9096921347220377,0.01911655970149254,0.0031426043067057114
|
||||
exponential,Chlorophyll,567.177002,0.02735476743599441,y = 0.021355 * exp(-0.011096*x),134,10.960663313432837,3.9096921347220377,0.01911655970149254,0.0031426043067057114
|
||||
logarithmic,Chlorophyll,567.177002,0.02624551679688847,y = 0.022403 + -0.001411*ln(x),134,10.960663313432837,3.9096921347220377,0.01911655970149254,0.0031426043067057114
|
||||
power,Chlorophyll,567.177002,0.01690050625981776,y = 0.024064 * x^-0.103446,134,10.960663313432837,3.9096921347220377,0.01911655970149254,0.0031426043067057114
|
||||
|
@ -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,571.362976,0.032367014815650186,y = 0.020732 + -0.000161*x,134,10.960663313432837,3.9096921347220377,0.018965664179104478,0.0035018296559745925
|
||||
logarithmic,Chlorophyll,571.362976,0.023243518566287924,y = 0.022412 + -0.001479*ln(x),134,10.960663313432837,3.9096921347220377,0.018965664179104478,0.0035018296559745925
|
||||
exponential,Chlorophyll,571.362976,0.021156108573575194,y = 0.021404 * exp(-0.012237*x),134,10.960663313432837,3.9096921347220377,0.018965664179104478,0.0035018296559745925
|
||||
power,Chlorophyll,571.362976,0.012371457852355827,y = 0.024473 * x^-0.115076,134,10.960663313432837,3.9096921347220377,0.018965664179104478,0.0035018296559745925
|
||||
|
@ -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,575.551025,0.003711200606445142,y = 0.019973 + -0.000095*x,134,10.960663313432837,3.9096921347220377,0.018931119402985072,0.006097893443512235
|
||||
logarithmic,Chlorophyll,575.551025,0.0022851664540198824,y = 0.020813 + -0.000808*ln(x),134,10.960663313432837,3.9096921347220377,0.018931119402985072,0.006097893443512235
|
||||
exponential,Chlorophyll,575.551025,-0.008535989595423121,y = 0.021158 * exp(-0.012309*x),134,10.960663313432837,3.9096921347220377,0.018931119402985072,0.006097893443512235
|
||||
power,Chlorophyll,575.551025,-0.009305973499541542,y = 0.024221 * x^-0.115919,134,10.960663313432837,3.9096921347220377,0.018931119402985072,0.006097893443512235
|
||||
|
@ -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,579.744019,0.005094509026133509,y = 0.019711 + -0.000110*x,134,10.960663313432837,3.9096921347220377,0.018505432835820897,0.006025405615191274
|
||||
logarithmic,Chlorophyll,579.744019,0.0034065768371064342,y = 0.020776 + -0.000974*ln(x),134,10.960663313432837,3.9096921347220377,0.018505432835820897,0.006025405615191274
|
||||
exponential,Chlorophyll,579.744019,-0.007270136129956084,y = 0.020909 * exp(-0.013357*x),134,10.960663313432837,3.9096921347220377,0.018505432835820897,0.006025405615191274
|
||||
power,Chlorophyll,579.744019,-0.008400608136997167,y = 0.024296 * x^-0.127275,134,10.960663313432837,3.9096921347220377,0.018505432835820897,0.006025405615191274
|
||||
|
@ -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,583.939026,0.004657425272328819,y = 0.019070 + -0.000108*x,134,10.960663313432837,3.9096921347220377,0.017881,0.006215784305887186
|
||||
logarithmic,Chlorophyll,583.939026,0.003161690862374833,y = 0.020137 + -0.000968*ln(x),134,10.960663313432837,3.9096921347220377,0.017881,0.006215784305887186
|
||||
exponential,Chlorophyll,583.939026,-0.00834323144293947,y = 0.020355 * exp(-0.014251*x),134,10.960663313432837,3.9096921347220377,0.017881,0.006215784305887186
|
||||
power,Chlorophyll,583.939026,-0.009289166222129941,y = 0.023939 * x^-0.136644,134,10.960663313432837,3.9096921347220377,0.017881,0.006215784305887186
|
||||
|
@ -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,588.138,0.007038900978221574,y = 0.018745 + -0.000132*x,134,10.960663313432837,3.9096921347220377,0.017294746268656718,0.006164975937402735
|
||||
logarithmic,Chlorophyll,588.138,0.005079658860641656,y = 0.020131 + -0.001218*ln(x),134,10.960663313432837,3.9096921347220377,0.017294746268656718,0.006164975937402735
|
||||
exponential,Chlorophyll,588.138,-0.008087990598099282,y = 0.020176 * exp(-0.016777*x),134,10.960663313432837,3.9096921347220377,0.017294746268656718,0.006164975937402735
|
||||
power,Chlorophyll,588.138,-0.009554552395312665,y = 0.024503 * x^-0.162327,134,10.960663313432837,3.9096921347220377,0.017294746268656718,0.006164975937402735
|
||||
|
@ -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,592.341003,0.005528079261697849,y = 0.017838 + -0.000118*x,134,10.960663313432837,3.9096921347220377,0.016543097014925373,0.006212725389009627
|
||||
logarithmic,Chlorophyll,592.341003,0.003887007195125136,y = 0.019044 + -0.001073*ln(x),134,10.960663313432837,3.9096921347220377,0.016543097014925373,0.006212725389009627
|
||||
exponential,Chlorophyll,592.341003,-0.011813537096786675,y = 0.019386 * exp(-0.017528*x),134,10.960663313432837,3.9096921347220377,0.016543097014925373,0.006212725389009627
|
||||
power,Chlorophyll,592.341003,-0.012846809152319283,y = 0.023741 * x^-0.169433,134,10.960663313432837,3.9096921347220377,0.016543097014925373,0.006212725389009627
|
||||
|
@ -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,596.546997,0.003794780064093062,y = 0.016713 + -0.000101*x,134,10.960663313432837,3.9096921347220377,0.015607044776119405,0.00640171329313859
|
||||
logarithmic,Chlorophyll,596.546997,0.0025823661966439815,y = 0.017707 + -0.000901*ln(x),134,10.960663313432837,3.9096921347220377,0.015607044776119405,0.00640171329313859
|
||||
exponential,Chlorophyll,596.546997,-0.014874157713247849,y = 0.018368 * exp(-0.018321*x),134,10.960663313432837,3.9096921347220377,0.015607044776119405,0.00640171329313859
|
||||
power,Chlorophyll,596.546997,-0.015374647488444415,y = 0.022702 * x^-0.177115,134,10.960663313432837,3.9096921347220377,0.015607044776119405,0.00640171329313859
|
||||
|
@ -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,600.755981,0.0031611200601100453,y = 0.015616 + -0.000092*x,134,10.960663313432837,3.9096921347220377,0.014608059701492537,0.006395783738002828
|
||||
logarithmic,Chlorophyll,600.755981,0.002151138099984129,y = 0.016523 + -0.000822*ln(x),134,10.960663313432837,3.9096921347220377,0.014608059701492537,0.006395783738002828
|
||||
exponential,Chlorophyll,600.755981,-0.015732414466808953,y = 0.017258 * exp(-0.018983*x),134,10.960663313432837,3.9096921347220377,0.014608059701492537,0.006395783738002828
|
||||
power,Chlorophyll,600.755981,-0.016109249860633446,y = 0.021535 * x^-0.184339,134,10.960663313432837,3.9096921347220377,0.014608059701492537,0.006395783738002828
|
||||
|
@ -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,604.968994,0.002398045183009123,y = 0.014883 + -0.000082*x,134,10.960663313432837,3.9096921347220377,0.013985902985074627,0.0065333725432655055
|
||||
logarithmic,Chlorophyll,604.968994,0.001603534119833716,y = 0.015675 + -0.000725*ln(x),134,10.960663313432837,3.9096921347220377,0.013985902985074627,0.0065333725432655055
|
||||
power,Chlorophyll,604.968994,-0.019789876685375907,y = 0.021361 * x^-0.202172,134,10.960663313432837,3.9096921347220377,0.013985902985074627,0.0065333725432655055
|
||||
exponential,Chlorophyll,604.968994,-0.019848091450597183,y = 0.016749 * exp(-0.020787*x),134,10.960663313432837,3.9096921347220377,0.013985902985074627,0.0065333725432655055
|
||||
|
@ -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,609.184998,0.0026364151647212397,y = 0.014491 + -0.000087*x,134,10.960663313432837,3.9096921347220377,0.013541022388059702,0.006602440257226493
|
||||
logarithmic,Chlorophyll,609.184998,0.001802623235843237,y = 0.015351 + -0.000777*ln(x),134,10.960663313432837,3.9096921347220377,0.013541022388059702,0.006602440257226493
|
||||
exponential,Chlorophyll,609.184998,-0.02173372426195974,y = 0.016508 * exp(-0.022851*x),134,10.960663313432837,3.9096921347220377,0.013541022388059702,0.006602440257226493
|
||||
power,Chlorophyll,609.184998,-0.021740201254617064,y = 0.021605 * x^-0.222988,134,10.960663313432837,3.9096921347220377,0.013541022388059702,0.006602440257226493
|
||||
|
@ -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,613.403992,0.002856835284101855,y = 0.014122 + -0.000091*x,134,10.960663313432837,3.9096921347220377,0.013127970149253732,0.006635524455917487
|
||||
logarithmic,Chlorophyll,613.403992,0.0019870205661061124,y = 0.015038 + -0.000820*ln(x),134,10.960663313432837,3.9096921347220377,0.013127970149253732,0.006635524455917487
|
||||
power,Chlorophyll,613.403992,-0.028809677197195516,y = 0.022780 * x^-0.263141,134,10.960663313432837,3.9096921347220377,0.013127970149253732,0.006635524455917487
|
||||
exponential,Chlorophyll,613.403992,-0.02909727099411552,y = 0.016581 * exp(-0.026956*x),134,10.960663313432837,3.9096921347220377,0.013127970149253732,0.006635524455917487
|
||||
|
@ -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,617.627014,0.0029411042399954956,y = 0.013748 + -0.000092*x,134,10.960663313432837,3.9096921347220377,0.012741731343283583,0.006619138380331145
|
||||
logarithmic,Chlorophyll,617.627014,0.0020597681456059336,y = 0.014681 + -0.000832*ln(x),134,10.960663313432837,3.9096921347220377,0.012741731343283583,0.006619138380331145
|
||||
power,Chlorophyll,617.627014,-0.02980562740210102,y = 0.022702 * x^-0.275804,134,10.960663313432837,3.9096921347220377,0.012741731343283583,0.006619138380331145
|
||||
exponential,Chlorophyll,617.627014,-0.030009751236651283,y = 0.016267 * exp(-0.028218*x),134,10.960663313432837,3.9096921347220377,0.012741731343283583,0.006619138380331145
|
||||
|
@ -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,621.853027,0.002549517816160085,y = 0.013405 + -0.000087*x,134,10.960663313432837,3.9096921347220377,0.012447716417910447,0.006761993519104926
|
||||
logarithmic,Chlorophyll,621.853027,0.0017633821329291477,y = 0.014281 + -0.000787*ln(x),134,10.960663313432837,3.9096921347220377,0.012447716417910447,0.006761993519104926
|
||||
power,Chlorophyll,621.853027,-0.03557395594809787,y = 0.023522 * x^-0.304801,134,10.960663313432837,3.9096921347220377,0.012447716417910447,0.006761993519104926
|
||||
exponential,Chlorophyll,621.853027,-0.0360883361973503,y = 0.016274 * exp(-0.031185*x),134,10.960663313432837,3.9096921347220377,0.012447716417910447,0.006761993519104926
|
||||
|
@ -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,626.083008,0.00243086769672618,y = 0.013228 + -0.000086*x,134,10.960663313432837,3.9096921347220377,0.01228736567164179,0.006808151575409212
|
||||
logarithmic,Chlorophyll,626.083008,0.0016770114876123454,y = 0.014087 + -0.000773*ln(x),134,10.960663313432837,3.9096921347220377,0.01228736567164179,0.006808151575409212
|
||||
|
@ -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,630.315979,0.0020507276873319435,y = 0.013084 + -0.000080*x,134,10.960663313432837,3.9096921347220377,0.012202574626865673,0.0069447202176291635
|
||||
logarithmic,Chlorophyll,630.315979,0.0014098383321846653,y = 0.013886 + -0.000723*ln(x),134,10.960663313432837,3.9096921347220377,0.012202574626865673,0.0069447202176291635
|
||||
power,Chlorophyll,630.315979,-0.037502977238619506,y = 0.023711 * x^-0.319128,134,10.960663313432837,3.9096921347220377,0.012202574626865673,0.0069447202176291635
|
||||
exponential,Chlorophyll,630.315979,-0.03824118768398388,y = 0.016114 * exp(-0.032602*x),134,10.960663313432837,3.9096921347220377,0.012202574626865673,0.0069447202176291635
|
||||
|
@ -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,634.552002,0.0017288451024077833,y = 0.012968 + -0.000074*x,134,10.960663313432837,3.9096921347220377,0.012159492537313433,0.006937426491471543
|
||||
logarithmic,Chlorophyll,634.552002,0.0011589143811774338,y = 0.013684 + -0.000654*ln(x),134,10.960663313432837,3.9096921347220377,0.012159492537313433,0.006937426491471543
|
||||
|
@ -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,638.791992,0.0011243091081014622,y = 0.012803 + -0.000061*x,134,10.960663313432837,3.9096921347220377,0.012136574626865673,0.007088601297076063
|
||||
logarithmic,Chlorophyll,638.791992,0.0007004467304244644,y = 0.013348 + -0.000520*ln(x),134,10.960663313432837,3.9096921347220377,0.012136574626865673,0.007088601297076063
|
||||
power,Chlorophyll,638.791992,-0.039341503691473934,y = 0.023270 * x^-0.314552,134,10.960663313432837,3.9096921347220377,0.012136574626865673,0.007088601297076063
|
||||
exponential,Chlorophyll,638.791992,-0.040722297966500065,y = 0.015930 * exp(-0.032288*x),134,10.960663313432837,3.9096921347220377,0.012136574626865673,0.007088601297076063
|
||||
|
@ -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,643.034973,0.0006784732036110297,y = 0.012527 + -0.000048*x,134,10.960663313432837,3.9096921347220377,0.012004380597014924,0.007158811687218512
|
||||
logarithmic,Chlorophyll,643.034973,0.0003810729347989428,y = 0.012907 + -0.000387*ln(x),134,10.960663313432837,3.9096921347220377,0.012004380597014924,0.007158811687218512
|
||||
power,Chlorophyll,643.034973,-0.02081846389883313,y = 0.018994 * x^-0.223479,134,10.960663313432837,3.9096921347220377,0.012004380597014924,0.007158811687218512
|
||||
exponential,Chlorophyll,643.034973,-0.02135362421636633,y = 0.014510 * exp(-0.022936*x),134,10.960663313432837,3.9096921347220377,0.012004380597014924,0.007158811687218512
|
||||
|
@ -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,647.281006,0.0003862335627651259,y = 0.012126 + -0.000037*x,134,10.960663313432837,3.9096921347220377,0.01172576119402985,0.007268962283181486
|
||||
logarithmic,Chlorophyll,647.281006,0.00018807939298448595,y = 0.012369 + -0.000276*ln(x),134,10.960663313432837,3.9096921347220377,0.01172576119402985,0.007268962283181486
|
||||
power,Chlorophyll,647.281006,-0.01877601035356702,y = 0.017959 * x^-0.209287,134,10.960663313432837,3.9096921347220377,0.01172576119402985,0.007268962283181486
|
||||
exponential,Chlorophyll,647.281006,-0.019328048239278806,y = 0.013958 * exp(-0.021494*x),134,10.960663313432837,3.9096921347220377,0.01172576119402985,0.007268962283181486
|
||||
|
@ -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,651.531006,0.00013536784274881253,y = 0.011403 + -0.000022*x,134,10.960663313432837,3.9096921347220377,0.011164671641791043,0.007310371385073625
|
||||
logarithmic,Chlorophyll,651.531006,3.4274085199070825e-05,y = 0.011441 + -0.000119*ln(x),134,10.960663313432837,3.9096921347220377,0.011164671641791043,0.007310371385073625
|
||||
power,Chlorophyll,651.531006,-0.01999886130480988,y = 0.017201 * x^-0.213892,134,10.960663313432837,3.9096921347220377,0.011164671641791043,0.007310371385073625
|
||||
exponential,Chlorophyll,651.531006,-0.02080973971468425,y = 0.013316 * exp(-0.022113*x),134,10.960663313432837,3.9096921347220377,0.011164671641791043,0.007310371385073625
|
||||
|
@ -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,655.784973,6.712523797403058e-05,y = 0.010412 + -0.000016*x,134,10.960663313432837,3.9096921347220377,0.010241910447761193,0.007414076922911311
|
||||
logarithmic,Chlorophyll,655.784973,7.939365778253382e-06,y = 0.010377 + -0.000058*ln(x),134,10.960663313432837,3.9096921347220377,0.010241910447761193,0.007414076922911311
|
||||
power,Chlorophyll,655.784973,-0.02451200929710562,y = 0.017204 * x^-0.257829,134,10.960663313432837,3.9096921347220377,0.010241910447761193,0.007414076922911311
|
||||
exponential,Chlorophyll,655.784973,-0.025546357658017715,y = 0.012631 * exp(-0.026616*x),134,10.960663313432837,3.9096921347220377,0.010241910447761193,0.007414076922911311
|
||||
|
@ -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,660.041016,2.3065421406842646e-05,y = 0.009045 + -0.000009*x,134,10.960663313432837,3.9096921347220377,0.008946089552238806,0.007372050533373144
|
||||
logarithmic,Chlorophyll,660.041016,8.478475440609756e-07,y = 0.008902 + 0.000019*ln(x),134,10.960663313432837,3.9096921347220377,0.008946089552238806,0.007372050533373144
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user