Files
massive-cloud/alg/delivery_v1.1/underframe_deflection/USAGE.md

12 KiB
Raw Blame History

underframe_deflection 集成调用指南 (v1.1)

高铁底架纵梁挠度测量算法 —— 基于点云的计算。 本文档面向把本算法集成进上层软件的开发者:怎么准备 CAD 基准 + 扫描点云 + ROI JSON 三件套输入、怎么调用、怎么读结果。


0. TL;DR

from underframe_deflection import run_measurement_pipeline

report = run_measurement_pipeline(
    input_cloud="scan.ply",
    cad_path="reference.ply",
    roi_path="roi.json",
    out_dir="out/",
)

# 关心的两个核心数值
print(report["deflection"]["left"]["selected_measurement"]["signed_deflection_mm"])
print(report["deflection"]["right"]["selected_measurement"]["signed_deflection_mm"])

落盘后会得到:

out/
├── report.json               # 完整结果
├── report.xlsx               # 误差报表(Summary + 4 张中间表)
├── deflection_curves.csv     # 沿 X 的挠度曲线
├── beam_tracks.csv           # 候选梁切片诊断
├── images/                   # 5 张诊断 PNG
└── ply/                      # 4 个着色 PLY 叠加图

1. 算法的三个输入

输入 类型 必需 说明
扫描点云 .ply / .xyz / .txt / .csv ✅ 必须 现场采集得到的 (N, 3) XYZ 点云,单位 mm
CAD 基准 .ply / .xyz / .stl / .obj 可选但强烈建议 预先 tessellate / 采样过的 CAD 几何,作为配准基准坐标系;不直接吃 STEP/IGES
ROI JSON .json 可选但强烈建议 描述车底测量区域的轴对齐 bbox,坐标基于 CAD 坐标系

三者全给时走 v1.1 完整流程:去噪 → 配准到 CAD 系 → 按 ROI JSON 截区 → 算挠度 → 出 Excel。 只给扫描点云时走 legacy 流程,与本算法 v1.0 行为完全一致(仅多出 bilateral 这一步可用 --no-bilateral 关掉)。

1.1 ROI JSON 文件格式

固定 6 个 float 字段,单位 mm,CAD 坐标系下:

{
  "x_min": -12500.0,
  "x_max":  12500.0,
  "y_min":    150.0,
  "y_max":   4100.0,
  "z_min":  -1680.0,
  "z_max":    200.0
}

约束:每个轴 min < max,且都是有限实数;任一字段缺失或非法 → InvalidPointCloudError。

test_data/sample_roi.json 是一个可直接使用的样例。

1.2 CAD 基准

  • 不解析 STEP / IGES。如果你只有 .stp,请用 CAD 软件或离线脚本先把它转成 .stl / .ply / .obj,再喂给本算法。
  • 三角网格的话只读顶点位置,face 信息忽略 —— 算法内部把 CAD 当作"参考点集"用 ICP 对齐。
  • test_data/m000004802409t02_asm_100000pts.ply 是从原始 STEP 采样得到的 10 万点 PLY,可直接当 CAD 基准。

2. 三种调用方式

2.1 命令行(最简单的集成方式)

underframe-measure \
  --input-cloud   scan.ply \
  --cad           reference.ply \
  --roi           roi.json \
  --out-dir       out/

可选开关:

开关 含义
--config CONFIG.json 覆盖部分内部超参数(结构同 DEFAULT_CONFIG)
--profile {auto,M,TC,TP,custom} 调参档位
--skip-registration 给了 --cad 但不跑 ICP(适合调试或扫描已经预对齐)
--no-bilateral 关闭双边滤波(v1.0 行为)
--skip-excel 不写 .xlsx

未安装本包时,等价命令:

python -m underframe_deflection.cli --input-cloud scan.ply ...

2.2 一次性 Python 函数(推荐)

from underframe_deflection import run_measurement_pipeline

report = run_measurement_pipeline(
    input_cloud="scan.ply",
    cad_path="reference.ply",        # 可选
    roi_path="roi.json",             # 可选
    out_dir="out/",
    config_path=None,                # 可选 JSON 配置覆盖
    profile="auto",                  # 可选档位
    skip_registration=False,
    enable_bilateral=True,
    skip_excel=False,
)

