# underframe_deflection 集成调用指南 (v1.1) > 高铁底架纵梁挠度测量算法 —— 基于点云的计算。 > 本文档面向把本算法集成进上层软件的开发者:怎么准备 **CAD 基准 + 扫描点云 + ROI JSON** 三件套输入、怎么调用、怎么读结果。 --- ## 0. TL;DR ```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/", ) # 关心的两个核心数值 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 坐标系下: ```json { "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 命令行(最简单的集成方式) ```bash 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` | 未安装本包时,等价命令: ```bash python -m underframe_deflection.cli --input-cloud scan.ply ... ``` ### 2.2 一次性 Python 函数(推荐) ```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: ```python 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`(这本来就是默认值)即可: ```python 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 核心数值(最常用) ```python 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`,公开数据类更直接: ```python 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 配准信息 ```python 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` 深度合并: ```python 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` 回调: ```python 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. 集成模板 ```python 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, } ```