bdck commited on
Commit
30857ce
·
verified ·
1 Parent(s): 8c60408

Upload scripts/reconstruct.py

Browse files
Files changed (1) hide show
  1. scripts/reconstruct.py +112 -0
scripts/reconstruct.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ CLI script: reconstruct a mesh from a point-cloud file using NKSR.
4
+
5
+ Usage
6
+ -----
7
+ python reconstruct.py input.ply output.ply --detail 1.0 --mise-iter 1
8
+ python reconstruct.py input.ply output.ply --chunk-size 50.0 --no-normals
9
+ """
10
+
11
+ import argparse
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ import torch
16
+
17
+ # Allow running from repo root without installing
18
+ sys.path.insert(0, str(Path(__file__).parent.parent))
19
+
20
+ from nksr_wrapper import NKSRMeshReconstructor, load_point_cloud, save_mesh
21
+
22
+
23
+ def main() -> None:
24
+ parser = argparse.ArgumentParser(
25
+ description="NKSR point-cloud → mesh reconstruction"
26
+ )
27
+ parser.add_argument("input", type=Path, help="Input PLY or PCD file")
28
+ parser.add_argument("output", type=Path, help="Output mesh file (PLY/OBJ/GLB)")
29
+ parser.add_argument("--device", default="cuda:0", help="PyTorch device")
30
+ parser.add_argument("--config", default="ks", help="NKSR model config (ks/snet/snet-wonormal)")
31
+ parser.add_argument("--detail", type=float, default=1.0, help="Detail level 0.0-1.0")
32
+ parser.add_argument("--voxel-size", type=float, default=None, help="Override voxel size")
33
+ parser.add_argument("--chunk-size", type=float, default=-1.0, help="Chunk size for large scenes")
34
+ parser.add_argument("--mise-iter", type=int, default=1, help="MISE iterations")
35
+ parser.add_argument("--no-normals", action="store_true", help="Ignore normals in file; estimate them")
36
+ parser.add_argument("--estimate-normals", action="store_true", help="Estimate normals if file lacks them")
37
+ parser.add_argument("--sensor", type=Path, default=None, help="Optional NPY file with sensor positions")
38
+ parser.add_argument("--colors", type=Path, default=None, help="Optional NPY file with per-point RGB colors")
39
+ parser.add_argument("--solver-iter", type=int, default=2000, help="PCG solver max iterations")
40
+ parser.add_argument("--solver-tol", type=float, default=1e-5, help="PCG solver tolerance")
41
+ parser.add_argument("--verbose", action="store_true", help="Print extra progress info")
42
+ args = parser.parse_args()
43
+
44
+ if not args.input.exists():
45
+ parser.error(f"Input file not found: {args.input}")
46
+
47
+ # ---- load point cloud -----------------------------------------------
48
+ print(f"Loading point cloud from {args.input} ...")
49
+ points, normals = load_point_cloud(
50
+ args.input,
51
+ estimate_normals=args.estimate_normals or args.no_normals,
52
+ )
53
+ print(f" Loaded {len(points)} points")
54
+ if normals is not None:
55
+ print(f" Normals present: {normals.shape}")
56
+ elif not args.no_normals:
57
+ print(" No normals found in file — will estimate on-the-fly")
58
+
59
+ if args.no_normals:
60
+ normals = None
61
+ print(" --no-normals set: normals will be estimated")
62
+
63
+ # ---- optional extras ------------------------------------------------
64
+ sensor = None
65
+ if args.sensor:
66
+ import numpy as np
67
+ sensor = np.load(args.sensor)
68
+ print(f" Sensor positions loaded: {sensor.shape}")
69
+
70
+ colors = None
71
+ if args.colors:
72
+ import numpy as np
73
+ colors = np.load(args.colors)
74
+ print(f" Per-point colors loaded: {colors.shape}")
75
+
76
+ # ---- reconstruct ----------------------------------------------------
77
+ print("\nInitialising NKSR reconstructor ...")
78
+ if not torch.cuda.is_available() and args.device.startswith("cuda"):
79
+ print("WARNING: CUDA not available, falling back to CPU (very slow)")
80
+ args.device = "cpu"
81
+
82
+ recon = NKSRMeshReconstructor(
83
+ device=args.device,
84
+ config=args.config,
85
+ chunk_tmp_device="cpu" if args.chunk_size > 0 else None,
86
+ )
87
+
88
+ print("Reconstructing mesh ...")
89
+ mesh = recon.reconstruct(
90
+ points=points,
91
+ normals=normals,
92
+ sensor_positions=sensor,
93
+ colors=colors,
94
+ detail_level=args.detail,
95
+ voxel_size=args.voxel_size,
96
+ chunk_size=args.chunk_size,
97
+ mise_iter=args.mise_iter,
98
+ solver_max_iter=args.solver_iter,
99
+ solver_tol=args.solver_tol,
100
+ )
101
+
102
+ # ---- save -----------------------------------------------------------
103
+ print(f"\nSaving mesh to {args.output} ...")
104
+ save_mesh(args.output, mesh.vertices, mesh.faces, mesh.vertex_colors)
105
+ print(f" Vertices: {len(mesh.vertices):,} | Faces: {len(mesh.faces):,}")
106
+ if mesh.vertex_colors is not None:
107
+ print(" Vertex colors included")
108
+ print("Done.")
109
+
110
+
111
+ if __name__ == "__main__":
112
+ main()