feat: 算法升级、整车配准集成与性能分析文档

算法更新: delivery v1.2.1、平整度v1.5、测距v2.2、整车配准v1.3
适配器: vehicle-registration 新增、各算法 manifest 和 adapter 更新
后端: taskCadFiles/source 字段迁移、derivedPointClouds 注册、取消任务联动
前端: ElapsedTime 时长显示、两步算法流程、canRun 逻辑修复
文档: 新增5份算法性能分析报告
This commit is contained in:
2026-06-25 08:22:20 +00:00
parent cec0766e67
commit 29f1d45d66
266 changed files with 1311475 additions and 2846 deletions

21
.gitignore vendored
View File

@@ -143,5 +143,22 @@ alg/点云测距v1.1/train_measurement_system/results/
**/dist-info/ **/dist-info/
# 算法运行中间产物 # 算法运行中间产物
alg/delivery_v1.2/coupler_panel_release/runs/ alg/delivery_v1.2.1/coupler_panel_release/runs/
alg/delivery_v1.2/underframe_deflection/runs/ alg/delivery_v1.2.1/underframe_deflection/runs/
# ─── 平整度/平面度/轮廓测量v1.5(大文件排除)────────────────────────────────────
alg/平整度、平面度、轮廓测量v1.5/*.zip
alg/平整度、平面度、轮廓测量v1.5/datasets/
alg/平整度、平面度、轮廓测量v1.5/vis/
alg/平整度、平面度、轮廓测量v1.5/results/
alg/平整度、平面度、轮廓测量v1.5/**/*.ply
# ─── train_measurement_system_v2.2(大文件排除)─────────────────────────────────
alg/train_measurement_system_v2.2/**/*.ply
alg/train_measurement_system_v2.2/**/results/
alg/train_measurement_system_v2.2/**/visualizations*/
# ─── 整车配准v1.3(大文件排除)──────────────────────────────────────────────────
alg/整车配准v1.3/**/*.ply
alg/整车配准v1.3/datasets/
alg/整车配准v1.3/results/

View File

@@ -92,6 +92,15 @@ RUN PY_VER=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.versi
&& SYS_SP=$(python3 -c "import site; print(site.getsitepackages()[0])") \ && SYS_SP=$(python3 -c "import site; print(site.getsitepackages()[0])") \
&& ln -sf "${CONDA_SP}/OCC" "${SYS_SP}/OCC" && ln -sf "${CONDA_SP}/OCC" "${SYS_SP}/OCC"
# conda-forge 编译的 .so 需要新版 libstdc++(CXXABI_1.3.15);
# 动态查找 conda 提供的最新真实文件,避免硬编码版本号或双重软链问题
RUN set -eux \
&& LIBSTDCPP=$(ls /opt/conda/lib/libstdc++.so.6.0.* 2>/dev/null | sort -V | tail -1) \
&& [ -n "$LIBSTDCPP" ] && ln -sf "$LIBSTDCPP" /usr/lib/x86_64-linux-gnu/libstdc++.so.6 \
&& echo "libstdc++ -> $LIBSTDCPP" \
&& python3 -c "from OCC.Core.STEPControl import STEPControl_Reader; print('pythonocc-core OK')" \
&& python3 -c "from OCC.Core.BRep import BRep_Builder; print('OCC BRep OK')"
ENV PYTHON3_PATH=/usr/bin/python3 ENV PYTHON3_PATH=/usr/bin/python3
# 创建非 root 用户(bookworm 用 groupadd/useradd) # 创建非 root 用户(bookworm 用 groupadd/useradd)

View File

@@ -0,0 +1,12 @@
"""Batch-evaluation reporting helpers (xlsx output)."""
from .excel import EvalRow, write_excel_report, build_row_from_summary
from .inspection import InspectionItem, write_inspection_report
__all__ = [
"EvalRow",
"write_excel_report",
"build_row_from_summary",
"InspectionItem",
"write_inspection_report",
]

View File

