364 lines
12 KiB
Markdown
364 lines
12 KiB
Markdown
# 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,
|
||
}
|
||
```
|