feat: Step2~Step7 Handler 批量生成 + WorkerThread 接入新调度器

- 新增 6 个 Handler:Step2GlintDetection / Step3GlintRemoval / Step4Sampling / Step5ProcessCsv / Step6ExtractSpectra / Step7CalcIndices

- 新增 register_handlers.py:register_all_handlers() 一键注册 Step1~Step7

- 更新 __init__.py:导出全部 7 个 Handler

- 重构 worker_thread.py:移除旧 WaterQualityInversionPipeline 导入,改用 PipelineScheduler + register_all_handlers

- run_single_step 改为 scheduler.run_step() 调用,保留外部模型透传逻辑
This commit is contained in:
DXC
2026-06-17 18:02:31 +08:00
parent f6455b71ba
commit f1cc339d4a
9 changed files with 482 additions and 74 deletions

View File

@ -6,8 +6,9 @@ import os
import traceback
from typing import Dict, List
from PyQt5.QtCore import QThread, pyqtSignal
from src.core.pipeline.runner import PipelineRunner, PipelineHalt
from src.core.pipeline.context import PipelineContext
from src.core.pipeline.runner import PipelineHalt
from src.core.handlers.pipeline_scheduler import PipelineScheduler
from src.core.handlers.register_handlers import register_all_handlers
# =============================================================================
@ -113,9 +114,10 @@ PIPELINE_ERROR_INFO = []
try:
error_info = diagnose_pipeline_import_error()
from src.core.water_quality_inversion_pipeline_GUI import WaterQualityInversionPipeline
from src.core.handlers.pipeline_scheduler import PipelineScheduler
from src.core.handlers.register_handlers import register_all_handlers
PIPELINE_AVAILABLE = True
print("[OK] 成功导入pipeline模块")
print("[OK] 成功导入 Handler 调度器模块")
PIPELINE_ERROR_INFO = error_info
except ImportError as e:
@ -140,12 +142,11 @@ except ImportError as e:
print(" 2. 如果需要修复,可以在.spec文件中添加unittest模块:")
print(" a = Analysis(..., hiddenimports=['unittest', 'unittest.mock'])")
print(" 3. 或在PyInstaller命令中添加: --hidden-import unittest")
elif "water_quality_inversion_pipeline_GUI" in str(e):
elif "handlers" in str(e) or "pipeline_scheduler" in str(e):
print("[INFO] 可能的解决方案:")
print(" 1. 检查src/core/water_quality_inversion_pipeline_GUI.py文件是否存在")
print(" 2. 确保Python路径设置正确")
print(" 1. 检查 src/core/handlers/ 目录是否存在")
print(" 2. 确保 Python 路径设置正确")
print(" 3. 尝试重新安装依赖: pip install -r requirements.txt")
print(" 4. 检查Python版本是否兼容推荐Python 3.8-3.11")
import traceback
print("\n完整错误追踪:")
@ -257,35 +258,32 @@ class WorkerThread(QThread):
except Exception:
mpl_prev = None
try:
from src.core.water_quality_inversion_pipeline_GUI import WaterQualityInversionPipeline
self.pipeline = WaterQualityInversionPipeline(work_dir=self.work_dir)
# ── 新架构PipelineScheduler + Handler 注册表 ──
scheduler = PipelineScheduler(work_dir=self.work_dir)
scheduler.set_callback(self.pipeline_callback)
register_all_handlers(scheduler)
self.pipeline = scheduler # 保持兼容stop() 等引用 self.pipeline
if self.mode == 'full':
self.log_message.emit("开始运行完整流程 (Runner 调度模式)...", "info")
if hasattr(self.pipeline, 'set_callback'):
self.pipeline.set_callback(self.pipeline_callback)
self.log_message.emit("开始运行完整流程 (Handler 调度模式)...", "info")
# ── ★ 预检已由 GUI 层 perform_preflight() 完成,此处不再重复预检 ──
# 构造上下文 (Ctx),将 config 整体注入 user_config
ctx = PipelineContext(
img_path=self.config.get('step1', {}).get('img_path'),
water_mask_path=self.config.get('step1', {}).get('mask_path'),
csv_path=self.config.get('step4_sampling', {}).get('csv_path'),
boundary_path=self.config.get('step5_clean', {}).get('boundary_path'),
boundary_shp_path=self.config.get('step11_map', {}).get('boundary_shp_path'),
formula_csv_path=self.config.get('step8_non_empirical_modeling', {}).get('formula_csv_path'),
work_dir=self.work_dir,
user_config=self.config
)
# 过滤 skip_list 中的步骤
active_config = {
k: v for k, v in self.config.items()
if k not in self.skip_list
}
# 启动新调度器
runner = PipelineRunner(self.pipeline)
result_ctx = runner.run(ctx, config=self.config, skip_list=self.skip_list)
result = scheduler.run_full_pipeline(active_config)
if result_ctx.last_error:
raise RuntimeError(f"流水线执行失败: {result_ctx.last_error}")
errors = result.get('errors', {})
if errors:
error_lines = [f" {k}: {v}" for k, v in errors.items()]
raise RuntimeError(
f"流水线部分步骤执行失败 ({len(errors)} 个):\n"
+ "\n".join(error_lines)
)
self.progress_update.emit(100, "流程执行完成")
self.finished.emit(True, "完整流程执行成功!")
@ -293,10 +291,7 @@ class WorkerThread(QThread):
self.log_message.emit(f"开始独立运行步骤: {self.step_name}", "info")
self.progress_update.emit(0, f"正在执行: {self.step_name}")
if hasattr(self.pipeline, 'set_callback'):
self.pipeline.set_callback(self.pipeline_callback)
self.run_single_step(self.step_name, self.config)
self.run_single_step(scheduler, self.step_name, self.config)
self.progress_update.emit(100, f"步骤 {self.step_name} 执行完成")
self.finished.emit(True, f"步骤 {self.step_name} 独立运行成功!")
@ -317,56 +312,24 @@ class WorkerThread(QThread):
except Exception:
pass
def run_single_step(self, step_name, config):
"""运行单个步骤"""
step_method_map = {
'step1': 'step1_generate_water_mask',
'step2': 'step2_find_glint_area',
'step3': 'step3_remove_glint',
'step4_sampling': 'step4_sampling',
'step5_clean': 'step5_process_csv',
'step6_feature': 'step6_extract_spectra',
'step7_index': 'step7_calc_indices',
'step8_ml_train': 'step8_train_ml',
'step8_non_empirical_modeling': 'step8_non_empirical_modeling',
'step8_qaa': 'step8_qaa_inversion',
'step9_ml_predict': 'step9_predict_ml',
'step10_watercolor': 'step9_watercolor_inversion',
'step11_map': 'step10_map',
}
if step_name not in step_method_map:
raise ValueError(f"未知的步骤名称: {step_name}")
method_name = step_method_map[step_name]
def run_single_step(self, scheduler, step_name, config):
"""使用新调度器运行单个步骤"""
step_config = dict(config.get(step_name, {}))
# step8_qaa_inversion 内部使用 config.get('step8_qaa', {}) 读取内层,
# 必须透传完整 config dict含外层 step_name key
if step_name == 'step8_qaa':
method = getattr(self.pipeline, method_name)
result = method(**config)
return result
# 透传面板顶层传入的外部预训练模型GUI step11_prediction_panel 通过 config['_external_model'] 传入)
# 非空才覆盖(遵循 feedback_never_overwrite_with_empty 原则)
# 透传外部预训练模型(非空才覆盖)
for key in ('_external_model', '_external_model_path',
'_external_models_dict', '_external_model_dir'):
val = config.get(key)
if val is not None and val != "":
step_config[key] = val
if key == '_external_models_dict':
print(f"[Worker] 提取到的外部字典 Keys: {list(val.keys())}")
else:
print(f"[Worker] 透传 {key}: {val}")
step_config['skip_dependency_check'] = True
if step_name in ['step2', 'step3', 'step4_sampling', 'step5_clean', 'step7_index', 'step9_ml_predict']:
step_config.pop('output_path', None)
method = getattr(self.pipeline, method_name)
result = method(**step_config)
# step8_qaa 特殊处理:透传完整 config含外层 step8_qaa key
if step_name == 'step8_qaa':
result = scheduler.run_step(step_name, config)
else:
result = scheduler.run_step(step_name, step_config)
return result