""" I/O helpers for images and point-cloud files. """ from __future__ import annotations from pathlib import Path from typing import Optional, Tuple, Union import numpy as np from PIL import Image def load_image(path: Union[str, Path, Image.Image, np.ndarray]) -> Image.Image: """ Normalise any image input to a PIL RGB image. Parameters ---------- path : str, Path, PIL.Image, or np.ndarray If a string/Path, loaded from disk. If an ndarray, converted. Returns ------- PIL.Image.Image RGB image ready for DepthPro. """ if isinstance(path, (str, Path)): return Image.open(str(path)).convert("RGB") if isinstance(path, np.ndarray): if path.dtype != np.uint8: path = (path * 255).clip(0, 255).astype(np.uint8) return Image.fromarray(path).convert("RGB") if isinstance(path, Image.Image): return path.convert("RGB") raise TypeError(f"Unsupported image type: {type(path)}") def save_point_cloud( path: Union[str, Path], points: np.ndarray, colors: Optional[np.ndarray] = None, normals: Optional[np.ndarray] = None, ) -> None: """ Save a point cloud to a PLY file. Parameters ---------- path : str or Path Output file path. Should end with ``.ply``. points : np.ndarray (N, 3) float array of 3D positions. colors : np.ndarray, optional (N, 3) uint8 RGB colours. normals : np.ndarray, optional (N, 3) float array of normals. """ path = Path(path) points = np.asarray(points, dtype=np.float32) has_colors = colors is not None has_normals = normals is not None header_lines = [ "ply", "format ascii 1.0", f"element vertex {len(points)}", "property float x", "property float y", "property float z", ] if has_normals: header_lines += [ "property float nx", "property float ny", "property float nz", ] if has_colors: header_lines += [ "property uchar red", "property uchar green", "property uchar blue", ] header_lines += ["end_header"] with open(path, "w") as f: f.write("\n".join(header_lines) + "\n") for i in range(len(points)): row = [f"{points[i, 0]:.6f}", f"{points[i, 1]:.6f}", f"{points[i, 2]:.6f}"] if has_normals: row += [ f"{normals[i, 0]:.6f}", f"{normals[i, 1]:.6f}", f"{normals[i, 2]:.6f}", ] if has_colors: c = np.clip(colors[i], 0, 255).astype(np.uint8) row += [str(c[0]), str(c[1]), str(c[2])] f.write(" ".join(row) + "\n")