格式统一

This commit is contained in:
duxin
2026-07-01 09:57:27 +08:00
parent c793ea2204
commit a3c20d3e49
37 changed files with 2286 additions and 1978 deletions

View File

@ -6,6 +6,8 @@
以及水体掩膜的预处理逻辑。
"""
import os
import re
import time
from pathlib import Path
from typing import Optional, Union
@ -61,6 +63,9 @@ def prepare_water_mask_for_algorithm(
# 字符串路径
if isinstance(water_mask, str):
# ★ 入口防御:自动将临时文件路径修正为稳定文件路径
water_mask = _resolve_stable_mask_path(water_mask)
ext = Path(water_mask).suffix.lower()
# shapefile 格式
@ -113,24 +118,83 @@ def _convert_shp_to_mask(shp_path: str, img_path: str,
return _load_raster_mask(temp_mask_path, image_shape[0], image_shape[1])
def _load_raster_mask(mask_path: str, img_height: int, img_width: int) -> np.ndarray:
"""从栅格文件加载掩膜"""
# ── 临时文件关键词(与 workspace_manager.TMP_KEYWORDS 保持一致)──
_TMP_PATTERN = re.compile(r'(__tmp|_tmp_delete|_tmp)(?=\.\w+$|$)', re.IGNORECASE)
def _resolve_stable_mask_path(mask_path: str) -> str:
"""如果 mask_path 指向临时文件,尝试解析为对应的稳定文件。
例如: "water_mask_out__tmp_delete.dat" → "water_mask_out.dat"
"""
if not _TMP_PATTERN.search(os.path.basename(mask_path)):
return mask_path
stable = _TMP_PATTERN.sub('', os.path.basename(mask_path))
stable_path = os.path.join(os.path.dirname(mask_path), stable)
if os.path.isfile(stable_path):
print(f" [mask_converter] 自动将临时路径解析为稳定文件: "
f"{os.path.basename(mask_path)} → {stable}")
return stable_path
# 尝试去除扩展名中多余的重复段(如 _tmp_delete 前后的双下划线残留)
stable2 = re.sub(r'_{2,}', '_', stable)
stable2_path = os.path.join(os.path.dirname(mask_path), stable2)
if os.path.isfile(stable2_path):
print(f" [mask_converter] 自动将临时路径解析为稳定文件: "
f"{os.path.basename(mask_path)} → {stable2}")
return stable2_path
return mask_path
def _load_raster_mask(mask_path: str, img_height: int, img_width: int,
retry_count: int = 3, retry_delay: float = 0.5) -> np.ndarray:
"""从栅格文件加载掩膜(带临时文件自动修复 + 重试机制)。
2026-06-30 修复:
- 自动将 __tmp_delete / _tmp 类临时路径解析为对应的稳定文件名
- 若文件不存在,短暂等待后重试(应对并发写入/删除的 TOCTOU 竞态)
"""
if not GDAL_AVAILABLE:
raise ImportError("GDAL未安装,无法读取掩膜文件")
mask_dataset = gdal.Open(mask_path, gdal.GA_ReadOnly)
if mask_dataset is None:
raise ValueError(f"无法打开掩膜文件: {mask_path}")
# ★ 临时文件 → 稳定文件自动修正
mask_path = _resolve_stable_mask_path(mask_path)
try:
mask_array = mask_dataset.GetRasterBand(1).ReadAsArray()
finally:
mask_dataset = None
last_error = None
for attempt in range(1, retry_count + 1):
if not os.path.isfile(mask_path):
last_error = FileNotFoundError(
f"掩膜文件不存在: {mask_path}"
)
if attempt < retry_count:
time.sleep(retry_delay)
continue
raise last_error
if mask_array.shape != (img_height, img_width):
raise ValueError(f"掩膜尺寸 {mask_array.shape} 与图像尺寸 {(img_height, img_width)} 不匹配")
mask_dataset = gdal.Open(mask_path, gdal.GA_ReadOnly)
if mask_dataset is None:
last_error = ValueError(f"无法打开掩膜文件: {mask_path}")
if attempt < retry_count:
time.sleep(retry_delay)
continue
raise last_error
return (mask_array > 0).astype(np.uint8)
try:
mask_array = mask_dataset.GetRasterBand(1).ReadAsArray()
finally:
mask_dataset = None
if mask_array.shape != (img_height, img_width):
raise ValueError(
f"掩膜尺寸 {mask_array.shape} 与图像尺寸 {(img_height, img_width)} 不匹配"
)
return (mask_array > 0).astype(np.uint8)
# 不应到达这里,但保持类型安全
raise last_error
def ensure_water_mask_dat(img_path: str,