**核心修复 (preview_generator.py):** - 废弃 _align_mask_to_image() (numpy crop/pad 在地理空间上错误) - 新增 _warp_mask_to_image(): 使用 gdal.Warp 将掩膜重采样到与底图 完全一致的像素网格,处理旋转/偏移/投影差异 - 新增 _normalize_mask(nodata_value): 正确的掩膜值域自劢归一间 (0/1 vs 0/255) - 修复 alpha = mask_data/255.0 → mask_data (掩膜是二值 0/1, 不是 0/255) - 面积计算 valid_pixels 使用 warp 前数据排除 nodata 背景 **新增防御工具模块:** - spatial_validator.py: SpatialAlignmentError 自定义异常 + validate_two_rasters() / validate_spatial_alignment() 强制空间一致性检查 + has_rotation() / get_pixel_resolution() 诊断工具 - nodata_handler.py: read_band_safe() / read_bands_safe() 自动 NoData→nan + read_band_masked() 返回 MaskedArray + create_valid_mask() **P0 高危代码集成 (3处):** - sampling.py: 耀斑掩膜与水体掩膜 bool 运算前验证 numpy shape 一致 - find_severe_glint_area.py: 栅格 mask 读取后验证 dims+GT+projection 对齐 - waterindex_inversion/__init__.py: mask 与 BSQ 维度验证, 不一致时优雅降级 **旋转影像兼容性修复:** - extract_water_area.py: pixel_size = sqrt(gt[1]^2+gt[2]^2) 使用勾股定理 正确计算旋转影像的像素分辨率
224 lines
8.7 KiB
Python
224 lines
8.7 KiB
Python
from src.utils.util import *
|
||
from osgeo import gdal, ogr
|
||
import argparse
|
||
gdal.UseExceptions()
|
||
ogr.UseExceptions()
|
||
|
||
def xml2shp():
|
||
pass
|
||
|
||
|
||
def rasterize_envi_xml(shp_filepath):
|
||
pass
|
||
|
||
|
||
@timeit
|
||
def rasterize_shp(shp_filepath, raster_fn_out, img_path, NoData_value=None):
|
||
# ---------- 防御性处理:路径标准化 ----------
|
||
shp_filepath = os.path.abspath(shp_filepath).replace('\\', '/')
|
||
print(f"[DEBUG rasterize_shp] 标准化后的 SHP 路径: {shp_filepath}")
|
||
|
||
# 检查伴随文件完整性
|
||
shp_base = os.path.splitext(shp_filepath)[0]
|
||
for ext in ['.dbf', '.shx', '.prj']:
|
||
companion = shp_base + ext
|
||
if os.path.exists(companion):
|
||
print(f"[DEBUG rasterize_shp] 伴随文件存在: {companion}")
|
||
else:
|
||
print(f"[WARNING rasterize_shp] 伴随文件缺失: {companion}")
|
||
|
||
# 确保 GDAL/OGR 驱动已注册
|
||
gdal.AllRegister()
|
||
ogr.RegisterAll()
|
||
|
||
# 检查 ESRI Shapefile 驱动
|
||
driver = ogr.GetDriverByName("ESRI Shapefile")
|
||
if driver is None:
|
||
raise RuntimeError(
|
||
"系统中未找到 ESRI Shapefile 驱动!请检查 GDAL 是否正确安装及是否包含 Shapefile 支持。"
|
||
)
|
||
print(f"[DEBUG rasterize_shp] ESRI Shapefile 驱动: {driver.GetName()}")
|
||
|
||
# 打开参考影像获取尺寸信息
|
||
dataset = gdal.Open(img_path)
|
||
if dataset is None:
|
||
raise ValueError(f"无法打开参考影像文件: {img_path}")
|
||
im_width = dataset.RasterXSize
|
||
im_height = dataset.RasterYSize
|
||
geotransform = dataset.GetGeoTransform()
|
||
imgdata_in = dataset.GetRasterBand(1).ReadAsArray()
|
||
del dataset
|
||
|
||
# ---------- 打开 SHP 文件(双重尝试获取详细错误) ----------
|
||
source_ds = gdal.OpenEx(shp_filepath, gdal.OF_VECTOR)
|
||
if source_ds is None:
|
||
# gdal.OpenEx 失败,尝试 ogr.Open 获取更详细的错误信息
|
||
try:
|
||
ogr_ds = ogr.Open(shp_filepath)
|
||
except Exception as ogr_err:
|
||
raise RuntimeError(
|
||
f"GDAL/OGR 无法打开 SHP 文件(详细原因):\n"
|
||
f" ogr.Open 抛出异常: {str(ogr_err)}\n"
|
||
f" 文件路径: {shp_filepath}\n"
|
||
f"常见原因:\n"
|
||
f" 1. 路径包含中文/空格/特殊字符(建议复制到纯英文路径下重试)\n"
|
||
f" 2. .dbf 或 .shx 伴随文件缺失或损坏\n"
|
||
f" 3. GDAL 未注册 ESRI Shapefile 驱动\n"
|
||
f" 4. 文件被其他程序锁定"
|
||
)
|
||
if ogr_ds is None:
|
||
raise RuntimeError(
|
||
f"ogr.Open 和 gdal.OpenEx 均返回 None,无法打开 SHP 文件。\n"
|
||
f"文件路径: {shp_filepath}\n"
|
||
f"请检查:\n"
|
||
f" 1. 所有伴随文件(.dbf/.shx/.prg)是否齐全\n"
|
||
f" 2. 文件是否被其他程序占用\n"
|
||
f" 3. 路径中是否存在不支持的字符"
|
||
)
|
||
|
||
# 检查图层数量,如果有多层,指定使用第一层
|
||
layer_count = source_ds.GetLayerCount()
|
||
layer_name = None
|
||
if layer_count > 1:
|
||
print(f"警告: shapefile包含{layer_count}个图层,将使用第一个图层进行栅格化")
|
||
# 获取第一个图层
|
||
layer = source_ds.GetLayer(0)
|
||
layer_name = layer.GetName()
|
||
|
||
# 计算像素分辨率(考虑旋转参数)
|
||
# 对于旋转影像(gt[2] 或 gt[4] != 0),像素实际分辨率需用勾股定理
|
||
pixel_size_x = np.sqrt(geotransform[1]**2 + geotransform[2]**2)
|
||
pixel_size_y = np.sqrt(geotransform[4]**2 + geotransform[5]**2)
|
||
raster_fn_out_tmp = append2filename(raster_fn_out, "_tmp_delete")
|
||
|
||
# 构建栅格化参数
|
||
rasterize_kwargs = {
|
||
'format': 'envi',
|
||
'outputType': gdal.GDT_Byte,
|
||
'noData': NoData_value,
|
||
'initValues': NoData_value,
|
||
'xRes': pixel_size_x,
|
||
'yRes': pixel_size_y,
|
||
'allTouched': True,
|
||
'burnValues': 1
|
||
}
|
||
|
||
# 如果有多层,指定使用第一层
|
||
if layer_name is not None:
|
||
rasterize_kwargs['layers'] = [layer_name]
|
||
|
||
# 执行栅格化
|
||
gdal.Rasterize(raster_fn_out_tmp, source_ds, **rasterize_kwargs)
|
||
|
||
dataset_tmp = gdal.Open(raster_fn_out_tmp)
|
||
geotransform_tmp = dataset_tmp.GetGeoTransform()
|
||
inv_geotransform_tmp = gdal.InvGeoTransform(geotransform_tmp)
|
||
data_tmp = dataset_tmp.GetRasterBand(1).ReadAsArray()
|
||
del dataset_tmp
|
||
|
||
# 创建和输入影像相同行列号、相同分辨率的水域掩膜,方便后续使用
|
||
water_mask = np.zeros((im_height, im_width))
|
||
for row in range(im_height):
|
||
for column in range(im_width):
|
||
coor = gdal.ApplyGeoTransform(geotransform, column, row)
|
||
|
||
coor_pixel = gdal.ApplyGeoTransform(inv_geotransform_tmp, coor[0], coor[1])
|
||
coor_pixel = [int(num) for num in coor_pixel]
|
||
|
||
if coor_pixel[0] < 0 or coor_pixel[0] >= data_tmp.shape[1]:
|
||
continue
|
||
if coor_pixel[1] < 0 or coor_pixel[1] >= data_tmp.shape[0]:
|
||
continue
|
||
|
||
if imgdata_in[row, column] == 0: # 当shp区域比影像区域大时,略过
|
||
continue
|
||
|
||
water_mask[row, column] = data_tmp[coor_pixel[1], coor_pixel[0]]
|
||
|
||
write_bands(img_path, raster_fn_out, water_mask)
|
||
|
||
os.remove(raster_fn_out_tmp)
|
||
|
||
|
||
def calculate_NDWI(green_bandnumber, nir_bandnumber, filename):
|
||
dataset = gdal.Open(filename) # 打开文件
|
||
num_bands = dataset.RasterCount # 栅格矩阵的波段数
|
||
im_geotrans = dataset.GetGeoTransform() # 仿射矩阵
|
||
im_proj = dataset.GetProjection() # 地图投影信息
|
||
|
||
tmp = dataset.GetRasterBand(green_bandnumber + 1) # 波段计数从1开始
|
||
band_green = tmp.ReadAsArray().astype(np.int16)
|
||
tmp = dataset.GetRasterBand(nir_bandnumber + 1) # 波段计数从1开始
|
||
band_nir = tmp.ReadAsArray().astype(np.int16)
|
||
|
||
ndwi = (band_green - band_nir) / (band_green + band_nir)
|
||
|
||
del dataset
|
||
|
||
return ndwi
|
||
|
||
|
||
def extract_water(ndwi, threshold=0.3, data_ignore_value=0):
|
||
water_region = np.where(ndwi > threshold, 1, data_ignore_value)
|
||
|
||
return water_region
|
||
|
||
|
||
def ndwi(file_path, ndwi_threshold=0.4, output_path=None, data_ignore_value=0):
|
||
if output_path is None:
|
||
output_path = append2filename(file_path, "_waterarea")
|
||
|
||
dataset_in = gdal.Open(file_path)
|
||
im_width_in = dataset_in.RasterXSize # 栅格矩阵的列数
|
||
im_height_in = dataset_in.RasterYSize # 栅格矩阵的行数
|
||
num_bands_in = dataset_in.RasterCount # 栅格矩阵的波段数
|
||
geotrans_in = dataset_in.GetGeoTransform() # 仿射矩阵
|
||
proj_in = dataset_in.GetProjection() # 地图投影信息
|
||
del dataset_in
|
||
|
||
green_wave = 552.19
|
||
nir_wave = 809.2890
|
||
green_band_number = find_band_number(green_wave, file_path)
|
||
nir_band_number = find_band_number(nir_wave, file_path)
|
||
|
||
ndwi = calculate_NDWI(green_band_number, nir_band_number, file_path)
|
||
|
||
water_binary = extract_water(ndwi, threshold=ndwi_threshold) # 0.4
|
||
|
||
write_bands(file_path, output_path, water_binary)
|
||
|
||
return output_path
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="此程序用于提取水域区域,输出的水域栅格和输入的影像具有相同的行列数。")
|
||
|
||
# parser.add_argument("--global_arg", type=str, help="A global argument for all modes", required=True)
|
||
|
||
# 创建子命令解析器
|
||
subparsers = parser.add_subparsers(dest="algorithm", required=True, help="Choose a mode")
|
||
|
||
rasterize_shp_ = subparsers.add_parser("rasterize_shp", help="Mode 1 description")
|
||
rasterize_shp_.add_argument('-i1', '--img_path', type=str, required=True, help='输入影像文件的路径')
|
||
rasterize_shp_.add_argument('-i2', '--shp_path', type=str, required=True, help='输入shp文件的路径')
|
||
rasterize_shp_.add_argument('-o', '--water_mask_outpath', required=True, type=str, help='输出水体掩膜文件的路径')
|
||
rasterize_shp_.set_defaults(func=rasterize_shp)
|
||
|
||
ndwi_ = subparsers.add_parser("ndwi", help="Mode 2 description")
|
||
ndwi_.add_argument('-i1', '--img_path', type=str, required=True, help='输入影像文件的路径')
|
||
ndwi_.add_argument('-i2', '--ndwi_threshold', type=float, required=True, help='输入ndwi水体阈值,大于此值的为水域')
|
||
ndwi_.add_argument('-o', '--water_mask_outpath', required=True, type=str, help='输出水体掩膜文件的路径')
|
||
ndwi_.set_defaults(func=ndwi)
|
||
|
||
# 解析参数
|
||
args = parser.parse_args()
|
||
if args.algorithm == "rasterize_shp":
|
||
args.func(args.shp_path, args.water_mask_outpath, args.img_path)
|
||
elif args.algorithm == "ndwi":
|
||
args.func(args.img_path, args.ndwi_threshold, args.water_mask_outpath)
|
||
|
||
|
||
# Press the green button in the gutter to run the script.
|
||
if __name__ == '__main__':
|
||
main()
|