Tohru127 commited on
Commit
66ed209
·
verified ·
1 Parent(s): 6cd3957

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +44 -40
app.py CHANGED
@@ -1,56 +1,60 @@
1
  import os
2
 
3
- def reconstruct(files: List[gr.File], max_img_size: int, threads: int) -> Tuple[str, str, str]:
4
- """Main entry for Gradio. Returns (obj_path, obj_download, ply_download)."""
5
- ts = time.strftime("%Y%m%d_%H%M%S")
6
- run_dir = RUNS_DIR / f"run_{ts}"
7
- img_dir = run_dir / "images"
8
- run_dir.mkdir(parents=True, exist_ok=True)
9
 
10
- paths = [Path(f.name) for f in files]
11
- _save_images_to(img_dir, paths, int(max_img_size))
12
-
13
- fused = _colmap_pipeline(img_dir, run_dir, num_threads=int(threads))
14
- obj = _poisson_mesh_from_ply(fused)
15
-
16
- logs = run_dir / "logs.txt"
17
- if logs.exists():
18
- shutil.copy2(logs, OUT_DIR / f"logs_{ts}.txt")
19
- fused_out = OUT_DIR / f"fused_{ts}.ply"
20
- shutil.copy2(fused, fused_out)
21
-
22
- return str(obj), str(obj), str(fused_out)
23
 
24
 
25
  def ui():
26
- with gr.Blocks(title="Sparse2City3D: Urban massing from few photos") as demo:
27
  gr.Markdown(
28
  """
29
- # Sparse2City3D
30
- Upload 5–30 photos of a street/building. The app runs **COLMAP** to reconstruct a dense point cloud and converts it into a coarse **OBJ** mesh for urban‑planning massing.
 
31
  """
32
  )
33
  with gr.Row():
34
  with gr.Column(scale=2):
35
- files = gr.Files(label="Upload images", file_types=["image"], file_count="multiple")
36
- max_size = gr.Slider(800, 3200, value=2000, step=100, label="Max image size (px)")
37
- threads = gr.Slider(1, 8, value=4, step=1, label="# CPU threads")
38
- run_btn = gr.Button("Reconstruct 3D", variant="primary")
39
- with gr.Column(scale=3):
40
- model3d = gr.Model3D(label="3D preview (OBJ)")
41
- dl_mesh = gr.File(label="Download mesh (OBJ)")
42
- dl_ply = gr.File(label="Download point cloud (PLY)")
43
-
44
- def _wrap(files, max_size, threads):
45
- if not files:
46
- raise gr.Error("Please upload at least 5 images.")
47
- obj, obj_dl, ply_dl = reconstruct(files, int(max_size), int(threads))
48
- return obj, obj_dl, ply_dl
49
-
50
- run_btn.click(_wrap, inputs=[files, max_size, threads], outputs=[model3d, dl_mesh, dl_ply])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  return demo
52
 
53
 
54
  if __name__ == "__main__":
55
- demo = ui()
56
- demo.launch()
 
1
  import os
2
 
3
+ # For preview, prefer GLB (compact)
4
+ return str(glb_path), [str(glb_path), str(obj_path), str(fused)], "\n".join(logs[-6:]), gr.update(visible=True)
 
 
 
 
5
 
6
+ except Exception as e:
7
+ logs.append(f"\n[ERROR]\n{e}")
8
+ return None, None, "\n".join(logs), gr.update(visible=False)
 
 
 
 
 
 
 
 
 
 
9
 
10
 
11
  def ui():
12
+ with gr.Blocks(title="Sparse Multi‑View 3D (Urban Planning)", theme=gr.themes.Soft()) as demo:
13
  gr.Markdown(
14
  """
15
+ # 🏙️ Sparse Multi‑View 3D for Urban Planning
16
+ Upload **3–30 photos** of a scene (streetscape, plaza, façade). We estimate camera poses with **COLMAP**, build a **dense point cloud**, and **mesh** it with Open3D.
17
+ **Tips for sparse captures:** overlap ~60–70%, varying viewpoints (walk an arc), avoid moving cars/people when possible.
18
  """
19
  )
20
  with gr.Row():
21
  with gr.Column(scale=2):
22
+ imgs = gr.UploadButton("Upload images (JPG/PNG)", file_types=["image"], file_count="multiple")
23
+ gallery = gr.Gallery(label="Selected", columns=6, height=160)
24
+ imgs.upload(lambda files: [(f.name, Image.open(f).convert('RGB')) for f in files], imgs, gallery)
25
+
26
+ with gr.Accordion("Reconstruction settings", open=False):
27
+ max_px = gr.Slider(1024, 4096, value=2400, step=64, label="Max image size (px, longest side)")
28
+ match_mode = gr.Radio(["exhaustive", "sequential", "spatial"], value="sequential", label="Matching mode")
29
+ use_gpu_sift = gr.Checkbox(True, label="Use GPU for SIFT (if available)")
30
+ with gr.Accordion("Meshing", open=True):
31
+ voxel = gr.Slider(0.0, 0.05, value=0.01, step=0.005, label="Voxel downsample (m, approx units)")
32
+ depth = gr.Slider(6, 12, value=9, step=1, label="Poisson depth (higher → more detail)")
33
+ tris = gr.Slider(0, 500_000, value=150_000, step=10_000, label="Target triangles (0 = keep)")
34
+
35
+ run = gr.Button("▶ Reconstruct 3D")
36
+ with gr.Column(scale=1):
37
+ preview = gr.Model3D(label="Mesh preview", visible=False)
38
+ files_out = gr.Files(label="Downloads (GLB/OBJ/PLY)")
39
+ logs = gr.Markdown("Logs will appear here after running…")
40
+
41
+ run.click(
42
+ run_pipeline,
43
+ [gallery, max_px, match_mode, use_gpu_sift, voxel, depth, tris],
44
+ [preview, files_out, logs, preview],
45
+ queue=True,
46
+ )
47
+
48
+ gr.Markdown(
49
+ """
50
+ ### Notes & Scaling
51
+ - Results are in **arbitrary units** (SfM scale). For metric scale, align the mesh in GIS/CAD using known distances.
52
+ - Outdoor scenes with repetitive textures (glass, trees) can be challenging—add a few more oblique views if possible.
53
+ """
54
+ )
55
+
56
  return demo
57
 
58
 
59
  if __name__ == "__main__":
60
+ ui().launch()