feat: 算法升级、整车配准集成与性能分析文档
算法更新: delivery v1.2.1、平整度v1.5、测距v2.2、整车配准v1.3 适配器: vehicle-registration 新增、各算法 manifest 和 adapter 更新 后端: taskCadFiles/source 字段迁移、derivedPointClouds 注册、取消任务联动 前端: ElapsedTime 时长显示、两步算法流程、canRun 逻辑修复 文档: 新增5份算法性能分析报告
This commit is contained in:
181
server/plugins/adapters/vehicle_registration_run.py
Normal file
181
server/plugins/adapters/vehicle_registration_run.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""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())
|
||||
Reference in New Issue
Block a user