Files
massive-cloud/alg/delivery_v1.2.1/underframe_deflection/roi.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

98 lines
3.3 KiB
Python

"""Region-of-interest helpers driven by an external JSON file.
Supports a single axis-aligned bounding box (AABB) in the CAD coordinate
frame, units millimetres. The AABB is used in two places:
1. ``preprocess_point_cloud`` applies it as an early passthrough crop
(performance + noise reduction).
2. ``frame.build_bottom_roi_mask`` consumes the same AABB to build the
canonical ROI mask used by the deflection stages.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
import numpy as np
from .exceptions import InvalidPointCloudError
_REQUIRED_KEYS = ("x_min", "x_max", "y_min", "y_max", "z_min", "z_max")
def load_roi_aabb(path: str | Path) -> dict[str, float]:
"""Load an AABB ROI specification from a JSON file.
Returns a normalised dict with the six float fields. Raises
``InvalidPointCloudError`` for missing keys, non-numeric values, or
inverted ranges (min >= max).
"""
path = Path(path)
try:
raw = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise InvalidPointCloudError(f"Failed to read ROI JSON {path}: {exc}") from exc
if not isinstance(raw, dict):
raise InvalidPointCloudError(
f"ROI JSON {path} must be a JSON object with AABB fields."
)
aabb: dict[str, float] = {}
for key in _REQUIRED_KEYS:
if key not in raw:
raise InvalidPointCloudError(
f"ROI JSON {path} missing required key '{key}'."
)
try:
aabb[key] = float(raw[key])
except (TypeError, ValueError) as exc:
raise InvalidPointCloudError(
f"ROI JSON {path} key '{key}' is not numeric: {raw[key]!r}"
) from exc
_validate_aabb(aabb, source=str(path))
return aabb
def normalize_aabb(aabb: dict[str, Any]) -> dict[str, float]:
"""Validate and coerce an in-memory AABB dict to ``{key: float}``."""
coerced: dict[str, float] = {}
for key in _REQUIRED_KEYS:
if key not in aabb:
raise InvalidPointCloudError(f"AABB dict missing required key '{key}'.")
coerced[key] = float(aabb[key])
_validate_aabb(coerced, source="<inline>")
return coerced
def _validate_aabb(aabb: dict[str, float], *, source: str) -> None:
for axis in ("x", "y", "z"):
lo = aabb[f"{axis}_min"]
hi = aabb[f"{axis}_max"]
if not (np.isfinite(lo) and np.isfinite(hi)):
raise InvalidPointCloudError(
f"AABB from {source} has non-finite bound on axis {axis}."
)
if lo >= hi:
raise InvalidPointCloudError(
f"AABB from {source} has inverted range on axis {axis}: "
f"{axis}_min={lo} >= {axis}_max={hi}."
)
def apply_passthrough(points: np.ndarray, aabb: dict[str, float]) -> np.ndarray:
"""Return a boolean mask of points inside the AABB (inclusive)."""
aabb = normalize_aabb(aabb)
return (
(points[:, 0] >= aabb["x_min"])
& (points[:, 0] <= aabb["x_max"])
& (points[:, 1] >= aabb["y_min"])
& (points[:, 1] <= aabb["y_max"])
& (points[:, 2] >= aabb["z_min"])
& (points[:, 2] <= aabb["z_max"])
)
__all__ = ["load_roi_aabb", "normalize_aabb", "apply_passthrough"]