feat: initial commit - massive-cloud point cloud processing system

This commit is contained in:
2026-05-27 09:51:42 +00:00
commit 16811100d1
391 changed files with 78338 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
"""Deflection-angle and maximum-deviation computations + GT comparison."""
from .deflection import (
DeflectionMetrics,
compute_deflection_metrics,
)
from .compare import compare_to_gt, GTComparison
__all__ = [
"DeflectionMetrics",
"compute_deflection_metrics",
"compare_to_gt",
"GTComparison",
]

View File

@@ -0,0 +1,60 @@
"""Compare measured deflection against the GT (CAD-derived) values."""
from __future__ import annotations
from dataclasses import dataclass
from .deflection import DeflectionMetrics
@dataclass(frozen=True)
class GTComparison:
measured: DeflectionMetrics
gt: DeflectionMetrics
@property
def d_theta_right_deg(self) -> float:
return self.measured.theta_right_deg - self.gt.theta_right_deg
@property
def d_theta_ref_deg(self) -> float:
return self.measured.theta_ref_deg - self.gt.theta_ref_deg
@property
def d_D_y_max_mm(self) -> float:
return self.measured.D_y_max_mm - self.gt.D_y_max_mm
@property
def d_D_z_max_mm(self) -> float:
return self.measured.D_z_max_mm - self.gt.D_z_max_mm
@property
def d_dx_y_max_mm(self) -> float:
return self.measured.dx_y_max_mm - self.gt.dx_y_max_mm
@property
def d_dx_z_max_mm(self) -> float:
return self.measured.dx_z_max_mm - self.gt.dx_z_max_mm
def to_dict(self) -> dict[str, float | dict]:
return {
"measured": self.measured.to_dict(),
"gt": self.gt.to_dict(),
"diff": {
"theta_right_deg": self.d_theta_right_deg,
"theta_ref_deg": self.d_theta_ref_deg,
"D_y_max_mm": self.d_D_y_max_mm,
"D_z_max_mm": self.d_D_z_max_mm,
"dx_y_max_mm": self.d_dx_y_max_mm,
"dx_z_max_mm": self.d_dx_z_max_mm,
"abs_theta_right_deg": abs(self.d_theta_right_deg),
"abs_theta_ref_deg": abs(self.d_theta_ref_deg),
},
}
def compare_to_gt(measured: DeflectionMetrics, gt: DeflectionMetrics) -> GTComparison:
return GTComparison(measured=measured, gt=gt)
__all__ = ["GTComparison", "compare_to_gt"]

View File

@@ -0,0 +1,103 @@
"""Deflection-angle and maximum-deviation formulas.
For a fitted panel-plane normal :math:`(n_x, n_y, n_z)` with
:math:`n_x > 0`, the spec is::
theta_right_deg = atan2(n_y, n_x) * 180/pi (right-axis sign applied)
theta_ref_deg = atan2(n_z, n_x) * 180/pi
A = -n_y / n_x
B = -n_z / n_x
D_y_max = max |y_i - C_y|
D_z_max = max |z_i - C_z|
dx_y_max = |A| * D_y_max
dx_z_max = |B| * D_z_max
``points`` should already be in the *world / CAD* frame, i.e. after the
4-hole Kabsch registration, and clipped to the panel ROI so the maxima
do not pick up edge or chamfer pixels.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
import numpy as np
@dataclass(frozen=True)
class DeflectionMetrics:
theta_right_deg: float
theta_ref_deg: float
D_y_max_mm: float
D_z_max_mm: float
dx_y_max_mm: float
dx_z_max_mm: float
A: float # -n_y / n_x
B: float # -n_z / n_x
def to_dict(self) -> dict[str, Any]:
return {
"theta_right_deg": self.theta_right_deg,
"theta_ref_deg": self.theta_ref_deg,
"D_y_max_mm": self.D_y_max_mm,
"D_z_max_mm": self.D_z_max_mm,
"dx_y_max_mm": self.dx_y_max_mm,
"dx_z_max_mm": self.dx_z_max_mm,
"A": self.A,
"B": self.B,
}
def compute_deflection_metrics(
*,
normal: tuple[float, float, float] | np.ndarray,
center_C: tuple[float, float, float] | np.ndarray,
panel_points: np.ndarray,
right_axis_sign: int = 1,
angle_clamp_deg: float = 0.0,
) -> DeflectionMetrics:
"""Compute the six measurands."""
n = np.asarray(normal, dtype=np.float64).reshape(3)
if n[0] <= 0:
# Caller is expected to enforce n_x > 0 before this; we hard-fail
# because every formula divides by n_x.
raise ValueError(
f"compute_deflection_metrics requires n_x > 0; got n = {tuple(n)}"
)
C = np.asarray(center_C, dtype=np.float64).reshape(3)
pts = np.asarray(panel_points, dtype=np.float64)
if pts.ndim != 2 or pts.shape[1] != 3:
raise ValueError(f"panel_points must be (N,3); got {pts.shape}")
if pts.shape[0] == 0:
raise ValueError("panel_points is empty")
sign = 1.0 if int(right_axis_sign) >= 0 else -1.0
theta_right = sign * float(np.degrees(np.arctan2(n[1], n[0])))
theta_ref = float(np.degrees(np.arctan2(n[2], n[0])))
if abs(theta_right) < float(angle_clamp_deg):
theta_right = 0.0
if abs(theta_ref) < float(angle_clamp_deg):
theta_ref = 0.0
A = float(-n[1] / n[0])
B = float(-n[2] / n[0])
D_y_max = float(np.max(np.abs(pts[:, 1] - C[1])))
D_z_max = float(np.max(np.abs(pts[:, 2] - C[2])))
return DeflectionMetrics(
theta_right_deg=theta_right,
theta_ref_deg=theta_ref,
D_y_max_mm=D_y_max,
D_z_max_mm=D_z_max,
dx_y_max_mm=abs(A) * D_y_max,
dx_z_max_mm=abs(B) * D_z_max,
A=A,
B=B,
)
__all__ = ["DeflectionMetrics", "compute_deflection_metrics"]