Files
zed-playground/py_workspace/aruco/marker_geometry.py
T
2026-02-04 11:44:37 +00:00

86 lines
2.6 KiB
Python

import numpy as np
import awkward as ak
from pathlib import Path
from typing import Union, cast
def load_marker_geometry(parquet_path: Union[str, Path]) -> dict[int, np.ndarray]:
"""
Reads ArUco marker geometry from a parquet file.
The parquet file is expected to have 'ids' and 'corners' columns,
similar to standard_box_markers.parquet.
Args:
parquet_path: Path to the parquet file.
Returns:
A dictionary mapping marker IDs to their 3D corner coordinates.
The corners are represented as a (4, 3) float32 numpy array.
"""
path = Path(parquet_path)
if not path.exists():
raise FileNotFoundError(f"Parquet file not found: {path}")
ops = ak.from_parquet(path)
# Extract IDs and corners using logic similar to find_extrinsic_object.py
total_ids = cast(np.ndarray, ak.to_numpy(ops["ids"])).flatten()
total_corners = cast(np.ndarray, ak.to_numpy(ops["corners"])).reshape(-1, 4, 3)
# Create the mapping and ensure coordinates are float32
marker_geometry = {
int(marker_id): corners.astype(np.float32)
for marker_id, corners in zip(total_ids, total_corners)
}
return marker_geometry
def validate_marker_geometry(geometry: dict[int, np.ndarray]) -> None:
"""
Validates the marker geometry dictionary.
Checks:
- Dictionary is not empty.
- Every entry has shape (4, 3).
- Every entry contains only finite numbers (no NaN or Inf).
- Coordinates are within a reasonable range (abs(coord) < 100m).
Args:
geometry: The marker geometry dictionary to validate.
Raises:
ValueError: If any validation check fails.
"""
if not geometry:
raise ValueError("Marker geometry is empty.")
for marker_id, corners in geometry.items():
# Check shape
if corners.shape != (4, 3):
raise ValueError(
f"Invalid shape for marker {marker_id}: "
f"expected (4, 3), got {corners.shape}"
)
# Check for non-finite values (NaN, Inf)
if not np.isfinite(corners).all():
raise ValueError(
f"Marker {marker_id} contains non-finite values (NaN or Inf)."
)
# Check for reasonable range (e.g., < 100 meters)
if np.any(np.abs(corners) > 100.0):
raise ValueError(
f"Marker {marker_id} has coordinates exceeding reasonable range (> 100m): "
f"{corners}"
)
def expected_ids(geometry: dict[int, np.ndarray]) -> set[int]:
"""
Returns the set of marker IDs present in the geometry.
"""
return set(geometry.keys())