Download code/render_previews.py from acd23/cad-3d-reconstruction: direct link, hf CLI and curl.
- Browser
- Download file 6.31 kB
-
https://huggingface.co/acd23/cad-3d-reconstruction/resolve/main/code/render_previews.py
- Command line
-
hf download hf://acd23/cad-3d-reconstruction/code/render_previews.py
-
curl -L -o render_previews.py https://huggingface.co/acd23/cad-3d-reconstruction/resolve/main/code/render_previews.py
6.31 kB
| """ | |
| Render preview images of point clouds and meshes using matplotlib (no GLU needed). | |
| Creates multi-view renders (front, side, top, iso) for each part. | |
| """ | |
| import os | |
| import sys | |
| import glob | |
| import numpy as np | |
| import trimesh | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| from mpl_toolkits.mplot3d import Axes3D # noqa: F401 | |
| RESULTS_DIR = "data/mast3r_results" | |
| PREVIEW_ANGLES = [ | |
| ("front", 30, -60), | |
| ("side", 30, 30), | |
| ("top", 90, 0), | |
| ("iso", 20, 45), | |
| ] | |
| def render_pointcloud(ax, pts, colors, title=""): | |
| """Render a point cloud on a 3D matplotlib axis.""" | |
| # Subsample if too many points | |
| n = len(pts) | |
| if n > 50000: | |
| idx = np.random.RandomState(42).choice(n, 50000, replace=False) | |
| pts = pts[idx] | |
| colors = colors[idx] | |
| ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], | |
| c=colors / 255.0, s=0.1, marker=".") | |
| ax.set_title(title, fontsize=8) | |
| def render_mesh(ax, verts, faces, title=""): | |
| """Render a mesh on a 3D matplotlib axis using Poly3DCollection.""" | |
| from mpl_toolkits.mplot3d.art3d import Poly3DCollection | |
| # Subsample faces if too many | |
| if len(faces) > 20000: | |
| idx = np.random.RandomState(42).choice(len(faces), 20000, replace=False) | |
| faces = faces[idx] | |
| triangles = verts[faces] | |
| collection = Poly3DCollection(triangles, alpha=0.8, linewidths=0) | |
| collection.set_facecolor([0.7, 0.7, 0.75, 0.9]) | |
| collection.set_edgecolor([0.3, 0.3, 0.35, 0.3]) | |
| ax.add_collection3d(collection) | |
| ax.set_title(title, fontsize=8) | |
| def setup_ax(ax, verts, elev, azim): | |
| """Configure 3D axis bounds and view angle.""" | |
| mins = verts.min(0) | |
| maxs = verts.max(0) | |
| center = (mins + maxs) / 2 | |
| extent = (maxs - mins).max() / 2 * 1.1 | |
| ax.set_xlim(center[0] - extent, center[0] + extent) | |
| ax.set_ylim(center[1] - extent, center[1] + extent) | |
| ax.set_zlim(center[2] - extent, center[2] + extent) | |
| ax.view_init(elev=elev, azim=azim) | |
| ax.set_box_aspect([1, 1, 1]) | |
| ax.set_axis_off() | |
| def render_part_previews(part): | |
| part_dir = os.path.join(RESULTS_DIR, part) | |
| if not os.path.isdir(part_dir): | |
| return | |
| preview_dir = os.path.join(part_dir, "previews") | |
| os.makedirs(preview_dir, exist_ok=True) | |
| print(f"\n=== Rendering previews for {part} ===") | |
| # Load point cloud | |
| pc_path = os.path.join(part_dir, "pointcloud.ply") | |
| pc = None | |
| if os.path.exists(pc_path): | |
| pc = trimesh.load(pc_path) | |
| print(f" Point cloud: {len(pc.vertices)} points") | |
| # Load meshes | |
| meshes = {} | |
| for f in sorted(os.listdir(part_dir)): | |
| if f.endswith("_poisson.ply"): | |
| m = trimesh.load(os.path.join(part_dir, f)) | |
| meshes["poisson"] = m | |
| print(f" Poisson mesh: {len(m.vertices)} verts, {len(m.faces)} faces") | |
| elif f.endswith("_ballpivoting.ply"): | |
| m = trimesh.load(os.path.join(part_dir, f)) | |
| meshes["ballpivoting"] = m | |
| print(f" Ball-pivoting mesh: {len(m.vertices)} verts, {len(m.faces)} faces") | |
| # --- Render point cloud from 4 angles --- | |
| if pc is not None: | |
| fig = plt.figure(figsize=(16, 4), dpi=100) | |
| for i, (name, elev, azim) in enumerate(PREVIEW_ANGLES): | |
| ax = fig.add_subplot(1, 4, i + 1, projection="3d") | |
| colors = pc.colors if hasattr(pc, "colors") and pc.colors is not None else np.tile([128, 128, 128], (len(pc.vertices), 1)) | |
| render_pointcloud(ax, np.array(pc.vertices), np.array(colors), f"PC - {name}") | |
| setup_ax(ax, np.array(pc.vertices), elev, azim) | |
| plt.suptitle(f"{part} - Point Cloud (MASt3R reconstruction)", fontsize=12) | |
| plt.tight_layout() | |
| path = os.path.join(preview_dir, f"{part}_pointcloud_all.png") | |
| plt.savefig(path, bbox_inches="tight", facecolor="white") | |
| plt.close() | |
| print(f" Saved: {path}") | |
| # Individual views | |
| for name, elev, azim in PREVIEW_ANGLES: | |
| fig = plt.figure(figsize=(6, 5), dpi=100) | |
| ax = fig.add_subplot(111, projection="3d") | |
| colors = pc.colors if hasattr(pc, "colors") and pc.colors is not None else np.tile([128, 128, 128], (len(pc.vertices), 1)) | |
| render_pointcloud(ax, np.array(pc.vertices), np.array(colors), f"{name}") | |
| setup_ax(ax, np.array(pc.vertices), elev, azim) | |
| plt.tight_layout() | |
| path = os.path.join(preview_dir, f"{part}_pc_{name}.png") | |
| plt.savefig(path, bbox_inches="tight", facecolor="white") | |
| plt.close() | |
| print(f" Saved: {path}") | |
| # --- Render meshes from 4 angles --- | |
| for mesh_name, mesh in meshes.items(): | |
| verts = np.array(mesh.vertices) | |
| faces = np.array(mesh.faces) | |
| # Combined 4-view | |
| fig = plt.figure(figsize=(16, 4), dpi=100) | |
| for i, (name, elev, azim) in enumerate(PREVIEW_ANGLES): | |
| ax = fig.add_subplot(1, 4, i + 1, projection="3d") | |
| render_mesh(ax, verts, faces, f"{mesh_name} - {name}") | |
| setup_ax(ax, verts, elev, azim) | |
| plt.suptitle(f"{part} - {mesh_name} Mesh", fontsize=12) | |
| plt.tight_layout() | |
| path = os.path.join(preview_dir, f"{part}_{mesh_name}_all.png") | |
| plt.savefig(path, bbox_inches="tight", facecolor="white") | |
| plt.close() | |
| print(f" Saved: {path}") | |
| # Individual views | |
| for name, elev, azim in PREVIEW_ANGLES: | |
| fig = plt.figure(figsize=(6, 5), dpi=100) | |
| ax = fig.add_subplot(111, projection="3d") | |
| render_mesh(ax, verts, faces, f"{mesh_name} - {name}") | |
| setup_ax(ax, verts, elev, azim) | |
| plt.tight_layout() | |
| path = os.path.join(preview_dir, f"{part}_{mesh_name}_{name}.png") | |
| plt.savefig(path, bbox_inches="tight", facecolor="white") | |
| plt.close() | |
| print(f" Saved: {path}") | |
| def main(): | |
| parts = sorted([d for d in os.listdir(RESULTS_DIR) if os.path.isdir(os.path.join(RESULTS_DIR, d))]) | |
| for part in parts: | |
| try: | |
| render_part_previews(part) | |
| except Exception as e: | |
| import traceback | |
| traceback.print_exc() | |
| print(f" FAILED for {part}: {e}") | |
| print("\nDone rendering previews.") | |
| if __name__ == "__main__": | |
| main() | |