Files
massive-cloud/alg/delivery_v1.2.1/coupler_panel_release/coupler_panel/cli.py
lipan 29f1d45d66 feat: 算法升级、整车配准集成与性能分析文档
算法更新: delivery v1.2.1、平整度v1.5、测距v2.2、整车配准v1.3
适配器: vehicle-registration 新增、各算法 manifest 和 adapter 更新
后端: taskCadFiles/source 字段迁移、derivedPointClouds 注册、取消任务联动
前端: ElapsedTime 时长显示、两步算法流程、canRun 逻辑修复
文档: 新增5份算法性能分析报告
2026-06-25 08:22:20 +00:00

162 lines
5.0 KiB
Python

"""``coupler-measure`` command-line entry point.
Three modes::
# measure an existing point cloud (production-like flow)
coupler-measure --input-cloud scan.ply --out-dir runs/demo
# research: synthesise a cloud from a STEP file and measure it
coupler-measure --stp panel.stp --out-dir runs/from_cad
# research: parameter sweep
coupler-measure --sweep matrix.yaml --out-dir runs/sweep_xx
"""
from __future__ import annotations
import argparse
import json
import logging
import sys
from pathlib import Path
log = logging.getLogger("coupler_panel.cli")
def _build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(
prog="coupler-measure",
description="Coupler-panel CAD-STP to point-cloud measurement & validation.",
)
src = p.add_mutually_exclusive_group(required=True)
src.add_argument("--input-cloud", type=Path, help="Path to a PLY/XYZ/CSV/PCD point cloud.")
src.add_argument("--stp", type=Path, help="Path to a STEP file (research mode).")
src.add_argument("--sweep", type=Path, help="Path to a sweep matrix YAML.")
p.add_argument("--out-dir", type=Path, required=True, help="Output directory.")
p.add_argument("--config", type=Path, default=None, help="YAML config overrides.")
p.add_argument(
"--profile",
type=str,
default="auto",
help="auto | default | high_density | low_density | custom",
)
p.add_argument(
"--target-holes",
type=Path,
default=None,
help="DEPRECATED — the main pipeline no longer performs 4-hole Kabsch "
"registration (it cancels the panel deflection signal). The flag "
"is accepted for backward compatibility but is ignored.",
)
p.add_argument(
"--render",
action="store_true",
help="After measurement, render PNG diagnostics into <out-dir>/images/.",
)
p.add_argument("-v", "--verbose", action="count", default=0, help="-v: INFO, -vv: DEBUG")
return p
def _setup_logging(verbose: int) -> None:
level = logging.WARNING
if verbose == 1:
level = logging.INFO
elif verbose >= 2:
level = logging.DEBUG
logging.basicConfig(
level=level,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
def _on_progress(stage: str, fraction: float) -> None:
log.info("[%5.1f%%] %s", fraction * 100, stage)
def _maybe_render(out_dir: Path) -> None:
try:
from .viz import render_all
render_all(out_dir)
except ImportError:
log.warning("matplotlib not installed; skip --render")
except Exception as exc:
log.warning("render_all failed: %s", exc)
def main(argv: list[str] | None = None) -> int:
args = _build_parser().parse_args(argv)
_setup_logging(args.verbose)
try:
if args.input_cloud is not None:
from .pipeline import run_coupler_measurement_pipeline
if args.target_holes is not None:
log.warning(
"--target-holes is deprecated and ignored; the main pipeline "
"no longer performs Kabsch registration."
)
result = run_coupler_measurement_pipeline(
input_cloud=args.input_cloud,
out_dir=args.out_dir,
config_path=args.config,
profile=args.profile,
progress=_on_progress,
)
print(
json.dumps(
{
"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,
"quality_flags": result.quality_flags,
},
indent=2,
ensure_ascii=False,
)
)
elif args.stp is not None:
from .pipeline import run_from_cad_pipeline
summary = run_from_cad_pipeline(
stp_path=args.stp,
out_dir=args.out_dir,
config_path=args.config,
profile=args.profile,
progress=_on_progress,
)
print(
json.dumps(
{
"diff": summary["diff"],
"quality_flags": summary["measured"]["quality_flags"],
},
indent=2,
ensure_ascii=False,
)
)
elif args.sweep is not None:
from importlib import import_module
mod = import_module("experiments.sweep_runner")
mod.main(matrix_path=args.sweep, out_dir=args.out_dir, config_path=args.config)
if args.render:
_maybe_render(args.out_dir)
except Exception as exc:
log.exception("pipeline failed: %s", exc)
return 2
return 0
if __name__ == "__main__":
sys.exit(main())