feat: update algorithm integrations and point cloud overlays

This commit is contained in:
2026-06-30 16:46:36 +08:00
parent ddca7e5436
commit 7e2e56a50d
64 changed files with 4356 additions and 2781 deletions

View File

@@ -1,18 +1,14 @@
"""underframe-deflection 算法适配器 (v1.2.1)。
调用 alg/delivery_v1.2.1/underframe_deflection 的 run_both_beams:
- 输入:整车配准点云 + 左梁 ROI JSON + 右梁 ROI JSON + CAD STP(溯源用)
- 输出:left/ right/ 各一份 report.xlsx + report.json + deflection_curve.csv/png
- measurements 展示左右梁最大挠度及检验结论
v1.2.1 变更:
- 算法包升级至 delivery_v1.2.1(单梁结果新增项点检验字段:公差/测量/误差/结论)
- report.xlsx 等 Excel 产物通过 result.json 的 files 字段暴露给前端下载
- PYTHONPATH 指向 delivery_v1.2.1
v1.2 变更:
- 新增 single_beam 单梁管线
v1.1 变更:
- 入口改为 run_measurement_pipeline(返回 dict,同时落盘 report.json / xlsx / PLY)
- 新增 cad_path(CAD 基准 PLY/STL)和 roi_path(ROI JSON)可选输入
- 新增 skip_registration / enable_bilateral 配置项
- 结果从 report dict 中读取,而非 DeflectionResult 对象字段
- 改用 run_both_beams 双梁并行入口,与算法方命令行一致
- leftRoiPath / rightRoiPath 参数默认指向 test_data ROI
- skipRegistration 默认 True(点云已配准)
"""
from __future__ import annotations
@@ -35,142 +31,139 @@ from _plugin_io import (
def _status_for(value: float | None, tolerance: float | None) -> str:
if value is None or tolerance is None:
return "info"
if abs(value) <= tolerance:
abs_val = abs(value)
if abs_val <= tolerance:
return "pass"
if abs(value) <= tolerance * 1.5:
if abs_val <= tolerance * 1.5:
return "warning"
return "fail"
def _safe_float(v: object) -> float | None:
try:
return float(v) # type: ignore[arg-type]
except (TypeError, ValueError):
return None
def main() -> int:
payload = load_input(sys.argv)
inputs = payload.get("inputs") or {}
config = inputs.get("config") or {}
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, "加载 underframe_deflection v1.2.1 算法包")
from underframe_deflection.pipeline import run_measurement_pipeline # type: ignore
emit_progress(0.05, "加载挠度算法包 v1.2.1")
pc_path = first_pointcloud(payload)
emit_progress(0.15, f"读取点云:{os.path.basename(pc_path)}")
# ── 输入文件 ────────────────────────────────────────────────────────
pointcloud_path = first_pointcloud(payload)
if not pointcloud_path:
write_error_result(out_dir, "未提供点云文件")
return 1
profile = str(config.get("profile") or "auto")
skip_registration = bool(config.get("skipRegistration") or False)
enable_bilateral = bool(config.get("enableBilateral") if "enableBilateral" in config else True)
# 可选 CAD 基准(PLY/STL)和 ROI JSON
cad_path = inputs.get("cadPath") or None
roi_path = inputs.get("roiPath") or None
emit_progress(0.20, "调用 run_measurement_pipeline")
left_roi = config.get("leftRoiPath") or \
"/alg/delivery_v1.2.1/underframe_deflection/test_data/roi_left_beam.json"
right_roi = config.get("rightRoiPath") or \
"/alg/delivery_v1.2.1/underframe_deflection/test_data/roi_right_beam.json"
# run_measurement_pipeline 内部会打印进度到 stdout,
# 我们在调用前后各报一次进度即可。
report: dict = run_measurement_pipeline(
input_cloud=pc_path,
out_dir=str(out_dir),
profile=profile,
cad_path=cad_path,
roi_path=roi_path,
skip_registration=skip_registration,
enable_bilateral=enable_bilateral,
skip_excel=False,
for p, label in [(left_roi, "左梁 ROI"), (right_roi, "右梁 ROI")]:
if not os.path.isfile(p):
write_error_result(out_dir, f"{label} 文件不存在:{p}")
return 1
tolerance_mm = float(config.get("toleranceMm") or 3.0)
left_tolerance_mm = float(config.get("leftToleranceMm") or tolerance_mm or 7.0)
right_tolerance_mm = float(config.get("rightToleranceMm") or tolerance_mm or 12.0)
skip_registration = bool(config.get("skipRegistration", True))
enable_bilateral = bool(config.get("enableBilateral", True))
emit_progress(0.10, f"开始左梁挠度测量(容差 {left_tolerance_mm} mm)")
from underframe_deflection import run_single_beam_pipeline # type: ignore
left_out = out_dir / "left"
left_out.mkdir(exist_ok=True)
left_report = run_single_beam_pipeline(
input_cloud = pointcloud_path,
roi_path = left_roi,
out_dir = str(left_out),
side = "left",
tolerance_mm = left_tolerance_mm,
cad_path = cad_path,
enable_registration= not skip_registration,
config = {"enable_bilateral": enable_bilateral},
)
emit_progress(0.92, "整理结果")
emit_progress(0.55, f"左梁完成,开始右梁挠度测量(容差 {right_tolerance_mm} mm)")
right_out = out_dir / "right"
right_out.mkdir(exist_ok=True)
right_report = run_single_beam_pipeline(
input_cloud = pointcloud_path,
roi_path = right_roi,
out_dir = str(right_out),
side = "right",
tolerance_mm = right_tolerance_mm,
cad_path = cad_path,
enable_registration= not skip_registration,
config = {"enable_bilateral": enable_bilateral},
)
# ── 从 report dict 中提取核心测量值 ──────────────────────────────────
deflection = report.get("deflection") or {}
left_sel = (deflection.get("left") or {}).get("selected_measurement") or {}
right_sel = (deflection.get("right") or {}).get("selected_measurement") or {}
result = {"left": left_report, "right": right_report}
left_mm = _safe_float(left_sel.get("signed_deflection_mm"))
right_mm = _safe_float(right_sel.get("signed_deflection_mm"))
emit_progress(0.95, "整理结果")
registration = report.get("registration") or {}
reg_rms = _safe_float(registration.get("rms_mm"))
def _beam_measurements(report: dict, label: str) -> list:
insp = report.get("inspection") or {}
item_name = insp.get("item_name") or "纵向梁到直线的垂直偏差(挠度)"
measured = insp.get("measured_mm")
tol = insp.get("tolerance_mm")
verdict = insp.get("verdict") or ""
return [{
"name" : f"{label} · {item_name}",
"value" : round(float(measured), 4) if measured is not None else 0.0,
"unit" : "mm",
"tolerance": float(tol) if tol is not None else None,
"status" : "pass" if verdict == "合格"
else "fail" if verdict == "不合格"
else "info",
}]
quality_flags: list[str] = report.get("quality_flags") or []
measurements = (
_beam_measurements(left_report, "左梁") +
_beam_measurements(right_report, "右梁")
)
# 容差:高铁底架挠度通常要求 ≤ 3 mm(可在 manifest inputs 中暴露)
tol = _safe_float(config.get("toleranceMm")) or 3.0
# 收集产物文件(xlsx/csv/png)- write_result 会自动扫 xlsx,
# 这里额外加 csv/png 让前端也能下载
extra_files: list[str] = []
for subdir in ("left", "right"):
sub = out_dir / subdir
if not sub.exists():
continue
for fname in sorted(sub.iterdir()):
if fname.suffix in (".csv", ".png"):
extra_files.append(str(fname.relative_to(out_dir)))
measurements = [
# ── 汇总消息 ────────────────────────────────────────────────────────
left_v = (left_report.get("inspection") or {}).get("verdict") or "—"
right_v = (right_report.get("inspection") or {}).get("verdict") or "—"
left_d = (left_report.get("inspection") or {}).get("measured_mm")
right_d = (right_report.get("inspection") or {}).get("measured_mm")
msg = (
f"挠度测量完成。"
f"左梁 {left_d:.3f} mm({left_v}),"
f"右梁 {right_d:.3f} mm({right_v})。"
if left_d is not None and right_d is not None
else "挠度测量完成。"
)
write_result(
out_dir,
{
"name": "左梁挠度",
"value": left_mm if left_mm is not None else float("nan"),
"unit": "mm",
"tolerance": tol,
"status": _status_for(left_mm, tol),
"message": msg,
"measurements": measurements,
"files": extra_files,
"extra": {"leftReport": left_report, "rightReport": right_report},
},
{
"name": "右梁挠度",
"value": right_mm if right_mm is not None else float("nan"),
"unit": "mm",
"tolerance": tol,
"status": _status_for(right_mm, tol),
},
]
if reg_rms is not None:
measurements.append(
{
"name": "配准 RMS",
"value": reg_rms,
"unit": "mm",
"status": "pass" if reg_rms <= 1.0 else "warning" if reg_rms <= 2.0 else "fail",
}
)
# ── 3D 叠加层:优先 out_dir/ply/ 子目录,回退到 out_dir 根目录 ──────────
overlay3d: dict = {"kind": "none"}
ply_files: list = []
ply_dir = out_dir / "ply"
if ply_dir.exists():
ply_files = sorted(ply_dir.glob("*.ply"))
if not ply_files:
# 算法直接输出到 out_dir 根目录;优先取 full overlay
for name in ("roi_measurement_overlay_full.ply", "roi_cropped_cloud.ply"):
candidate = out_dir / name
if candidate.exists():
ply_files = [candidate]
break
if not ply_files:
ply_files = sorted(out_dir.glob("*.ply"))
if ply_files:
rel = ply_files[0].relative_to(out_dir)
overlay3d = {
"kind": "ply",
"file": str(rel),
}
payload_out = {
"schemaVersion": report.get("schema_version", "1.1"),
"status": "completed",
"measurements": measurements,
"overlay3d": overlay3d,
"extra": {
"algorithmVersion": report.get("algorithm_version"),
"qualityFlags": quality_flags,
"registration": registration,
"deflectionFull": deflection,
},
}
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"underframe-deflection 计算失败:{exc}",