Files
massive-cloud/server/plugins/adapters/train_measurement_run.py

208 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""train-measurement 算法适配器 (v1.1)。
读 input.json → 调 TaskManager(来自 alg/点云测距v1.1)→ 写统一 result.json。
v1.1 变更:
- 路径从 260505列车点云测距v1 → 点云测距v1.1
- 算法包现在是可安装的 Python 包(有 __init__.py),PYTHONPATH 指向 src/
- TRAIN_MEAS_ROOT 指向 train_measurement_system/(用于定位 config/measurements.yaml)
- TaskManager.run_all_tasks() 返回结构不变,data 字段含 mean_distance / std_distance 等
约定:
- 第一个点云路径作为 pointcloud_file 注入到测量配置 YAML 中;
- 若用户在 UI 选择「自定义 YAML」并提供 customYaml,则使用之;
否则使用算法仓库自带的 config/measurements.yaml 作为模板。
- 所有任务结果合成成 measurements[] 列表,附带可视化 PLY 路径到 extra。
"""
from __future__ import annotations
import os
import sys
import traceback
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from _plugin_io import (
emit_progress,
ensure_ascii_ply,
first_pointcloud,
load_input,
write_error_result,
write_result,
)
def main() -> int:
payload = load_input(sys.argv)
inputs = payload.get("inputs") or {}
config = inputs.get("config") or {}
out_dir = Path(payload["outDir"])
out_dir.mkdir(parents=True, exist_ok=True)
try:
emit_progress(0.05, "加载 train_measurement v1.1 算法包")
# TRAIN_MEAS_ROOT 由 manifest envExtra 注入,指向 train_measurement_system/
train_root = Path(os.environ.get("TRAIN_MEAS_ROOT") or "")
if not train_root.exists():
# 回退:从 ALG_ROOT 推导
alg_root = Path(os.environ.get("ALG_ROOT") or "/alg")
train_root = alg_root / "点云测距v1.1" / "train_measurement_system"
if not train_root.exists():
raise RuntimeError(
f"找不到 train_measurement_system 目录:{train_root}。"
"请确认 TRAIN_MEAS_ROOT 或 ALG_ROOT 环境变量已正确设置。"
)
from task_manager import TaskManager # type: ignore
pc_path = first_pointcloud(payload)
emit_progress(0.15, f"读取点云:{os.path.basename(pc_path)}")
# 若是二进制 PLY,先转成 ASCII PLY(算法内置 reader 不依赖 open3d)
pc_path = ensure_ascii_ply(pc_path, out_dir / "_ply_cache")
# ── 准备 YAML 配置 ────────────────────────────────────────────────────
config_preset = str(config.get("configPreset") or "builtin")
custom_yaml_str = str(config.get("customYaml") or "").strip()
if config_preset == "custom" and custom_yaml_str:
# 支持 JSON 格式(前端传来)和 YAML 格式
yaml_path = out_dir / "custom_measurements.yaml"
try:
import json as _json
parsed = _json.loads(custom_yaml_str)
# JSON 格式:转成 YAML 写盘
import yaml as _yaml
yaml_path.write_text(_yaml.dump(parsed, allow_unicode=True, default_flow_style=False), encoding="utf-8")
except Exception:
# 已经是 YAML 格式,直接写盘
yaml_path.write_text(custom_yaml_str, encoding="utf-8")
else:
# 使用算法仓库内置的 config/measurements.yaml
yaml_path = train_root / "config" / "measurements.yaml"
if not yaml_path.exists():
raise RuntimeError(f"内置配置文件不存在:{yaml_path}")
emit_progress(0.20, f"加载配置:{yaml_path.name}")
# ── 初始化 TaskManager ────────────────────────────────────────────────
tm = TaskManager()
# 先注入点云路径,让 load_config 跳过 YAML 里的 pointcloud_file(开发测试路径)
tm.pointcloud_path_override = pc_path
if not tm.load_config(str(yaml_path)):
raise RuntimeError(f"TaskManager 加载配置失败:{yaml_path}")
# 注入点云路径(覆盖 YAML 中的 pointcloud_file)
tm.config["pointcloud_file"] = pc_path
# 注入可视化输出目录
viz_dir = out_dir / "visualizations"
viz_dir.mkdir(parents=True, exist_ok=True)
for task in tm.tasks:
if hasattr(task, "visualization_dir"):
task.visualization_dir = str(viz_dir)
emit_progress(0.30, "加载点云(已在 load_config 中完成)")
emit_progress(0.40, f"执行 {len(tm.tasks)} 个测量任务")
results = tm.run_all_tasks()
emit_progress(0.92, "整理结果")
measurements: list[dict] = []
clean_raw: list[dict] = []
for r in results:
name = str(r.get("name") or "未命名任务")
status_raw = r.get("status")
data = r.get("data") if isinstance(r.get("data"), dict) else {}
# 剥掉以 '_' 开头的私有 numpy 数组(不可 JSON 序列化)
clean_data = {k: v for k, v in data.items() if not k.startswith("_")} if data else {}
clean_raw.append(
{
"name": name,
"type": r.get("type"),
"status": status_raw,
"data": clean_data,
"error": r.get("error"),
}
)
if status_raw == "success":
value = clean_data.get("mean_distance")
if value is None:
value = clean_data.get("distance")
measurements.append(
{
"name": name,
"value": float(value) if isinstance(value, (int, float)) else float("nan"),
"unit": "mm",
"status": "info",
}
)
std = clean_data.get("std_distance")
if isinstance(std, (int, float)):
measurements.append(
{
"name": f"{name} · 标准差",
"value": float(std),
"unit": "mm",
"status": "info",
}
)
# v1.1 新增字段:min/max distance
for field_name, label in [
("min_distance", "最小距离"),
("max_distance", "最大距离"),
]:
v = clean_data.get(field_name)
if isinstance(v, (int, float)):
measurements.append(
{
"name": f"{name} · {label}",
"value": float(v),
"unit": "mm",
"status": "info",
}
)
else:
measurements.append(
{
"name": name,
"value": float("nan"),
"unit": "mm",
"status": "fail",
}
)
success_count = sum(1 for r in results if r.get("status") == "success")
payload_out = {
"schemaVersion": 1,
"status": "completed",
"message": f"共 {len(results)} 个任务,成功 {success_count} 个。",
"measurements": measurements,
"overlay3d": {"kind": "none"},
"extra": {
"rawTaskResults": clean_raw,
"visualizationDir": "visualizations",
},
}
write_result(out_dir, payload_out)
emit_progress(1.0, "完成")
return 0
except Exception as exc:
traceback.print_exc()
write_error_result(
out_dir,
f"train-measurement 计算失败:{exc}",
extra={"traceback": traceback.format_exc()},
)
return 1
if __name__ == "__main__":
sys.exit(main())