@@ -0,0 +1,124 @@
"""Inspection-item Excel reports (项点报表).
Each report is one measurement item with the six columns the quality
department expects:
项点名称 | 理论尺寸 | 公差要求 | 测量结果 | 误差结果 | 测量结论
For the coupler panel, the two deflection angles are reported in a
**slope form** ``value/L mm`` (e.g. ``0.8/375mm``) rather than degrees,
because the tolerance is specified that way. ``value`` is the lateral
offset over a baseline of ``L`` mm:
value = L * tan(theta) (theta in radians)
so a measured angle ``theta`` becomes ``(L*tan theta)/L mm``. The
tolerance ``T/L mm`` is the angle whose offset over ``L`` is ``T``; the
result passes when ``|L*tan(theta)| <= T``.
"""
from __future__ import annotations
import math
from dataclasses import dataclass
from pathlib import Path
try:
from openpyxl import Workbook
from openpyxl.styles import Alignment, Font, PatternFill
except ImportError as exc: # pragma: no cover
raise ImportError(
"openpyxl is required for Excel reporting; install with `pip install openpyxl`"
) from exc
COLUMNS = ("项点名称", "理论尺寸", "公差要求", "测量结果", "误差结果", "测量结论")
@dataclass
class InspectionItem:
"""One deflection-angle inspection item, reported in ``value/L mm`` form."""
name: str # 项点名称
theta_deg: float # measured angle (degrees), theory = 0
baseline_mm: float # L in the slope form
tol_value_mm: float # tolerance numerator T (so tolerance = T/L mm)
# --- derived ---
@property
def offset_mm(self) -> float:
"""L * tan(theta): lateral offset over the baseline, mm."""
return self.baseline_mm * math.tan(math.radians(self.theta_deg))
@property
def theoretical_str(self) -> str:
return f"0/{self.baseline_mm:g}mm"
@property
def tolerance_str(self) -> str:
return f"{self.tol_value_mm:g}/{self.baseline_mm:g}mm"
@property
def measured_str(self) -> str:
return f"{self.offset_mm:.6f}/{self.baseline_mm:g}mm"
@property
def error_str(self) -> str:
# Error == measured result here (theory is 0).
return self.measured_str
@property
def passed(self) -> bool:
return abs(self.offset_mm) <= self.tol_value_mm
@property
def conclusion_str(self) -> str:
return "合格" if self.passed else "不合格"
def row(self) -> tuple[str, str, str, str, str, str]:
return (
self.name,
self.theoretical_str,
self.tolerance_str,
self.measured_str,
self.error_str,
self.conclusion_str,
)
def write_inspection_report(item: InspectionItem, path: str | Path) -> Path:
"""Write a single-item inspection report to an xlsx file."""
out = Path(path)
out.parent.mkdir(parents=True, exist_ok=True)
wb = Workbook()
ws = wb.active
ws.title = "inspection"
header_font = Font(bold=True)
header_fill = PatternFill("solid", fgColor="DDDDDD")
center = Alignment(horizontal="center", vertical="center")
for c, label in enumerate(COLUMNS, start=1):
cell = ws.cell(row=1, column=c, value=label)
cell.font = header_font
cell.fill = header_fill
cell.alignment = center
for c, val in enumerate(item.row(), start=1):
cell = ws.cell(row=2, column=c, value=val)
cell.alignment = center
# red font for 不合格 conclusion
if not item.passed:
ws.cell(row=2, column=6).font = Font(color="CC0000", bold=True)
widths = (44, 12, 14, 16, 16, 12)
for c, w in enumerate(widths, start=1):
ws.column_dimensions[ws.cell(row=1, column=c).column_letter].width = w
wb.save(out)
return out
__all__ = ["InspectionItem", "write_inspection_report", "COLUMNS"]

View File

@@ -0,0 +1,9 @@
{
"x_min": -11753.0,
"x_max": -11506.0,
"y_min": -950.0,
"y_max": 950.0,
"z_min": 788.0,
"z_max": 1192.0,
"comment": "Coupler mounting panel ROI on train_1 full-vehicle scan (m000004802409t02_asm). Six numbers are the cropping hyper-parameters consumed directly by the algorithm; coordinates are in the cloud's own frame."
}

View File

