RAP / rectified_point_flow /data /transform.py
YuePanEdward's picture
Squash history: release superseded example-data blobs
be88765
Raw History Blame Contribute Delete
3.98 kB
import logging
from typing import Tuple
import os
import numpy as np
import trimesh
from scipy.spatial.transform import Rotation
logger = logging.getLogger("Data")
trimesh.util.log.setLevel(logging.ERROR)
if os.environ.get("USE_PCU", "0") == "1":
try:
import point_cloud_utils as pcu
use_pcu = True
logger.info("Using point_cloud_utils for point sampling.")
except ImportError:
logger.warning("point_cloud_utils not found, using trimesh.sample instead.")
use_pcu = False
else:
logger.info("Using trimesh.sample for point sampling.")
use_pcu = False
def sample_points_poisson(mesh: trimesh.Trimesh, count: int) -> Tuple[np.ndarray, np.ndarray]:
"""Sample points using Poisson disk sampling."""
if use_pcu:
v = mesh.vertices
f = mesh.faces
idx, bc = pcu.sample_mesh_poisson_disk(v, f, num_samples=count)
pts = pcu.interpolate_barycentric_coords(f, idx, bc, v)
else:
pts, idx = trimesh.sample.sample_surface_even(mesh, count=count)
return pts, idx
def sample_points_uniform(mesh: trimesh.Trimesh, count: int) -> Tuple[np.ndarray, np.ndarray]:
"""Sample points using uniform sampling."""
if use_pcu:
v = mesh.vertices
f = mesh.faces
idx, bc = pcu.sample_mesh_uniform(v, f, num_samples=count)
else:
pts, idx = trimesh.sample.sample_surface(mesh, count=count)
return pts, idx
def center_pcd(pcd: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
"""Center point cloud at origin."""
center = np.mean(pcd, axis=0)
pcd = pcd - center
return pcd, center
def rotate_pcd(pcd: np.ndarray, normals: np.ndarray = None) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Randomly rotate point cloud and normals."""
rot = Rotation.random()
pcd = rot.apply(pcd)
if normals is not None:
normals = rot.apply(normals)
rot_inv = rot.inv()
return pcd, normals, rot_inv.as_matrix()
def rotate_pcd_yaw(pcd: np.ndarray, normals: np.ndarray = None,
yaw_range: float = 360.0,
roll_pitch_range: float = 30.0) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Apply random yaw rotation around z-axis with small roll/pitch perturbations.
Args:
pcd: Point cloud of shape (N, 3)
normals: Normal vectors of shape (N, 3), optional
yaw_range: Range for yaw rotation in degrees (default: 360.0 for full rotation)
roll_pitch_range: Range for roll/pitch perturbations in degrees (default: 30.0)
Returns:
pcd: Rotated point cloud
normals: Rotated normal vectors (if provided)
rot_inv: Inverse rotation matrix (3, 3)
"""
# Generate random angles
yaw = np.random.uniform(-yaw_range/2, yaw_range/2) # Random yaw around z-axis
roll = np.random.uniform(-roll_pitch_range, roll_pitch_range) # Small roll perturbation
pitch = np.random.uniform(-roll_pitch_range, roll_pitch_range) # Small pitch perturbation
# Convert to radians
yaw_rad = np.radians(yaw)
roll_rad = np.radians(roll)
pitch_rad = np.radians(pitch)
# Create rotation matrix: R = Rz(yaw) * Rx(roll) * Ry(pitch)
# This applies yaw first, then small roll and pitch perturbations
rot = Rotation.from_euler('zxy', [yaw_rad, roll_rad, pitch_rad])
# Apply rotation to point cloud
pcd = rot.apply(pcd)
# Apply rotation to normals if provided
if normals is not None:
normals = rot.apply(normals)
# Return inverse rotation matrix for later use
rot_inv = rot.inv()
return pcd, normals, rot_inv.as_matrix()
def pad_data(input_data: np.ndarray, max_parts: int) -> np.ndarray:
"""Pad zeros to data of shape (p, ...) to (max_parts, ...)"""
d = np.array(input_data)
pad_shape = (max_parts,) + tuple(d.shape[1:])
pad_data = np.zeros(pad_shape, dtype=np.float32)
pad_data[: d.shape[0]] = d
return pad_data