返回:report 是一个 dict,结构与 out/report.json 完全一致。

2.3 纯内存计算(不读写文件)

如果你已经从其它来源拿到了点云和 ROI(例如直接从相机驱动 / 数据库取数据),可以避开文件 I/O:

import numpy as np
from underframe_deflection import compute_deflection, load_point_cloud

points = load_point_cloud("scan.ply")           # 或自己构造 (N, 3) ndarray
roi_aabb = {                                    # 与 ROI JSON 同结构的 dict
    "x_min": -12500, "x_max":  12500,
    "y_min":    150, "y_max":   4100,
    "z_min":  -1680, "z_max":    200,
}

result = compute_deflection(points, roi_aabb=roi_aabb)
print(result.left.selected_signed_mm, result.right.selected_signed_mm)

注意:compute_deflection 本身不做 CAD 配准。如果你想自己控制配准过程,先调用 register_to_cad,把变换应用到点云后再喂给 compute_deflection,配合 config["frame"]["use_input_axes"]=True(这本来就是默认值)即可:

from underframe_deflection import (
    read_cad_reference, register_to_cad, compute_deflection,
)
from underframe_deflection.registration import apply_transform

scan = load_point_cloud("scan.ply")
cad  = read_cad_reference("reference.ply")

# 你需要一份预解算好的 config(spacing 之类)。最简单的办法:
from underframe_deflection.config import load_config, resolve_numeric_parameters, cloud_geometry
from underframe_deflection.preprocess import estimate_spacing
cfg = load_config(None, "auto")
cfg = resolve_numeric_parameters(cfg, cloud_geometry(scan), estimate_spacing(scan))

reg = register_to_cad(scan, cad, cfg)
scan_aligned = apply_transform(scan, reg.R, reg.t)

result = compute_deflection(scan_aligned, roi_aabb=roi_aabb)

3. 读取输出

3.1 核心数值(最常用)

left  = report["deflection"]["left"]["selected_measurement"]
right = report["deflection"]["right"]["selected_measurement"]

if left["available"]:
    x_mm      = left["x_mm"]
    signed    = left["signed_deflection_mm"]       # 带符号,向下为负
    absolute  = left["absolute_deflection_mm"]
    type_     = left["selected_type"]              # "downward" / "upward"
    beam_pt   = left["beam_point"]                 # [x, y, z]
    ref_pt    = left["reference_point"]            # [x, y, z]

如果你用的是 compute_deflection,公开数据类更直接:

result.left.selected_signed_mm
result.left.max_downward_mm
result.left.max_downward_x_mm
result.left.max_upward_mm
result.left.reference_confidence
result.curves.x_mm                  # ndarray
result.curves.left_deflection_mm    # ndarray, NaN 表示该 X 处无效

3.2 质量警告 quality_flags

非致命问题列在这里,调用方应判断是否要重测:

Flag 含义
left_track_missing / right_track_missing 单侧梁追踪失败
left_representative_line_missing / right_... 单侧代表线拟合失败
left_reference_unavailable / right_... 单侧参考线不可用
left_reference_low_confidence / right_... 参考线置信度低
left_track_low_coverage / right_... 单侧追踪覆盖率 < 45%
left_right_symmetry_warning 左右对称性偏差 > 18%
registration_not_converged ICP 未收敛到容差内
registration_poor_quality ICP 最终 RMS > max(10 mm, 2×点间距)
registration_low_inlier_ratio ICP 内点比例 < 50%
passthrough_empty ROI passthrough 后点过少,已自动回退
bilateral_degraded bilateral k 邻域被裁剪

加粗的几项是 v1.1 新增。

3.3 配准信息

reg = report["registration"]
if reg["performed"]:
    reg["rms_mm"]            # 最终对齐残差
    reg["initial_rms_mm"]    # 粗对齐后的初始残差
    reg["iterations"]
    reg["inlier_ratio"]
    reg["converged"]         # bool
    reg["R"]                 # 3x3 list
    reg["t"]                 # length-3 list

3.4 Excel 误差报表

out/report.xlsx 5 个 sheet:

