Spaces:
Running on Zero
Running on Zero
Download lib/util/visualization.py from hugging-apps/spaceflow: direct link, hf CLI and curl.
- Browser
- Download file 4.23 kB
-
https://huggingface.co/spaces/hugging-apps/spaceflow/resolve/main/lib/util/visualization.py
- Command line
-
hf download hf://spaces/hugging-apps/spaceflow/lib/util/visualization.py
-
curl -L -o visualization.py https://huggingface.co/spaces/hugging-apps/spaceflow/resolve/main/lib/util/visualization.py
4.23 kB
| import logging as log | |
| import os.path as osp | |
| import imageio | |
| import matplotlib | |
| matplotlib.use('Agg') | |
| import matplotlib.colors as mcolors | |
| import numpy as np | |
| import torch | |
| from sklearn.neighbors import KDTree | |
| from tqdm import tqdm | |
| from third_party.TRELLIS.trellis.representations.mesh.cube2mesh import MeshExtractResult | |
| from third_party.TRELLIS.trellis.renderers.mesh_renderer import MeshRenderer | |
| from third_party.TRELLIS.trellis.utils.render_utils import yaw_pitch_r_fov_to_extrinsics_intrinsics | |
| def map_voxel_labels_to_vertices(mesh_vertices, voxel_coords, voxel_labels): | |
| """Assign each mesh vertex the label of its nearest voxel via KD-tree lookup. | |
| Args: | |
| mesh_vertices: np.ndarray (V, 3) β mesh vertex positions | |
| voxel_coords: np.ndarray (M, 3) β voxel center positions (same coordinate space) | |
| voxel_labels: np.ndarray (M,) β integer label per voxel | |
| Returns: | |
| np.ndarray (V,) β integer label per vertex | |
| """ | |
| tree = KDTree(voxel_coords) | |
| _, idx = tree.query(mesh_vertices, k=1) | |
| return voxel_labels[idx.flatten()] | |
| def visualize_and_save(structure, partfield_clusters, output_folder, | |
| output_name='cluster_visualization.mp4', | |
| num_frames=300, resolution=512): | |
| """Render a smooth rotating orbit video of the structure colored by cluster labels. | |
| Uses the TRELLIS MeshRenderer (nvdiffrast, ssaa=4) and the same yaw/pitch | |
| camera trajectory as the pipeline's Gaussian scene videos so all sides are visible. | |
| Args: | |
| structure: trimesh.Trimesh β normalized structure mesh | |
| (vertices in [-0.5, 0.5]) | |
| partfield_clusters: np.ndarray (V,) β per-vertex integer cluster labels, | |
| where V matches structure.vertices.shape[0] | |
| output_folder: str β directory in which to save the video | |
| output_name: str β filename for the saved video | |
| (default 'cluster_visualization.mp4') | |
| num_frames: int β number of video frames (default 300) | |
| resolution: int β output resolution in pixels (default 512) | |
| """ | |
| partfield_clusters = np.asarray(partfield_clusters, dtype=int) | |
| num_clusters = int(partfield_clusters.max()) + 1 | |
| # Evenly-spaced HSV hues β maximally distinct colors | |
| hues = np.linspace(0, 1, num_clusters, endpoint=False) | |
| cluster_colors = np.array([mcolors.hsv_to_rgb([h, 0.90, 0.95]) for h in hues], | |
| dtype=np.float32) # (num_clusters, 3) | |
| vertex_colors = cluster_colors[partfield_clusters] # (V, 3) | |
| # Build MeshExtractResult for the TRELLIS MeshRenderer | |
| verts_t = torch.tensor(structure.vertices, dtype=torch.float32, device='cuda') | |
| faces_t = torch.tensor(structure.faces, dtype=torch.long, device='cuda') | |
| colors_t = torch.tensor(vertex_colors, dtype=torch.float32, device='cuda') | |
| mesh_result = MeshExtractResult(vertices=verts_t, faces=faces_t, vertex_attrs=colors_t) | |
| # Camera trajectory: identical to render_utils.render_video | |
| yaws = torch.linspace(0, 2 * torch.pi, num_frames).tolist() | |
| pitch = (0.25 + 0.5 * torch.sin(torch.linspace(0, 2 * torch.pi, num_frames))).tolist() | |
| extrinsics, intrinsics = yaw_pitch_r_fov_to_extrinsics_intrinsics(yaws, pitch, 2, 40) | |
| renderer = MeshRenderer(rendering_options={ | |
| "resolution": resolution, | |
| "near": 1, | |
| "far": 100, | |
| "ssaa": 4, | |
| }) | |
| frames = [] | |
| log.info(f"Rendering {num_frames} frames β {output_name} ...") | |
| with torch.no_grad(): | |
| for extr, intr in tqdm(zip(extrinsics, intrinsics), total=num_frames, | |
| desc=f'Rendering {output_name}'): | |
| res = renderer.render(mesh_result, extr, intr, | |
| return_types=["color", "mask"]) | |
| # composite cluster colors over black background | |
| frame = (res['color'] * res['mask']).permute(1, 2, 0).clamp(0.0, 1.0) | |
| frames.append((frame.cpu().numpy() * 255).astype(np.uint8)) | |
| video_path = osp.join(output_folder, output_name) | |
| imageio.mimsave(video_path, frames, fps=30) | |
| log.info(f"Saved {video_path}") | |