377 lines
13 KiB
Markdown
377 lines
13 KiB
Markdown
# coupler_panel — 集成开发者使用指南
|
||
|
||
> 高铁车钩面板偏转角与最大偏差测量算法
|
||
>
|
||
> 主输入:**CAD STP + 点云 PLY + ROI JSON**
|
||
>
|
||
> 主输出:两个方向偏转角 `θ_right` / `θ_ref`、最大 x 向偏差 `dx_y_max` / `dx_z_max`,以及一份完整可序列化的 `report.json`。
|
||
|
||
---
|
||
|
||
## 1. 一句话集成
|
||
|
||
```python
|
||
from coupler_panel.pipeline import run_coupler_measurement_pipeline
|
||
|
||
result = run_coupler_measurement_pipeline(
|
||
input_cloud = "scan.ply", # 你的测量点云(已在 CAD 帧)
|
||
cad_stp_path = "panel.stp", # 同款面板的 CAD STP
|
||
roi_json = "roi.json", # ROI 描述(不裁剪就传 mode=all)
|
||
out_dir = "runs/today", # 中间产物 + report.json 落盘目录
|
||
)
|
||
|
||
print(result.theta_right_deg, result.theta_ref_deg) # 两个偏转角
|
||
print(result.dx_y_max_mm, result.dx_z_max_mm) # 最大 x 偏差
|
||
```
|
||
|
||
返回 `CouplerMeasurement`,全字段语义见 §4。`out_dir` 下额外得到 8 个文件(含 `report.json`,便于排查 / 后处理)。
|
||
|
||
---
|
||
|
||
## 2. 安装
|
||
|
||
```bash
|
||
# 全量安装(推荐)— core + 点云 IO + STP 解析 + 可视化 + Excel
|
||
python -m pip install -r requirements.txt
|
||
pip install -e ".[all]"
|
||
|
||
# 也可以按需取子集(见 pyproject.toml 的 extras):
|
||
pip install -e ".[cloud]" # 只要点云 + 去噪
|
||
pip install -e ".[cad]" # 需要解析 STP
|
||
pip install -e ".[report]" # 需要 Excel 报表
|
||
```
|
||
|
||
`requirements.txt` 锁定了端到端跑通过的版本(Python 3.10 验证)。
|
||
|
||
---
|
||
|
||
## 3. 三件输入的准备
|
||
|
||
### 3.1 CAD STP
|
||
|
||
- 提供面板的 STEP 文件路径。
|
||
- 算法用它做两件事:
|
||
1. 从拓扑直接提 4 个定位孔 + 中心大孔 + 面板法向(写入 `01_gt.json`,作审计用)
|
||
2. 把 CAD 法向作为 `panel_normal_seed` 喂给 hole-detection 的投影几何,加速并稳定 2D 网格化
|
||
- **可以不传**(`cad_stp_path=None`)—— 此时 hole detection 走 PCA 自行估法向;适用于你已经预先把法向算好/扫描坐标系本来就规范的场景。建议生产里**始终传**,便于审计。
|
||
|
||
### 3.2 点云 PLY
|
||
|
||
- 支持 `.ply` / `.pcd` / `.xyz` / `.txt` / `.csv`(PCD 与二进制 PLY 需要 open3d)。
|
||
- 数组形状 `(N, 3)`,单位 **mm**。
|
||
- **必须在 CAD 坐标系**:
|
||
- x = 车体纵向,y = 车体宽度,z = 车体高度
|
||
- 算法默认面板法向 `n_x > 0`
|
||
- **不再做配准**(参见 §6 "为什么不配准")。这意味着上游扫描端外参必须已经标定 —— 把点云对齐到 CAD 帧是扫描软件的职责,不是本算法的职责。
|
||
- 可以带颜色 / 法向等额外列;算法只读前 3 列。
|
||
|
||
### 3.3 ROI JSON
|
||
|
||
定义"PLY 里哪些点要纳入后续算法",**轴对齐 bounding box(AABB)**格式:
|
||
|
||
```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
|
||
}
|
||
```
|
||
|
||
六个 key 全部必填,单位 mm,坐标系 CAD 帧。点 `(x, y, z)` 被保留的条件是:
|
||
|
||
```
|
||
x_min <= x <= x_max ∧ y_min <= y <= y_max ∧ z_min <= z <= z_max
|
||
```
|
||
|
||
- **想保留全部点云**:把 6 个边界写得比点云自身 bbox 还宽即可(不需要任何 mode 切换)。
|
||
- **想不写文件**:传 `roi_json=None`,算法走"±∞" 默认(pass-through)。
|
||
- **想程序内构造**:传 `roi_spec=ROISpec(x_min=..., ...)`。
|
||
|
||
---
|
||
|
||
## 4. 公共 API 详解
|
||
|
||
### 4.1 主入口 `run_coupler_measurement_pipeline`
|
||
|
||
```python
|
||
def run_coupler_measurement_pipeline(
|
||
input_cloud : str | Path, # 必填:点云路径
|
||
out_dir : str | Path, # 必填:输出目录(自动创建)
|
||
*,
|
||
config_path : str | Path | None = None, # 可选:自定义 YAML
|
||
profile : str = "auto", # 可选:preset profile
|
||
cad_stp_path: str | Path | None = None, # 推荐:CAD STP 路径
|
||
roi_spec : ROISpec | None = None, # 二选一:内存 ROI 对象
|
||
roi_json : str | Path | None = None, # 二选一:ROI JSON 路径
|
||
progress : Callable[[str, float], None] | None = None,
|
||
) -> CouplerMeasurement
|
||
```
|
||
|
||
**算法链路(固定不可绕过)**:
|
||
```
|
||
load_point_cloud → apply_roi → [optional CAD parse → panel_normal_seed]
|
||
→ denoise_cloud → compute_coupler_measurement
|
||
├─ validate
|
||
├─ detect 4 holes
|
||
├─ build reference frame
|
||
├─ extract panel ROI
|
||
├─ fit panel plane (RANSAC + SVD)
|
||
└─ compute metrics
|
||
```
|
||
|
||
**Profile**:传 `"auto"` 即可;若你的面板型号对应到一个特定 profile,可指定 `profile="lu1_af4"` 等(详见 `configs/profiles/`)。
|
||
|
||
**配置覆盖**:
|
||
```python
|
||
result = run_coupler_measurement_pipeline(
|
||
input_cloud="scan.ply",
|
||
out_dir="runs/x",
|
||
config_path={
|
||
# 任何在 configs/default.yaml 里的字段都可以这样覆盖
|
||
"denoise": {"steps": [{"type": "voxel", "voxel_mm": 0.3}]},
|
||
"plane_fit": {"ransac_inlier_thresh_mm": 1.0},
|
||
"metrics": {"right_axis_sign": -1},
|
||
},
|
||
)
|
||
```
|
||
|
||
### 4.2 返回值 `CouplerMeasurement`
|
||
|
||
```python
|
||
@dataclass
|
||
class CouplerMeasurement:
|
||
# 版本号
|
||
schema_version: str # 字段语义稳定性 → SCHEMA_VERSION
|
||
algorithm_version: str # 数值结果稳定性 → ALGORITHM_VERSION
|
||
|
||
# === 6 个主测量量 ===
|
||
theta_right_deg: float # 右偏转角 atan2(n_y, n_x),applied right_axis_sign
|
||
theta_ref_deg: float # 上偏转角 atan2(n_z, n_x)
|
||
D_y_max_mm: float # max |y_i - C_y|
|
||
D_z_max_mm: float # max |z_i - C_z|
|
||
dx_y_max_mm: float # |A| · D_y_max, A = -n_y/n_x
|
||
dx_z_max_mm: float # |B| · D_z_max, B = -n_z/n_x
|
||
|
||
# === 几何 ===
|
||
center_C: tuple[float, float, float] # 面板中心 (4 孔均值)
|
||
normal: tuple[float, float, float] # 拟合面板法向,n_x > 0
|
||
|
||
# === 详细子结构 ===
|
||
holes: HoleSet # 检测到的 4 个定位孔 + 可选中心孔
|
||
plane: PanelPlane # normal + offset + inlier_ratio + RMS
|
||
frame: ReferenceFrame # C + 参考面 z=C_z + 纵向中面 y=C_y
|
||
|
||
# === 诊断 ===
|
||
quality_flags: list[str] # 例如 'plane_inlier_ratio_low'
|
||
timings: list[StageTiming] # 每阶段秒数 + 峰值内存
|
||
report: dict # 全量可序列化,与 report.json 一致
|
||
```
|
||
|
||
```python
|
||
result.to_dict() # 完全 JSON 化的 dict(落 DB / 网络传输用)
|
||
```
|
||
|
||
### 4.3 异常
|
||
|
||
```
|
||
CouplerError # 所有可预见异常的基类
|
||
├── InvalidPointCloudError # 输入形状/dtype/NaN 非法
|
||
│ └── UnsupportedFormatError # 文件后缀不支持
|
||
├── InsufficientPointsError # 点数 < 200 或 ROI 后 < 100
|
||
├── CADParseError # STP 解析失败 / 找不到 4 孔
|
||
├── HoleDetectionError # 点云上 4 孔检测失败
|
||
└── PlaneFitError # RANSAC 找不到平面
|
||
```
|
||
|
||
非致命问题通过 `result.quality_flags` 暴露:
|
||
|
||
| Flag | 含义 |
|
||
|---|---|
|
||
| `plane_inlier_ratio_low` | 平面拟合内点率低于 `plane_fit.min_inlier_ratio` |
|
||
|
||
### 4.4 进度回调
|
||
|
||
```python
|
||
def on_progress(stage: str, fraction: float) -> None:
|
||
print(f"[{fraction:5.1%}] {stage}")
|
||
|
||
result = run_coupler_measurement_pipeline(..., progress=on_progress)
|
||
```
|
||
|
||
阶段顺序:`validate → detect_holes → build_frame → extract_roi → fit_plane → metrics`。
|
||
|
||
---
|
||
|
||
## 5. ROI JSON schema 详细
|
||
|
||
**完整字段**(6 个 key,全部必填,单位 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
|
||
}
|
||
```
|
||
|
||
**规则**:
|
||
|
||
- 必须满足 `x_min < x_max` 且 `y_min < y_max` 且 `z_min < z_max`,否则 `load_roi_json` 抛 `ValueError`
|
||
- 不在 [min, max] 闭区间内的点被丢弃(用 `>=` 与 `<=`,包含边界)
|
||
- 多余的 key(例如 `comment`、`source`)会被忽略,方便挂元数据
|
||
|
||
**手动构造 ROI(不读文件)**:
|
||
|
||
```python
|
||
from coupler_panel.roi_select import ROISpec, default_roi_spec
|
||
|
||
# 全保留默认(六个轴都是 ±∞)
|
||
spec = default_roi_spec()
|
||
|
||
# 自定义 bbox
|
||
spec = ROISpec(
|
||
x_min=-12000, x_max= 100,
|
||
y_min= -300, y_max= 1200,
|
||
z_min= -1000, z_max= 800,
|
||
)
|
||
|
||
result = run_coupler_measurement_pipeline(..., roi_spec=spec)
|
||
```
|
||
|
||
**诊断输出**:
|
||
|
||
每次运行后 `out_dir/roi.json` 写出实际生效的 spec + 应用诊断:
|
||
|
||
```jsonc
|
||
{
|
||
"spec": { "x_min": -12000.0, ... },
|
||
"diagnostics": {
|
||
"kind": "aabb",
|
||
"n_in": 96662,
|
||
"n_out": 96662,
|
||
"kept_ratio": 1.0,
|
||
"bounds": { "x_min": -12000.0, ... }
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 为什么主路径**不做配准**
|
||
|
||
车钩面板上的 4 个定位孔与面板一起刚体旋转。如果用"扫到的 4 孔 → CAD 原始 4 孔"做 Kabsch 配准,数学上会**精确解出**"扫描器位姿误差 · 面板偏转"的复合刚体变换并反向作用 —— 把面板偏转随同位姿误差**一起消掉**,下游平面拟合再读不到偏转信号。
|
||
|
||
这是几何性质,不是实现 bug。
|
||
|
||
工程上,主算法假设扫描端外参标定让点云已经在 CAD 帧。如果你的设备做不到位姿标定,要么:
|
||
1. 在面板**外**装基准(不动)做 Kabsch
|
||
2. 用 ICP 或其他不依赖 4 孔的方法
|
||
|
||
研究模式(`coupler_panel.pipeline.run_from_cad_pipeline`)保留了 4 孔 Kabsch + 注入偏转 oracle 的能力,用于上限对照实验;生产集成请用 `run_coupler_measurement_pipeline`。
|
||
|
||
---
|
||
|
||
## 7. 落盘产物(`out_dir/`)
|
||
|
||
| 文件 | 内容 |
|
||
|---|---|
|
||
| `config.resolved.yaml` | 解析合并后实际生效的完整配置 |
|
||
| `roi.json` | ROI spec + 应用诊断(n_in / n_out / kept_ratio) |
|
||
| `01_gt.json` | (传了 cad_stp_path 时)从 STP 提的审计用 GT |
|
||
| `02_sampled.ply` | ROI 裁剪后的点云 |
|
||
| `05_denoised.ply` | 去噪后点云(喂给测量算法的最终点云) |
|
||
| `10_metrics.json` | 等价于 `report.json` 的内部 report 部分 |
|
||
| `report.json` | **完整结果**,等于 `result.to_dict()`;schema 见 §4.2 |
|
||
| `images/` | 占位空目录(仅 `viz` 模块写图时会用) |
|
||
|
||
---
|
||
|
||
## 8. 集成模板
|
||
|
||
```python
|
||
import logging
|
||
from pathlib import Path
|
||
|
||
from coupler_panel.pipeline import run_coupler_measurement_pipeline
|
||
from coupler_panel.exceptions import CouplerError
|
||
|
||
log = logging.getLogger(__name__)
|
||
|
||
|
||
def measure_coupler(
|
||
cloud_path: Path,
|
||
cad_stp: Path,
|
||
roi_json: Path,
|
||
out_dir: Path,
|
||
) -> dict | None:
|
||
"""主集成入口。失败时返回 None;成功时返回 6 个测量量 + flags。"""
|
||
try:
|
||
result = run_coupler_measurement_pipeline(
|
||
input_cloud = cloud_path,
|
||
cad_stp_path = cad_stp,
|
||
roi_json = roi_json,
|
||
out_dir = out_dir,
|
||
progress = lambda s, f: log.debug("%s %.0f%%", s, f * 100),
|
||
)
|
||
except CouplerError as exc:
|
||
log.error("coupler measurement failed: %s", exc)
|
||
return None
|
||
|
||
if result.quality_flags:
|
||
log.warning("quality flags: %s", result.quality_flags)
|
||
|
||
return {
|
||
"schema_version": result.schema_version,
|
||
"algorithm_version": result.algorithm_version,
|
||
"theta_right_deg": result.theta_right_deg,
|
||
"theta_ref_deg": result.theta_ref_deg,
|
||
"dx_y_max_mm": result.dx_y_max_mm,
|
||
"dx_z_max_mm": result.dx_z_max_mm,
|
||
"center_C_mm": list(result.center_C),
|
||
"normal": list(result.normal),
|
||
"flags": result.quality_flags,
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 命令行(备用,非主集成路径)
|
||
|
||
```bash
|
||
# 等价于 run_coupler_measurement_pipeline(input_cloud=..., out_dir=...)
|
||
coupler-measure --input-cloud scan.ply --out-dir runs/today
|
||
```
|
||
|
||
> 当前 CLI 暂未把 `--cad` 与 `--roi` 暴露出来(设计上 CLI 主要给研究流程用);集成方请直接用 Python API。
|
||
|
||
---
|
||
|
||
## 10. 版本兼容性
|
||
|
||
| 标识 | 在哪 | 含义 |
|
||
|---|---|---|
|
||
| `coupler_panel.__version__` | `coupler_panel._version` | 包版本,遵循语义化版本 |
|
||
| `coupler_panel.ALGORITHM_VERSION` | 同上 | 算法版本;升级算法时递增,**数值结果可能变化** |
|
||
| `coupler_panel.SCHEMA_VERSION` | 同上 | `CouplerMeasurement` / `report.json` 字段结构;只在破坏性变更时递增;**新增可选字段不递增** |
|
||
|
||
集成建议:
|
||
1. 落库时把 `algorithm_version` 写进每条记录,便于历史复现
|
||
2. 反序列化 `report.json` 时校验 `schema_version` 主版本号
|
||
|
||
---
|
||
|
||
## 11. 坐标 & 单位速查
|
||
|
||
| 量 | 单位 | 约定 |
|
||
|---|---|---|
|
||
| 点云坐标 | mm | x=纵向、y=宽度、z=高度 |
|
||
| 角度 | 度 (deg) | `θ_right` = atan2(n_y, n_x)、`θ_ref` = atan2(n_z, n_x) |
|
||
| 法向 | 单位向量 | 强制 `n_x > 0` |
|
||
| `right_axis_sign` | ±1 | 如果车体"右"是 +y 留 1;如果是 -y 则配置 -1 |
|