@@ -25,12 +25,15 @@ from __future__ import annotations
import argparse import argparse
import logging import logging
import math
import time import time
from pathlib import Path from pathlib import Path
import numpy as np
from coupler_panel.exceptions import CouplerError from coupler_panel.exceptions import CouplerError
from coupler_panel.pipeline import run_coupler_measurement_pipeline from coupler_panel.pipeline import run_coupler_measurement_pipeline
from coupler_panel.reporting import build_row_from_summary, write_excel_report from coupler_panel.reporting import InspectionItem, write_inspection_report
from coupler_panel.roi_select import load_roi_json from coupler_panel.roi_select import load_roi_json
@@ -80,32 +83,33 @@ def main(argv: list[str] | None = None) -> int:
return 2 return 2
pipeline_seconds = time.perf_counter() - t0 pipeline_seconds = time.perf_counter() - t0
# n_points after ROI crop is recorded in <out>/roi.json diagnostics. # --- Raw (pre-clamp) deflection angles from the fitted plane normal ----
import json as _json # Use the un-clamped normal so a tiny non-zero angle is NOT rounded to 0.
roi_diag = _json.loads((args.out / "roi.json").read_text(encoding="utf-8")) n = np.asarray(result.report["plane"]["normal"], dtype=np.float64)
n_roi = int(roi_diag.get("diagnostics", {}).get("n_out", 0)) theta_right_deg = math.degrees(math.atan2(n[1], n[0])) # 距纵向中心 (y)
n_in = int(roi_diag.get("diagnostics", {}).get("n_in", n_roi)) theta_ref_deg = math.degrees(math.atan2(n[2], n[0])) # 距参考面 (z)
row = build_row_from_summary( # --- Two inspection-item Excel reports (slope form value/L mm) ---------
sample_id=sample_id, item_ref = InspectionItem(
ply_path=str(args.cloud), name="车钩面板距参考面的右偏转角(头车一位端处车钩)",
roi_kind=f"aabb {roi_spec.x_min:.0f}-{roi_spec.x_max:.0f}," theta_deg=theta_ref_deg,
f"{roi_spec.y_min:.0f}-{roi_spec.y_max:.0f}," baseline_mm=375.0,
f"{roi_spec.z_min:.0f}-{roi_spec.z_max:.0f}", tol_value_mm=0.8, # 公差 0.8/375mm
measured=result.to_dict(), )
gt_angles=(args.gt_theta_right, args.gt_theta_ref), item_long = InspectionItem(
registration=None, name="车钩面板距纵向中心的右偏转角(头车一位端处车钩)",
n_points_in=n_in, theta_deg=theta_right_deg,
n_points_after_roi=n_roi, baseline_mm=700.0,
pipeline_seconds=pipeline_seconds, tol_value_mm=1.0, # 公差 1/700mm
) )
# Record the STP path in the row's extra metadata (audit trail).
row.extra["stp_audit_path"] = str(args.stp) if args.stp else ""
xlsx = write_excel_report([row], args.out / "eval_report.xlsx") out_ref = write_inspection_report(item_ref, args.out / "report_ref_plane.xlsx")
log.info("theta_right=%+.4f theta_ref=%+.4f flags=%s", out_long = write_inspection_report(item_long, args.out / "report_longitudinal.xlsx")
result.theta_right_deg, result.theta_ref_deg, result.quality_flags)
log.info("wrote %s", xlsx) for it, p in ((item_ref, out_ref), (item_long, out_long)):
log.info("%s | 测量结果=%s | 公差=%s | %s",
it.name, it.measured_str, it.tolerance_str, it.conclusion_str)
log.info("wrote %s , %s", out_ref, out_long)
return 0 return 0

View File

