feat: initial commit - massive-cloud point cloud processing system
This commit is contained in:
161
alg/delivery_v1.1/coupler_panel_release/coupler_panel/cli.py
Normal file
161
alg/delivery_v1.1/coupler_panel_release/coupler_panel/cli.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""``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())
|
||||
Reference in New Issue
Block a user