格式统一
This commit is contained in:
@ -164,6 +164,24 @@ class LogManager(QObject):
|
||||
if self._log_text is not None:
|
||||
self._log_text.clear()
|
||||
|
||||
def info(self, message: str):
|
||||
"""便捷方法:发布 info 级别日志。"""
|
||||
global_event_bus.publish('LogMessage', {
|
||||
'message': message, 'level': 'info',
|
||||
})
|
||||
|
||||
def warning(self, message: str):
|
||||
"""便捷方法:发布 warning 级别日志。"""
|
||||
global_event_bus.publish('LogMessage', {
|
||||
'message': message, 'level': 'warning',
|
||||
})
|
||||
|
||||
def error(self, message: str):
|
||||
"""便捷方法:发布 error 级别日志。"""
|
||||
global_event_bus.publish('LogMessage', {
|
||||
'message': message, 'level': 'error',
|
||||
})
|
||||
|
||||
@property
|
||||
def progress_bar(self) -> QProgressBar:
|
||||
return self._progress_bar
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.QtWidgets import QWidget, QTabWidget, QScrollArea, QSpinBox, QDoubleSpinBox, QComboBox
|
||||
from PyQt5.QtCore import Qt
|
||||
@ -301,6 +302,16 @@ class PanelFactory:
|
||||
if not os.path.exists(absolute_path):
|
||||
continue
|
||||
|
||||
# ★ 2026-07-01 加强:若是目录,必须非空(至少含 1 个文件),
|
||||
# 防止 PipelineContext 预创建的空目录(如 9_ML_Prediction)被当作有效产出广播
|
||||
if os.path.isdir(absolute_path):
|
||||
try:
|
||||
has_content = any(True for _ in Path(absolute_path).iterdir())
|
||||
except (OSError, PermissionError):
|
||||
has_content = False
|
||||
if not has_content:
|
||||
continue
|
||||
|
||||
global_event_bus.publish('OutputUpdated', {
|
||||
'step_id': dep_step,
|
||||
'output_type': output_type,
|
||||
|
||||
@ -69,22 +69,72 @@ class Step11MapBatchThread(QThread):
|
||||
except Exception:
|
||||
mpl_prev = None
|
||||
try:
|
||||
from src.core.steps.mapping_step import MappingStep
|
||||
from src.postprocessing.map import ContentMapper
|
||||
|
||||
n = len(self.csv_paths)
|
||||
if n == 0:
|
||||
self.finished_ok.emit(0)
|
||||
return
|
||||
|
||||
boundary_shp = self.step10_kwargs.get('boundary_shp_path')
|
||||
input_crs = self.step10_kwargs.get('input_crs', 'EPSG:32651')
|
||||
output_crs = self.step10_kwargs.get('output_crs', input_crs)
|
||||
resolution = float(self.step10_kwargs.get('resolution', 30))
|
||||
|
||||
# ── ★ 2026-07-01:QThread 内预计算共享空间上下文 ──
|
||||
# 63 个 CSV 坐标一致,边界/网格/掩膜只算一次
|
||||
mapper = ContentMapper(input_crs=input_crs, output_crs=output_crs)
|
||||
shared_ctx = None
|
||||
if n > 1 and boundary_shp:
|
||||
try:
|
||||
shared_ctx = mapper.prepare_shared_context(
|
||||
sample_csv=self.csv_paths[0],
|
||||
shp_file=boundary_shp,
|
||||
resolution=resolution,
|
||||
)
|
||||
self.log_message.emit(
|
||||
f"[共享上下文] 空间基准预计算完成,后续 {n} 个 CSV 复用", "info"
|
||||
)
|
||||
except Exception as e:
|
||||
self.log_message.emit(
|
||||
f"[警告] 共享上下文失败: {e},回退逐个处理", "warning"
|
||||
)
|
||||
|
||||
# ── 批量处理 ──
|
||||
for i, csv_p in enumerate(self.csv_paths):
|
||||
if self._cancelled:
|
||||
self.log_message.emit("专题图批量任务已被用户取消", "warning")
|
||||
break
|
||||
self.progress.emit(i + 1, n)
|
||||
self.log_message.emit(f"专题图 [{i + 1}/{n}] {csv_p}", "info")
|
||||
kw = {**self.step10_kwargs, "prediction_csv_path": csv_p}
|
||||
kw.pop("skip_dependency_check", None)
|
||||
if self.output_dir_optional:
|
||||
stem = Path(csv_p).stem
|
||||
kw["output_image_path"] = str(Path(self.output_dir_optional) / f"{stem}_distribution.png")
|
||||
else:
|
||||
kw["output_image_path"] = None
|
||||
MappingStep.generate_distribution_map(**kw)
|
||||
|
||||
stem = Path(csv_p).stem
|
||||
output_file = (
|
||||
str(Path(self.output_dir_optional) / f'{stem}_distribution.png')
|
||||
if self.output_dir_optional
|
||||
else None
|
||||
)
|
||||
# 已存在则跳过(兼容 tif 重定向后的文件名)
|
||||
if output_file and (
|
||||
Path(output_file).exists()
|
||||
or Path(output_file).with_suffix('.tif').exists()
|
||||
):
|
||||
self.log_message.emit(f" → 跳过(已存在)", "info")
|
||||
continue
|
||||
|
||||
try:
|
||||
mapper.process_data(
|
||||
csv_file=csv_p,
|
||||
shp_file=boundary_shp,
|
||||
output_file=output_file,
|
||||
resolution=resolution,
|
||||
output_format='tif',
|
||||
shared_context=shared_ctx,
|
||||
)
|
||||
except Exception as e:
|
||||
self.log_message.emit(f" → 失败: {e}", "error")
|
||||
continue
|
||||
|
||||
self.finished_ok.emit(n)
|
||||
except Exception as e:
|
||||
self.failed.emit(f"{e}\n{traceback.format_exc()}")
|
||||
@ -534,6 +584,7 @@ class Step11MapPanel(QWidget):
|
||||
|
||||
# ── 智能自动路由:预测 CSV 目录 ──
|
||||
if self.work_dir:
|
||||
from src.gui.core.event_bus import global_event_bus
|
||||
wd = self.work_dir
|
||||
done = False
|
||||
|
||||
@ -542,10 +593,18 @@ class Step11MapPanel(QWidget):
|
||||
pred_dir = resolve_subdir(wd, cand_dir_key)
|
||||
if pred_dir and os.path.isdir(pred_dir):
|
||||
csvs = list(Path(pred_dir).glob("*.csv"))
|
||||
global_event_bus.publish('LogMessage', {
|
||||
'message': f'[Step11 自动路由] 检查 Step9 目录: {pred_dir} → CSV 数量: {len(csvs)}',
|
||||
'level': 'info',
|
||||
})
|
||||
if csvs:
|
||||
self.prediction_csv_dir_edit.setText(pred_dir)
|
||||
self.batch_mode_combo.setCurrentIndex(1)
|
||||
done = True
|
||||
global_event_bus.publish('LogMessage', {
|
||||
'message': f'[Step11 自动路由] ✓ 使用 Step9 预测结果目录 ({len(csvs)} 个 CSV)',
|
||||
'level': 'info',
|
||||
})
|
||||
break
|
||||
|
||||
# Priority 2 (Fallback): Step 10 水色指数输出目录
|
||||
@ -554,12 +613,26 @@ class Step11MapPanel(QWidget):
|
||||
wc_dir = resolve_subdir(wd, cand_dir_key)
|
||||
if wc_dir and os.path.isdir(wc_dir):
|
||||
csvs = list(Path(wc_dir).glob("*.csv"))
|
||||
global_event_bus.publish('LogMessage', {
|
||||
'message': f'[Step11 自动路由] 检查 Step10 目录: {wc_dir} → CSV 数量: {len(csvs)}',
|
||||
'level': 'info',
|
||||
})
|
||||
if csvs:
|
||||
self.prediction_csv_dir_edit.setText(wc_dir)
|
||||
self.batch_mode_combo.setCurrentIndex(1)
|
||||
done = True
|
||||
global_event_bus.publish('LogMessage', {
|
||||
'message': f'[Step11 自动路由] ✓ 使用 Step10 水色指数目录 ({len(csvs)} 个 CSV)',
|
||||
'level': 'info',
|
||||
})
|
||||
break
|
||||
|
||||
if not done:
|
||||
global_event_bus.publish('LogMessage', {
|
||||
'message': '[Step11 自动路由] ⚠ 未找到任何有效 CSV 目录(Step9 和 Step10 均为空或不存在)',
|
||||
'level': 'warning',
|
||||
})
|
||||
|
||||
# GeoTIFF 目录:指向 step10 水色指数输出
|
||||
geotiff_dir = resolve_subdir(wd, 'watercolor')
|
||||
if geotiff_dir and os.path.isdir(geotiff_dir) and not self.geotiff_dir_edit.text().strip():
|
||||
|
||||
@ -503,8 +503,21 @@ class WaterQualityGUI(QMainWindow):
|
||||
|
||||
try:
|
||||
# 1. 触发懒加载生成面板
|
||||
self._panel_factory.get_panel(item_data)
|
||||
|
||||
panel = self._panel_factory.get_panel(item_data)
|
||||
|
||||
# ★ 2026-07-01:每次切页时刷新面板的自动路由
|
||||
# 面板首次加载时 _replay_state_to_panel 会调 update_from_config,
|
||||
# 但再次切回时 get_panel() 直接返回已有实例,不会重扫文件系统。
|
||||
# 此处显式调用确保 Step11 等面板始终基于最新磁盘状态做文件夹自动导入。
|
||||
if panel is not None and hasattr(panel, 'update_from_config'):
|
||||
try:
|
||||
panel.update_from_config(
|
||||
work_dir=self._workspace_initializer.work_dir,
|
||||
pipeline=None,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 🚨 核心防卡死补丁:如果目标 Tab 被后台任务异常永久锁定,强制撬开!
|
||||
if not self._tab_widget.isTabEnabled(tab_index):
|
||||
self._log_manager.info(f"检测到 {item_data} 处于异常锁定状态,已执行强制解锁。")
|
||||
|
||||
Reference in New Issue
Block a user