feat: 各步骤产物改为原子化落盘,复用前校验 .done 完成标记
跳过条件从 Path.exists() 改为 is_file_complete(),产物成功落盘后统一补 mark_file_complete()。涉及 step1 水域掩膜、step3 去耀斑、SHP 栅格化缓存三处快路径。 step3 去耀斑新增清理分支:无 .done 标记的旧文件一律视为半成品,先删除再重跑(Kutser/Goodman/Hedley/SUGAR 四种方法一致)。 模型落盘改为 atomic_filepath:modeling_batch 批量建模与 automl_trainer 的 AutoML 结果均先写 .__wip 再原子替换,避免中断产生半个 joblib 文件。
This commit is contained in:
@ -824,7 +824,9 @@ class WaterQualityModelingBatch:
|
|||||||
'metadata': metadata
|
'metadata': metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
joblib.dump(save_data, filepath)
|
from src.utils.util import atomic_filepath
|
||||||
|
with atomic_filepath(str(filepath)) as _tmp:
|
||||||
|
joblib.dump(save_data, _tmp)
|
||||||
print(f"模型已保存: {filepath}")
|
print(f"模型已保存: {filepath}")
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
|||||||
@ -455,11 +455,12 @@ def train_with_automl(
|
|||||||
if final_model is None:
|
if final_model is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 保存
|
# 保存(★ 原子写入:先 .__wip,成功后同卷替换 + .done)
|
||||||
import joblib
|
import joblib
|
||||||
|
from src.utils.util import atomic_filepath
|
||||||
fname = f"{tgt}_{preproc}_{model_name}_AUTOML.joblib"
|
fname = f"{tgt}_{preproc}_{model_name}_AUTOML.joblib"
|
||||||
fpath = preproc_dir / fname
|
fpath = preproc_dir / fname
|
||||||
joblib.dump({
|
_save_data = {
|
||||||
"model": final_model,
|
"model": final_model,
|
||||||
"target_column_name": tgt,
|
"target_column_name": tgt,
|
||||||
"preprocess_method": preproc,
|
"preprocess_method": preproc,
|
||||||
@ -475,7 +476,9 @@ def train_with_automl(
|
|||||||
"was_subsampled": was_sub,
|
"was_subsampled": was_sub,
|
||||||
"split_method": split_method,
|
"split_method": split_method,
|
||||||
},
|
},
|
||||||
}, fpath)
|
}
|
||||||
|
with atomic_filepath(str(fpath)) as _tmp:
|
||||||
|
joblib.dump(_save_data, _tmp)
|
||||||
|
|
||||||
cand = AutoMLResult(
|
cand = AutoMLResult(
|
||||||
success=True,
|
success=True,
|
||||||
|
|||||||
@ -300,6 +300,7 @@ class GlintRemovalStep:
|
|||||||
from src.core.utils.mask_converter import (
|
from src.core.utils.mask_converter import (
|
||||||
prepare_water_mask_for_algorithm as _default_prepare,
|
prepare_water_mask_for_algorithm as _default_prepare,
|
||||||
)
|
)
|
||||||
|
from src.utils.util import is_file_complete, mark_file_complete
|
||||||
|
|
||||||
# 使用提供的函数或默认函数
|
# 使用提供的函数或默认函数
|
||||||
if _get_image_geo_info is None:
|
if _get_image_geo_info is None:
|
||||||
@ -428,10 +429,19 @@ class GlintRemovalStep:
|
|||||||
final_bsq = hardcoded_bsq
|
final_bsq = hardcoded_bsq
|
||||||
final_hdr = hardcoded_hdr
|
final_hdr = hardcoded_hdr
|
||||||
|
|
||||||
if Path(hardcoded_bsq).exists():
|
if is_file_complete(hardcoded_bsq):
|
||||||
print(f"检测到已存在的去耀斑影像文件,直接使用: {hardcoded_bsq}")
|
print(f"检测到完整且已完成的去耀斑影像,直接使用: {hardcoded_bsq}")
|
||||||
notify("skipped", f"去耀斑影像已设置: {hardcoded_bsq}")
|
notify("skipped", f"去耀斑影像已设置: {hardcoded_bsq}")
|
||||||
return _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
return _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
||||||
|
else:
|
||||||
|
# 清理上次中断遗留的半成品脏数据(无 .done 标记一律视为不完整)
|
||||||
|
for _stale in (hardcoded_bsq, hardcoded_hdr, hardcoded_bsq + '.hdr'):
|
||||||
|
if _stale and Path(_stale).exists():
|
||||||
|
print(f"清理上次中断遗留的半成品数据: {_stale}")
|
||||||
|
try:
|
||||||
|
Path(_stale).unlink()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
kutser = Kutser(
|
kutser = Kutser(
|
||||||
img_path,
|
img_path,
|
||||||
@ -448,6 +458,7 @@ class GlintRemovalStep:
|
|||||||
if Path(hardcoded_bsq).exists():
|
if Path(hardcoded_bsq).exists():
|
||||||
_copy_hdr_info(img_path, hardcoded_bsq)
|
_copy_hdr_info(img_path, hardcoded_bsq)
|
||||||
final = _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
final = _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
||||||
|
mark_file_complete(final) # ★ 原子化成功标记,防半成品复用
|
||||||
notify("completed", f"去耀斑影像已生成: {final}")
|
notify("completed", f"去耀斑影像已生成: {final}")
|
||||||
return final
|
return final
|
||||||
raise RuntimeError(f"Kutser算法未生成输出文件: {hardcoded_bsq}")
|
raise RuntimeError(f"Kutser算法未生成输出文件: {hardcoded_bsq}")
|
||||||
@ -466,10 +477,19 @@ class GlintRemovalStep:
|
|||||||
final_bsq = hardcoded_bsq
|
final_bsq = hardcoded_bsq
|
||||||
final_hdr = hardcoded_hdr
|
final_hdr = hardcoded_hdr
|
||||||
|
|
||||||
if Path(hardcoded_bsq).exists():
|
if is_file_complete(hardcoded_bsq):
|
||||||
print(f"检测到已存在的去耀斑影像文件,直接使用: {hardcoded_bsq}")
|
print(f"检测到完整且已完成的去耀斑影像,直接使用: {hardcoded_bsq}")
|
||||||
notify("skipped", f"去耀斑影像已设置: {hardcoded_bsq}")
|
notify("skipped", f"去耀斑影像已设置: {hardcoded_bsq}")
|
||||||
return _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
return _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
||||||
|
else:
|
||||||
|
# 清理上次中断遗留的半成品脏数据(无 .done 标记一律视为不完整)
|
||||||
|
for _stale in (hardcoded_bsq, hardcoded_hdr, hardcoded_bsq + '.hdr'):
|
||||||
|
if _stale and Path(_stale).exists():
|
||||||
|
print(f"清理上次中断遗留的半成品数据: {_stale}")
|
||||||
|
try:
|
||||||
|
Path(_stale).unlink()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
goodman = Goodman(
|
goodman = Goodman(
|
||||||
img_path,
|
img_path,
|
||||||
@ -489,6 +509,7 @@ class GlintRemovalStep:
|
|||||||
del corrected_bands
|
del corrected_bands
|
||||||
|
|
||||||
final = _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
final = _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
||||||
|
mark_file_complete(final) # ★ 原子化成功标记,防半成品复用
|
||||||
notify("completed", f"去耀斑影像已生成: {final}")
|
notify("completed", f"去耀斑影像已生成: {final}")
|
||||||
return final
|
return final
|
||||||
|
|
||||||
@ -504,10 +525,19 @@ class GlintRemovalStep:
|
|||||||
final_bsq = hardcoded_bsq
|
final_bsq = hardcoded_bsq
|
||||||
final_hdr = hardcoded_hdr
|
final_hdr = hardcoded_hdr
|
||||||
|
|
||||||
if Path(hardcoded_bsq).exists():
|
if is_file_complete(hardcoded_bsq):
|
||||||
print(f"检测到已存在的去耀斑影像文件,直接使用: {hardcoded_bsq}")
|
print(f"检测到完整且已完成的去耀斑影像,直接使用: {hardcoded_bsq}")
|
||||||
notify("skipped", f"去耀斑影像已设置: {hardcoded_bsq}")
|
notify("skipped", f"去耀斑影像已设置: {hardcoded_bsq}")
|
||||||
return _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
return _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
||||||
|
else:
|
||||||
|
# 清理上次中断遗留的半成品脏数据(无 .done 标记一律视为不完整)
|
||||||
|
for _stale in (hardcoded_bsq, hardcoded_hdr, hardcoded_bsq + '.hdr'):
|
||||||
|
if _stale and Path(_stale).exists():
|
||||||
|
print(f"清理上次中断遗留的半成品数据: {_stale}")
|
||||||
|
try:
|
||||||
|
Path(_stale).unlink()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
hedley = Hedley(
|
hedley = Hedley(
|
||||||
img_path,
|
img_path,
|
||||||
@ -521,6 +551,7 @@ class GlintRemovalStep:
|
|||||||
if Path(hardcoded_bsq).exists():
|
if Path(hardcoded_bsq).exists():
|
||||||
_copy_hdr_info(img_path, hardcoded_bsq)
|
_copy_hdr_info(img_path, hardcoded_bsq)
|
||||||
final = _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
final = _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
||||||
|
mark_file_complete(final) # ★ 原子化成功标记,防半成品复用
|
||||||
notify("completed", f"去耀斑影像已生成: {final}")
|
notify("completed", f"去耀斑影像已生成: {final}")
|
||||||
return final
|
return final
|
||||||
raise RuntimeError(f"Hedley算法未生成输出文件: {hardcoded_bsq}")
|
raise RuntimeError(f"Hedley算法未生成输出文件: {hardcoded_bsq}")
|
||||||
@ -547,10 +578,19 @@ class GlintRemovalStep:
|
|||||||
final_bsq = hardcoded_bsq
|
final_bsq = hardcoded_bsq
|
||||||
final_hdr = hardcoded_hdr
|
final_hdr = hardcoded_hdr
|
||||||
|
|
||||||
if Path(hardcoded_bsq).exists():
|
if is_file_complete(hardcoded_bsq):
|
||||||
print(f"检测到已存在的去耀斑影像文件,直接使用: {hardcoded_bsq}")
|
print(f"检测到完整且已完成的去耀斑影像,直接使用: {hardcoded_bsq}")
|
||||||
notify("skipped", f"去耀斑影像已设置: {hardcoded_bsq}")
|
notify("skipped", f"去耀斑影像已设置: {hardcoded_bsq}")
|
||||||
return _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
return _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
||||||
|
else:
|
||||||
|
# 清理上次中断遗留的半成品脏数据(无 .done 标记一律视为不完整)
|
||||||
|
for _stale in (hardcoded_bsq, hardcoded_hdr, hardcoded_bsq + '.hdr'):
|
||||||
|
if _stale and Path(_stale).exists():
|
||||||
|
print(f"清理上次中断遗留的半成品数据: {_stale}")
|
||||||
|
try:
|
||||||
|
Path(_stale).unlink()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
if sugar_bounds is None:
|
if sugar_bounds is None:
|
||||||
sugar_bounds = [(1, 2)]
|
sugar_bounds = [(1, 2)]
|
||||||
@ -569,6 +609,7 @@ class GlintRemovalStep:
|
|||||||
if Path(hardcoded_bsq).exists():
|
if Path(hardcoded_bsq).exists():
|
||||||
_copy_hdr_info(img_path, hardcoded_bsq)
|
_copy_hdr_info(img_path, hardcoded_bsq)
|
||||||
final = _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
final = _safe_rename(hardcoded_bsq, hardcoded_hdr, final_bsq, final_hdr)
|
||||||
|
mark_file_complete(final) # ★ 原子化成功标记,防半成品复用
|
||||||
notify("completed", f"去耀斑影像已生成: {final}")
|
notify("completed", f"去耀斑影像已生成: {final}")
|
||||||
return final
|
return final
|
||||||
raise RuntimeError(f"SUGAR算法未生成输出文件: {hardcoded_bsq}")
|
raise RuntimeError(f"SUGAR算法未生成输出文件: {hardcoded_bsq}")
|
||||||
|
|||||||
@ -46,6 +46,7 @@ class WaterMaskStep:
|
|||||||
dat 格式的水域掩膜文件路径
|
dat 格式的水域掩膜文件路径
|
||||||
"""
|
"""
|
||||||
from src.utils.extract_water_area import rasterize_shp, ndwi
|
from src.utils.extract_water_area import rasterize_shp, ndwi
|
||||||
|
from src.utils.util import is_file_complete, mark_file_complete
|
||||||
from src.core.utils.preview_generator import (
|
from src.core.utils.preview_generator import (
|
||||||
generate_image_preview,
|
generate_image_preview,
|
||||||
generate_water_mask_overlay,
|
generate_water_mask_overlay,
|
||||||
@ -85,12 +86,13 @@ class WaterMaskStep:
|
|||||||
ndwi_output_path = output_path or str(water_mask_dir / "water_mask_from_ndwi.dat")
|
ndwi_output_path = output_path or str(water_mask_dir / "water_mask_from_ndwi.dat")
|
||||||
os.makedirs(Path(ndwi_output_path).parent, exist_ok=True)
|
os.makedirs(Path(ndwi_output_path).parent, exist_ok=True)
|
||||||
|
|
||||||
if Path(ndwi_output_path).exists():
|
if is_file_complete(ndwi_output_path):
|
||||||
print(f"检测到已存在的NDWI掩膜文件,直接使用: {ndwi_output_path}")
|
print(f"检测到完整且已完成的NDWI掩膜文件,直接使用: {ndwi_output_path}")
|
||||||
notify("skipped", f"水域掩膜已设置: {ndwi_output_path}")
|
notify("skipped", f"水域掩膜已设置: {ndwi_output_path}")
|
||||||
return ndwi_output_path
|
return ndwi_output_path
|
||||||
|
|
||||||
ndwi(img_path, ndwi_threshold, ndwi_output_path)
|
ndwi(img_path, ndwi_threshold, ndwi_output_path)
|
||||||
|
mark_file_complete(ndwi_output_path)
|
||||||
|
|
||||||
if generate_png:
|
if generate_png:
|
||||||
overlay_path = water_mask_dir / "water_mask_overlay.png"
|
overlay_path = water_mask_dir / "water_mask_overlay.png"
|
||||||
@ -119,8 +121,8 @@ class WaterMaskStep:
|
|||||||
shp_output_path = output_path or str(water_mask_dir / "water_mask_from_shp.dat")
|
shp_output_path = output_path or str(water_mask_dir / "water_mask_from_shp.dat")
|
||||||
os.makedirs(Path(shp_output_path).parent, exist_ok=True)
|
os.makedirs(Path(shp_output_path).parent, exist_ok=True)
|
||||||
|
|
||||||
if Path(shp_output_path).exists():
|
if is_file_complete(shp_output_path):
|
||||||
print(f"检测到已存在的栅格化掩膜文件,直接使用: {shp_output_path}")
|
print(f"检测到完整且已完成的栅格化掩膜文件,直接使用: {shp_output_path}")
|
||||||
notify("skipped", f"水域掩膜已设置: {shp_output_path}")
|
notify("skipped", f"水域掩膜已设置: {shp_output_path}")
|
||||||
if generate_png:
|
if generate_png:
|
||||||
overlay_path = water_mask_dir / "water_mask_overlay.png"
|
overlay_path = water_mask_dir / "water_mask_overlay.png"
|
||||||
@ -130,6 +132,7 @@ class WaterMaskStep:
|
|||||||
|
|
||||||
safe_mask_path = os.path.abspath(mask_path).replace("\\", "/")
|
safe_mask_path = os.path.abspath(mask_path).replace("\\", "/")
|
||||||
rasterize_shp(safe_mask_path, shp_output_path, img_path)
|
rasterize_shp(safe_mask_path, shp_output_path, img_path)
|
||||||
|
mark_file_complete(shp_output_path)
|
||||||
|
|
||||||
if generate_png:
|
if generate_png:
|
||||||
overlay_path = water_mask_dir / "water_mask_overlay.png"
|
overlay_path = water_mask_dir / "water_mask_overlay.png"
|
||||||
|
|||||||
@ -94,6 +94,7 @@ def _convert_shp_to_mask(shp_path: str, img_path: str,
|
|||||||
callback=None) -> np.ndarray:
|
callback=None) -> np.ndarray:
|
||||||
"""将 shapefile 栅格化为掩膜数组"""
|
"""将 shapefile 栅格化为掩膜数组"""
|
||||||
from src.utils.extract_water_area import rasterize_shp
|
from src.utils.extract_water_area import rasterize_shp
|
||||||
|
from src.utils.util import is_file_complete, mark_file_complete
|
||||||
|
|
||||||
safe_shp_path = os.path.abspath(shp_path).replace('\\', '/')
|
safe_shp_path = os.path.abspath(shp_path).replace('\\', '/')
|
||||||
shp_name = Path(safe_shp_path).stem
|
shp_name = Path(safe_shp_path).stem
|
||||||
@ -103,9 +104,9 @@ def _convert_shp_to_mask(shp_path: str, img_path: str,
|
|||||||
else:
|
else:
|
||||||
temp_mask_path = f"/tmp/water_mask_{shp_name}.dat"
|
temp_mask_path = f"/tmp/water_mask_{shp_name}.dat"
|
||||||
|
|
||||||
# 缓存:已栅格化则直接读取
|
# 缓存:已完成(带 .done 标记)才直接读取,否则重新栅格化
|
||||||
if Path(temp_mask_path).exists():
|
if is_file_complete(temp_mask_path):
|
||||||
print(f"使用已存在的栅格化掩膜: {temp_mask_path}")
|
print(f"使用已完成的栅格化掩膜: {temp_mask_path}")
|
||||||
return _load_raster_mask(temp_mask_path, image_shape[0], image_shape[1])
|
return _load_raster_mask(temp_mask_path, image_shape[0], image_shape[1])
|
||||||
|
|
||||||
# 需要栅格化
|
# 需要栅格化
|
||||||
@ -114,6 +115,7 @@ def _convert_shp_to_mask(shp_path: str, img_path: str,
|
|||||||
|
|
||||||
print(f"正在将 SHP 栅格化: {safe_shp_path}")
|
print(f"正在将 SHP 栅格化: {safe_shp_path}")
|
||||||
rasterize_shp(safe_shp_path, temp_mask_path, img_path)
|
rasterize_shp(safe_shp_path, temp_mask_path, img_path)
|
||||||
|
mark_file_complete(temp_mask_path)
|
||||||
|
|
||||||
return _load_raster_mask(temp_mask_path, image_shape[0], image_shape[1])
|
return _load_raster_mask(temp_mask_path, image_shape[0], image_shape[1])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user