Files
massive-cloud/server/plugins/adapters/vehicle_registration_run.py
lipan 29f1d45d66 feat: 算法升级、整车配准集成与性能分析文档
算法更新: delivery v1.2.1、平整度v1.5、测距v2.2、整车配准v1.3
适配器: vehicle-registration 新增、各算法 manifest 和 adapter 更新
后端: taskCadFiles/source 字段迁移、derivedPointClouds 注册、取消任务联动
前端: ElapsedTime 时长显示、两步算法流程、canRun 逻辑修复
文档: 新增5份算法性能分析报告
2026-06-25 08:22:20 +00:00

182 lines
7.0 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.
"""vehicle-registration 整车配准适配器 (v1.3)。
调用 alg/整车配准v1.3 的 translate_align_by_front_tail:
- 输入:整车点云 PLY + 标准 STEP CAD
- 原理:车头/车尾各采样少量点算质心差,融合成整体平移矩阵
- v1.3 新增:ICP 精配准、CAD 采样缓存、配准误差统计(RMSE/p95/p99)
- 输出:
aligned_pointcloud_translation_only.ply 配准后的完整点云
T_translation_only.txt 4x4 平移矩阵
result.json 额外约定 derivedPointClouds 字段:runner 会把列出的 PLY
回写为该任务的新点云记录(source='registered'),供后续测量算法选用。
"""
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,
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, "加载整车配准算法包 v1.3")
from translate_align_pointcloud_to_cad import translate_align_by_front_tail # type: ignore
pc_path = first_pointcloud(payload)
cad_path = inputs.get("cadPath")
if not cad_path:
raise RuntimeError("整车配准需要 STEP CAD 文件,请在任务下上传并选中 CAD。")
axis = str(config.get("axis") or "x")
crop_ratio = float(config.get("cropRatio") or 0.01)
mesh_deflection = float(config.get("meshDeflection") or 0.1)
cad_sample_points = int(config.get("cadSamplePoints") or 30000)
local_sample_points = int(config.get("localSamplePoints") or 1000)
scale_pointcloud = float(config.get("scalePointcloud") or 1.0)
# v1.3 新增参数
use_icp = bool(config.get("useIcp") if config.get("useIcp") is not None else True)
icp_sample_points = int(config.get("icpSamplePoints") or 200000)
compute_error_stats = bool(config.get("computeErrorStats") if config.get("computeErrorStats") is not None else True)
use_cad_cache = bool(config.get("useCadCache") if config.get("useCadCache") is not None else True)
emit_progress(
0.15,
f"开始配准:{os.path.basename(pc_path)} ← {os.path.basename(str(cad_path))}"
+ (" + ICP" if use_icp else ""),
)
result = translate_align_by_front_tail(
cad_path=str(cad_path),
pointcloud_path=pc_path,
output_dir=str(out_dir),
axis=axis,
crop_ratio=crop_ratio,
mesh_deflection=mesh_deflection,
cad_sample_points=cad_sample_points,
local_sample_points=local_sample_points,
scale_pointcloud=scale_pointcloud,
visualize=False,
use_cad_cache=use_cad_cache,
use_icp=use_icp,
icp_sample_points=icp_sample_points,
compute_error_stats=compute_error_stats,
save_debug_samples=False,
)
emit_progress(0.92, "整理结果")
t_final = [float(v) for v in result["translation"]]
ft_diff = float(result["front_tail_translation_difference"])
elapsed = float(result.get("elapsed_seconds") or 0)
measurements = [
{"name": "最终平移量 Tx", "value": t_final[0], "unit": "mm", "status": "info"},
{"name": "最终平移量 Ty", "value": t_final[1], "unit": "mm", "status": "info"},
{"name": "最终平移量 Tz", "value": t_final[2], "unit": "mm", "status": "info"},
{
"name": "前后端平移差",
"value": ft_diff,
"unit": "mm",
"tolerance": 50.0,
"status": "pass" if ft_diff <= 50.0 else "warning",
},
]
# ICP 结果指标
icp_stats = result.get("icp_stats")
if icp_stats:
measurements.append({
"name": "ICP fitness",
"value": float(icp_stats.get("fitness", 0)),
"unit": "",
"status": "pass" if float(icp_stats.get("fitness", 0)) >= 0.9 else "warning",
})
measurements.append({
"name": "ICP inlier RMSE",
"value": float(icp_stats.get("inlier_rmse", 0)),
"unit": "mm",
"status": "info",
})
# 配准误差统计
error_stats = result.get("error_stats")
if error_stats:
rmse = float(error_stats.get("rmse", 0))
p95 = float(error_stats.get("p95", 0))
measurements += [
{"name": "配准 RMSE", "value": rmse, "unit": "mm",
"status": "pass" if rmse <= 5.0 else "warning"},
{"name": "配准误差 p95", "value": p95, "unit": "mm", "status": "info"},
{"name": "配准误差 p99", "value": float(error_stats.get("p99", 0)), "unit": "mm", "status": "info"},
]
aligned_name = Path(result["aligned_path"]).name
src_stem = Path(pc_path).stem
# 下载文件:矩阵 + 误差统计(如有)
files = [Path(result["matrix_path"]).name]
error_stats_path = result.get("error_stats_path")
if error_stats_path and Path(error_stats_path).exists():
files.append(Path(error_stats_path).name)
payload_out = {
"schemaVersion": 1,
"status": "completed",
"message": (
f"整车配准完成({'平移 + ICP' if use_icp else '仅平移'},耗时 {elapsed:.1f}s)。"
"配准后的点云已加入任务点云列表,请在后续算法中选用。"
),
"measurements": measurements,
"overlay3d": {"kind": "none"},
"files": files,
"derivedPointClouds": [
{"file": aligned_name, "name": f"{src_stem}_已配准.ply"}
],
"extra": {
"initialTranslation": [float(v) for v in result.get("initial_translation", result["translation"])],
"finalTranslation": t_final,
"tFront": [float(v) for v in result["t_front"]],
"tTail": [float(v) for v in result["t_tail"]],
"matrix": [[float(v) for v in row] for row in result["T_final"]],
"icpStats": icp_stats,
"elapsedSeconds": elapsed,
"sourcePointCloud": os.path.basename(pc_path),
"cad": os.path.basename(str(cad_path)),
},
}
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"整车配准失败:{exc}",
extra={"traceback": traceback.format_exc()},
)
return 1
if __name__ == "__main__":
sys.exit(main())