Sheet 内容
Summary key/value 形式:版本、输入路径、配准统计、预处理各阶段计数、左右挠度核心数值、质量警告
Deflection_Curves 沿 X 的逐点表:x / 左右 beam_y, beam_z / 左右 ref_y, ref_z / 左右 deflection / 左右 confidence
Beam_Tracks 每片候选条带:slice_index、y_center、z_low、width、score、selected_rank 等
Reference_Lines 左右参考线 front/back 端点、direction、residual、confidence
Representative_Points 左右每个代表点的 x/y/z/width/residual/confidence

用 openpyxl、pandas.read_excel、Excel/LibreOffice 都可以直接读。

3.5 异常

异常类 触发条件
InvalidPointCloudError 点云 / CAD / ROI JSON 文件格式非法、字段缺失、AABB 反转
UnsupportedFormatError 文件后缀不在支持列表
InsufficientPointsError 点数过少(点云 < 256)或 ROI 过滤后无重叠
FrameInferenceError 自动坐标系推断失败(仅 legacy 路径)

集成方应在外层 try / except DeflectionError 兜底,并把 quality_flags 落入日志或上层告警。


4. 配置覆盖(高级)

绝大多数场景不需要碰配置。需要微调时,通过 --config my.json 或 config= 传入嵌套 dict,与 DEFAULT_CONFIG 深度合并:

config = {
    "preprocess": {
        "bilateral": {"enabled": True, "k": 32, "sigma_n_deg": 10.0},
        # 也可以从外部传 passthrough,但通常用 ROI JSON 即可
    },
    "registration": {
        "icp_max_iter": 80,
        "trim_ratio": 0.15,
    },
    "reference": {
        "low_confidence_threshold": 0.5,
    },
    "deflection": {
        "evaluation_grid_factor": 0.5,    # 更密的挠度采样
    },
}

完整字段定义见 underframe_deflection/config.py: DEFAULT_CONFIG。


5. 进度回调

compute_deflection 支持 progress 回调:

def on_progress(stage: str, fraction: float) -> None:
    print(f"[{fraction:5.1%}] {stage}")

result = compute_deflection(points, roi_aabb=roi_aabb, progress=on_progress)

阶段顺序:preprocess → frame → beam_tracking → line_fitting → reference → deflection。

run_measurement_pipeline 当前未透传 progress 回调,需要时请直接调 compute_deflection。


6. 坐标系约定

输入点云必须满足以下约定,否则结果无意义:

  • X 轴:纵向(车长方向)
  • Y 轴:横向(车宽方向)
  • Z 轴:高度方向,向上为正
  • 单位:毫米

CAD 基准也必须遵循同样的轴向;ROI JSON 中的 bbox 字段在 CAD 坐标系下。 扫描点云轴向不同 → 调用前自行做置换 / 旋转。


7. 版本兼容性

  • __version__ —— 包版本
  • ALGORITHM_VERSION —— 算法版本(升级会改变数值结果)
  • SCHEMA_VERSION —— report.json 结构版本(仅在破坏性变更时递增;新字段不递增)

v1.1 新增了非破坏性的可选字段(cad_reference / roi_source / roi_aabb / registration / preprocess.passthrough / preprocess.bilateral / roi.source / quality_flags 新值),SCHEMA_VERSION 保持 "1.0" 不变。


8. 集成模板

import logging
from underframe_deflection import (
    run_measurement_pipeline,
    DeflectionError,
)

log = logging.getLogger(__name__)


def measure(scan_path: str, cad_path: str, roi_path: str, out_dir: str) -> dict | None:
    try:
        report = run_measurement_pipeline(
            input_cloud=scan_path,
            cad_path=cad_path,
            roi_path=roi_path,
            out_dir=out_dir,
        )
    except DeflectionError as exc:
        log.error("deflection failed: %s", exc)
        return None

    flags = report.get("quality_flags") or []
    if flags:
        log.warning("quality flags: %s", flags)

    return {
        "schema_version":      report["schema_version"],
        "algorithm_version":   report["algorithm_version"],
        "left_signed_mm":  report["deflection"]["left"].get("selected_measurement", {}).get("signed_deflection_mm"),
        "right_signed_mm": report["deflection"]["right"].get("selected_measurement", {}).get("signed_deflection_mm"),
        "registration_rms_mm": report["registration"].get("rms_mm"),
        "flags": flags,
    }