"""Integration entry point — the contract for the software integrator. Three input files, one Excel output: --roi ROI JSON (6-key AABB crop hyper-parameters, in the cloud frame) --cloud full-vehicle point cloud PLY (may be multi-GB; cropped by streaming) --stp theoretical CAD STEP file (recorded for audit/traceability) --out output directory (gets eval_report.xlsx + intermediates) The cloud is cropped to the ROI inside the algorithm (no external pre-processing needed), the coupler panel is measured, and the two deflection angles + intermediate values are written to an .xlsx report. Example:: python examples/run_integration.py \ --roi examples/roi_train2_coupler.json \ --cloud E:/BaiduNetdiskDownload/pointsCloud_data/train_2_1T.ply \ --stp E:/BaiduNetdiskDownload/pointsCloud_data/m000004802418tc01_asm.stp \ --out runs/train2_integration \ --profile train2_coupler """ from __future__ import annotations import argparse import logging import math import time from pathlib import Path import numpy as np from coupler_panel.exceptions import CouplerError from coupler_panel.pipeline import run_coupler_measurement_pipeline from coupler_panel.reporting import InspectionItem, write_inspection_report from coupler_panel.roi_select import load_roi_json def _build_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser(description=__doc__) p.add_argument("--roi", type=Path, required=True, help="ROI JSON (6-key AABB).") p.add_argument("--cloud", type=Path, required=True, help="Full-vehicle point cloud PLY.") p.add_argument("--stp", type=Path, default=None, help="Theoretical CAD STEP (audit).") p.add_argument("--out", type=Path, required=True, help="Output directory.") p.add_argument("--profile", type=str, default="train2_coupler", help="Config profile (default: train2_coupler).") p.add_argument("--config", type=Path, default=None, help="Optional YAML override.") p.add_argument("--gt-theta-right", type=float, default=0.0, help="Known/expected theta_right (deg) for the error column. Default 0.") p.add_argument("--gt-theta-ref", type=float, default=0.0, help="Known/expected theta_ref (deg) for the error column. Default 0.") p.add_argument("--sample-id", type=str, default=None, help="Row label in the report.") return p def main(argv: list[str] | None = None) -> int: logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s") log = logging.getLogger("run_integration") args = _build_parser().parse_args(argv) roi_spec = load_roi_json(args.roi) log.info("ROI bbox: x[%g,%g] y[%g,%g] z[%g,%g]", roi_spec.x_min, roi_spec.x_max, roi_spec.y_min, roi_spec.y_max, roi_spec.z_min, roi_spec.z_max) log.info("cloud: %s", args.cloud) log.info("stp (audit): %s", args.stp) sample_id = args.sample_id or args.cloud.stem t0 = time.perf_counter() try: result = run_coupler_measurement_pipeline( input_cloud=args.cloud, out_dir=args.out, config_path=args.config, profile=args.profile, roi_spec=roi_spec, progress=lambda s, f: log.info("[%5.1f%%] %s", f * 100, s), ) except CouplerError as exc: log.error("measurement failed: %s", exc) return 2 pipeline_seconds = time.perf_counter() - t0 # --- Raw (pre-clamp) deflection angles from the fitted plane normal ---- # Use the un-clamped normal so a tiny non-zero angle is NOT rounded to 0. n = np.asarray(result.report["plane"]["normal"], dtype=np.float64) theta_right_deg = math.degrees(math.atan2(n[1], n[0])) # 距纵向中心 (y) theta_ref_deg = math.degrees(math.atan2(n[2], n[0])) # 距参考面 (z) # --- Two inspection-item Excel reports (slope form value/L mm) --------- item_ref = InspectionItem( name="车钩面板距参考面的右偏转角(头车一位端处车钩)", theta_deg=theta_ref_deg, baseline_mm=375.0, tol_value_mm=0.8, # 公差 0.8/375mm ) item_long = InspectionItem( name="车钩面板距纵向中心的右偏转角(头车一位端处车钩)", theta_deg=theta_right_deg, baseline_mm=700.0, tol_value_mm=1.0, # 公差 1/700mm ) out_ref = write_inspection_report(item_ref, args.out / "report_ref_plane.xlsx") out_long = write_inspection_report(item_long, args.out / "report_longitudinal.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 if __name__ == "__main__": raise SystemExit(main())