Files
massive-cloud/alg/delivery_v1.2/coupler_panel_release/coupler_panel/structures.py
lipan cec0766e67 feat: 升级算法包 delivery_v1.1 → v1.2,新增任务级 CAD 与测距面板
- 算法包替换为 delivery_v1.2(coupler_panel 新增 autotune/layer_select,underframe_deflection 新增 single_beam 管线),删除 delivery_v1.1
- manifests/adapters 的 PYTHONPATH 与版本说明同步更新到 v1.2
- 新增任务级 CAD 文件支持(task_cad_files 表、上传/读取路由、TaskManager 全选与加载显示)
- 新增 train-measurement 自定义前端面板(YAML 编辑器 + 可视化 ROI 配置 + 3D 取点)
- PointCloudViewer 支持 PLY 叠加层渲染与 ROI 拾取
- Dockerfile 改用 Miniforge 安装 pythonocc-core,修复 libgomp/libstdc++ 依赖
2026-06-11 02:30:04 +00:00

149 lines
4.4 KiB
Python

"""Result dataclasses for coupler_panel.
These types form the **stable public schema** of the package; their
field names and semantics are guarded by ``SCHEMA_VERSION`` (see
``_version.py``).
All numeric units are **mm** for distances and **degree** for angles
unless otherwise noted.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, field
from typing import Any
import numpy as np
# ---------------------------------------------------------------------------
# Building blocks
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class Hole:
"""A single detected (or GT) circular hole on the panel."""
center: tuple[float, float, float] # (x, y, z), mm
radius_mm: float
fit_residual_rms_mm: float # 0 for GT
role: str # "locating" | "central" | "unknown"
@dataclass(frozen=True)
class HoleSet:
"""The four locating holes plus an optional central hole.
``locating`` is ordered consistently between GT and detection so that
Kabsch pairing is index-aligned.
"""
locating: tuple[Hole, Hole, Hole, Hole]
central: Hole | None = None
def locating_centers(self) -> np.ndarray:
"""Return a (4, 3) ndarray of locating-hole centres."""
return np.asarray([h.center for h in self.locating], dtype=np.float64)
@dataclass(frozen=True)
class PanelPlane:
"""The fitted (or GT) panel plane."""
normal: tuple[float, float, float] # unit vector, n_x > 0 enforced
point_on_plane: tuple[float, float, float]
d_offset: float # n . x + d = 0
inlier_ratio: float # 1.0 for GT
inlier_residual_rms_mm: float # 0 for GT
@dataclass(frozen=True)
class ReferenceFrame:
"""Reference geometry derived from the four-hole panel centre."""
center_C: tuple[float, float, float]
# Reference line L = C + lam * e_x (always parallel to the +x axis).
line_direction: tuple[float, float, float] = (1.0, 0.0, 0.0)
# Reference plane: z = C_z. Stored as plane normal + offset.
reference_plane_z: float = 0.0
# Longitudinal mid-plane: y = C_y.
longitudinal_mid_y: float = 0.0
@dataclass(frozen=True)
class StageTiming:
"""Per-stage wall-clock and peak-memory cost."""
stage: str
seconds: float
peak_kib: float
# ---------------------------------------------------------------------------
# Top-level result
# ---------------------------------------------------------------------------
@dataclass
class CouplerMeasurement:
"""Top-level result of the coupler-panel measurement pipeline.
See ``docs/USAGE.md`` for the stable-API contract.
"""
schema_version: str
algorithm_version: str
# --- Primary measurands ---
theta_right_deg: float # atan2(n_y, n_x), with right_axis_sign applied
theta_ref_deg: float # atan2(n_z, n_x)
D_y_max_mm: float # max |y_i - C_y| over panel ROI
D_z_max_mm: float # max |z_i - C_z| over panel ROI
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
# --- Geometry ---
center_C: tuple[float, float, float]
normal: tuple[float, float, float] # n_x > 0
holes: HoleSet
plane: PanelPlane
frame: ReferenceFrame
# --- Diagnostics ---
quality_flags: list[str] = field(default_factory=list)
timings: list[StageTiming] = field(default_factory=list)
report: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]:
"""Return a fully JSON-serialisable dict."""
return _to_jsonable(asdict(self))
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _to_jsonable(obj: Any) -> Any:
"""Recursively convert numpy / tuple structures into plain Python types."""
if isinstance(obj, dict):
return {k: _to_jsonable(v) for k, v in obj.items()}
if isinstance(obj, (list, tuple)):
return [_to_jsonable(v) for v in obj]
if isinstance(obj, np.ndarray):
return obj.tolist()
if isinstance(obj, (np.floating, np.integer)):
return obj.item()
return obj
__all__ = [
"Hole",
"HoleSet",
"PanelPlane",
"ReferenceFrame",
"StageTiming",
"CouplerMeasurement",
]