@@ -132,26 +132,30 @@ report = run_single_beam_pipeline(
--- ---
## 5. 输出 Excel 字段(report.xlsx) ## 5. 输出 Excel(report.xlsx,左右各一份)
### Sheet「Summary」(key/value) ### Sheet「检测报表」(核心交付,6 列)
| 字段 | 含义 | | 列 | 本项目取值 |
| --- | --- | | --- | --- |
| `mode` / `side` | `single_beam` / `left`、`right` | | 项点名称 | 纵向梁到直线的垂直偏差(挠度) |
| `input_cloud` / `roi_source` / `cad_reference` | 三个输入文件的绝对路径 | | 理论尺寸 | 0 |
| `registration_performed` / `registration_note` | 是否配准 + 说明 | | 公差要求 | 左梁 ±7mm / 右梁 ±12mm |
| `roi_point_count` | ROI 裁剪命中点数 | | 测量结果 | 实测最大挠度(mm) |
| `n_slices` / `n_kept` | 沿车长切片数 / 鲁棒拟合保留的切片数 | | 误差结果 | 与理论 0 的误差(= 测量结果,因理论值为 0) |
| `reference_slope_mm_per_m` | 拟合参考线斜率 | | 测量结论 | 误差 ≤ 公差 → **合格**,否则 → **不合格** |
| **`selected_signed_deflection_mm`** | **最大绝对挠度(带符号)← 核心结果** |
| `selected_absolute_deflection_mm` | 最大绝对挠度 |
| `selected_x_mm` | 该点的车长位置 |
| `max_downward_mm` / `max_upward_mm` | 最大下凹 / 上凸挠度 |
| `rms_mm` | 挠度 RMS |
| `quality_flags` | 质量警告(空 = 正常) |
### Sheet「Deflection_Curve」 **左右判定**:面朝 x 正方向,`z > 0` 为左梁(公差 7mm),`z < 0` 为右梁(公差 12mm)。
程序按 ROI 的 z 中心**自动判定**,不依赖文件名——即使左右 ROI 文件传反也不会用错公差。
公差可覆盖:命令行 `--left-tolerance` / `--right-tolerance`(两梁模式)或 `--tolerance`(单梁模式);
Python API 同名参数 `left_tolerance_mm` / `right_tolerance_mm` / `tolerance_mm`。
### Sheet「明细」(溯源,key/value)
输入文件路径、是否配准、ROI 命中点数、切片数、参考线斜率、最大下凹/上凸、RMS、质量警告等。
### Sheet「挠度曲线」
沿车长逐点表:`x_mm, underside_height_mm, reference_height_mm, deflection_mm, kept`。 沿车长逐点表:`x_mm, underside_height_mm, reference_height_mm, deflection_mm, kept`。

View File

@@ -56,7 +56,27 @@ def parse_args() -> argparse.Namespace:
parser.add_argument( parser.add_argument(
"--side", "--side",
default=None, default=None,
help="Single mode label for --roi (left/right); inferred from the file name if omitted.", help="Single mode label for --roi (left/right); inferred from the ROI's "
"lateral z (x-forward: z>0 left, z<0 right) if omitted.",
)
parser.add_argument(
"--tolerance",
type=float,
default=None,
help="Single mode --roi: pass/fail tolerance in mm. Default by side "
"(left 7, right 12).",
)
parser.add_argument(
"--left-tolerance",
type=float,
default=None,
help="Both-beam mode: left-beam tolerance in mm (default 7).",
)
parser.add_argument(
"--right-tolerance",
type=float,
default=None,
help="Both-beam mode: right-beam tolerance in mm (default 12).",
) )
parser.add_argument( parser.add_argument(
"--enable-registration", "--enable-registration",
@@ -135,6 +155,8 @@ def main() -> None:
config=config, config=config,
cad_path=args.cad, cad_path=args.cad,
enable_registration=args.enable_registration, enable_registration=args.enable_registration,
left_tolerance_mm=args.left_tolerance,
right_tolerance_mm=args.right_tolerance,
skip_excel=args.skip_excel, skip_excel=args.skip_excel,
skip_plot=args.skip_plot, skip_plot=args.skip_plot,
) )
@@ -144,6 +166,7 @@ def main() -> None:
roi_path=args.roi, roi_path=args.roi,
out_dir=args.out_dir, out_dir=args.out_dir,
side=args.side, side=args.side,
tolerance_mm=args.tolerance,
config=config, config=config,
cad_path=args.cad, cad_path=args.cad,
enable_registration=args.enable_registration, enable_registration=args.enable_registration,

Some files were not shown because too many files have changed in this diff Show More