gagndeep commited on
Commit
acce386
·
1 Parent(s): 0103eea
Files changed (1) hide show
  1. app.py +90 -121
app.py CHANGED
@@ -1,8 +1,8 @@
1
  """
2
  SHARP Gradio Demo
3
- - Standard Two-Column Layout
4
- - Simplified Camera options
5
- - Robust Error Handling
6
  """
7
 
8
  from __future__ import annotations
@@ -16,20 +16,8 @@ import gradio as gr
16
  # Suppress internal warnings to keep logs clean
17
  warnings.filterwarnings("ignore", category=FutureWarning, module="torch.distributed")
18
 
19
- # Ensure model_utils is present
20
- # We wrap this import to prevent app crash if model_utils is missing during UI dev
21
- try:
22
- from model_utils import TrajectoryType, predict_and_maybe_render_gpu
23
- except ImportError:
24
- # Fallback mocks for testing/building UI without backend
25
- print("WARNING: model_utils not found. Using dummy backend.")
26
- class TrajectoryType:
27
- ROTATE_FORWARD = "rotate_forward"
28
- ROTATE = "rotate"
29
- SWIPE = "swipe"
30
- SHAKE = "shake"
31
- def predict_and_maybe_render_gpu(*args, **kwargs):
32
- return None, Path("dummy_output.ply")
33
 
34
  # -----------------------------------------------------------------------------
35
  # Paths & Config
@@ -42,6 +30,25 @@ EXAMPLES_DIR: Final[Path] = ASSETS_DIR / "examples"
42
 
43
  IMAGE_EXTS: Final[tuple[str, ...]] = (".png", ".jpg", ".jpeg", ".webp")
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  # -----------------------------------------------------------------------------
46
  # Helpers
47
  # -----------------------------------------------------------------------------
@@ -54,7 +61,7 @@ def get_example_files() -> list[list[str]]:
54
  """Discover images in assets/examples for the UI."""
55
  _ensure_dir(EXAMPLES_DIR)
56
 
57
- # Priority 1: Check manifest.json
58
  manifest_path = EXAMPLES_DIR / "manifest.json"
59
  if manifest_path.exists():
60
  try:
@@ -70,7 +77,7 @@ def get_example_files() -> list[list[str]]:
70
  except Exception as e:
71
  print(f"Manifest error: {e}")
72
 
73
- # Priority 2: Simple file scan
74
  examples = []
75
  for ext in IMAGE_EXTS:
76
  for img in sorted(EXAMPLES_DIR.glob(f"*{ext}")):
@@ -79,10 +86,10 @@ def get_example_files() -> list[list[str]]:
79
 
80
  def run_sharp(
81
  image_path: str | None,
82
- trajectory_type_raw: str,
83
- output_long_side: int | float | None,
84
- num_frames: int | float,
85
- fps: int | float,
86
  render_video: bool,
87
  progress=gr.Progress()
88
  ) -> tuple[str | None, str | None, str]:
@@ -92,41 +99,20 @@ def run_sharp(
92
  if not image_path:
93
  raise gr.Error("Please upload an image first.")
94
 
95
- # 1. Safe Integer Conversion (Handle None or Float inputs from sliders/dropdowns)
96
- try:
97
- # If dropdown returns 0 (Match Input), treat as None for backend
98
- out_long_side_val = int(output_long_side) if output_long_side and int(output_long_side) > 0 else None
99
-
100
- # Sliders usually return float, convert to int safely
101
- n_frames = int(num_frames) if num_frames is not None else 60
102
- fps_val = int(fps) if fps is not None else 30
103
- except Exception as e:
104
- print(f"Input validation warning: {e}. Using defaults.")
105
- out_long_side_val = None
106
- n_frames = 60
107
- fps_val = 30
108
-
109
- # 2. Resolve Trajectory Enum safely
110
- # Ensure input is a clean string
111
- traj_str = str(trajectory_type_raw).strip()
112
- traj_enum = traj_str # Default fallback is raw string
113
-
114
- # Try looking up by Uppercase key (Standard Python Enum convention: TrajectoryType.ROTATE)
115
- if hasattr(TrajectoryType, traj_str.upper()):
116
- traj_enum = getattr(TrajectoryType, traj_str.upper())
117
- # Fallback: Try looking up by exact string match if the class uses different naming
118
- elif hasattr(TrajectoryType, traj_str):
119
- traj_enum = getattr(TrajectoryType, traj_str)
120
 
121
- # 3. Run Inference
 
 
122
  try:
123
- progress(0.1, desc="Initializing model...")
124
 
125
  video_path, ply_path = predict_and_maybe_render_gpu(
126
  image_path,
127
  trajectory_type=traj_enum,
128
- num_frames=n_frames,
129
- fps=fps_val,
130
  output_long_side=out_long_side_val,
131
  render_video=bool(render_video),
132
  )
@@ -134,8 +120,6 @@ def run_sharp(
134
  status_msg = f"✅ **Success**\n\nPLY: `{ply_path.name}`"
135
  if video_path:
136
  status_msg += f"\nVideo: `{video_path.name}`"
137
- else:
138
- status_msg += "\n(Video rendering skipped)"
139
 
140
  return (
141
  str(video_path) if video_path else None,
@@ -144,8 +128,7 @@ def run_sharp(
144
  )
145
 
146
  except Exception as e:
147
- # Catch backend errors elegantly so UI doesn't crash
148
- raise gr.Error(f"Generation failed: {str(e)}")
149
 
150
  # -----------------------------------------------------------------------------
151
  # UI Construction
@@ -153,102 +136,86 @@ def run_sharp(
153
 
154
  def build_demo() -> gr.Blocks:
155
  theme = gr.themes.Default()
156
-
157
- # Minimal CSS for standard centering
158
- css = """
159
- .container { max-width: 1200px; margin: auto; }
160
- #header { text-align: center; margin-bottom: 2rem; }
161
- """
162
 
163
- with gr.Blocks(theme=theme, css=css, title="SHARP 3D") as demo:
164
 
165
  # --- Header ---
166
- with gr.Column(elem_id="header"):
167
- gr.Markdown("# SHARP: Single-Image 3D Generator")
168
- gr.Markdown("Convert any static image into a 3D Gaussian Splat scene instantly.")
169
 
170
- # --- Main Layout (2 Columns) ---
171
- with gr.Row(equal_height=False):
172
 
173
- # LEFT: Input
174
- with gr.Column():
175
  image_in = gr.Image(
176
  label="Input Image",
177
  type="filepath",
178
  sources=["upload", "clipboard"],
179
- height=350
 
180
  )
181
 
 
182
  with gr.Group():
183
- gr.Markdown("### 🎥 Configuration")
184
- # Simplified Trajectory Dropdown
185
- trajectory = gr.Dropdown(
186
- label="Camera Trajectory",
187
- choices=["rotate_forward", "rotate", "swipe", "shake"],
188
- value="rotate_forward",
189
- interactive=True
190
- )
 
 
 
 
 
 
 
 
191
 
192
- output_res = gr.Dropdown(
193
- label="Output Resolution",
194
- choices=[("Match Input", 0), ("512px", 512), ("1024px", 1024)],
195
- value=0,
196
- interactive=True
197
- )
198
-
199
- with gr.Accordion("Advanced Options", open=False):
200
- frames = gr.Slider(label="Duration (Frames)", minimum=24, maximum=120, step=1, value=60)
201
- fps_in = gr.Slider(label="Frame Rate (FPS)", minimum=8, maximum=60, step=1, value=30)
202
  render_toggle = gr.Checkbox(label="Render Video Preview", value=True)
203
 
204
  run_btn = gr.Button("🚀 Generate 3D Scene", variant="primary", size="lg")
205
 
206
- # RIGHT: Output
207
- with gr.Column():
 
 
 
 
 
 
 
 
 
 
 
 
208
  video_out = gr.Video(
209
  label="3D Preview",
210
  autoplay=True,
211
- height=350
 
212
  )
213
 
214
  with gr.Group():
 
215
  ply_download = gr.DownloadButton(
216
- label="Download .PLY File",
217
  variant="secondary",
218
  visible=True
219
  )
220
- status_md = gr.Markdown("Waiting for input...")
221
-
222
- # --- Footer: Examples ---
223
- # NOTE: cache_examples=False and run_on_click=False are required
224
- # to prevent crashing when clicking examples due to missing slider inputs.
225
- example_files = get_example_files()
226
- if example_files:
227
- gr.Examples(
228
- examples=example_files,
229
- inputs=[image_in],
230
- outputs=None,
231
- fn=None,
232
- cache_examples=False,
233
- run_on_click=False,
234
- label="Click an example to load it:"
235
- )
236
 
237
- # --- Event Binding ---
238
  run_btn.click(
239
  fn=run_sharp,
240
- inputs=[
241
- image_in,
242
- trajectory,
243
- output_res,
244
- frames,
245
- fps_in,
246
- render_toggle
247
- ],
248
  outputs=[video_out, ply_download, status_md],
249
  concurrency_limit=1
250
  )
251
-
252
  return demo
253
 
254
  # -----------------------------------------------------------------------------
@@ -259,5 +226,7 @@ _ensure_dir(OUTPUTS_DIR)
259
 
260
  if __name__ == "__main__":
261
  demo = build_demo()
262
- # SSR Mode False helps with stability in some Spaces environments
263
- demo.queue().launch(allowed_paths=[str(ASSETS_DIR)], ssr_mode=False)
 
 
 
1
  """
2
  SHARP Gradio Demo
3
+ - Standard Split-View Layout (Left Input / Right Output)
4
+ - SEO Optimized
5
+ - Configs Visible (No Accordion)
6
  """
7
 
8
  from __future__ import annotations
 
16
  # Suppress internal warnings to keep logs clean
17
  warnings.filterwarnings("ignore", category=FutureWarning, module="torch.distributed")
18
 
19
+ # Ensure model_utils is present in your directory
20
+ from model_utils import TrajectoryType, predict_and_maybe_render_gpu
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  # -----------------------------------------------------------------------------
23
  # Paths & Config
 
30
 
31
  IMAGE_EXTS: Final[tuple[str, ...]] = (".png", ".jpg", ".jpeg", ".webp")
32
 
33
+ # -----------------------------------------------------------------------------
34
+ # SEO & Styling
35
+ # -----------------------------------------------------------------------------
36
+
37
+ SEO_HEAD = """
38
+ <meta name="description" content="Turn 2D images into 3D Gaussian Splats instantly. SHARP (Apple) AI Demo. Free, fast, single-image 3D reconstruction.">
39
+ <meta name="keywords" content="SHARP, 3D Gaussian Splatting, AI 3D model, Image to 3D, Apple Research, Gradio, Machine Learning">
40
+ <meta property="og:title" content="SHARP: Instant Image-to-3D Model">
41
+ <meta property="og:description" content="Generate 3D camera trajectories and PLY files from a single image in seconds using the SHARP model.">
42
+ <meta name="viewport" content="width=device-width, initial-scale=1">
43
+ """
44
+
45
+ # Minimal CSS to just center headers and ensure spacing
46
+ CSS = """
47
+ h1 { text-align: center; margin-bottom: 0.5rem; }
48
+ .sub-desc { text-align: center; margin-bottom: 2rem; color: #666; font-size: 1.1rem; }
49
+ .gradio-container { max-width: 1400px !important; margin: 0 auto; }
50
+ """
51
+
52
  # -----------------------------------------------------------------------------
53
  # Helpers
54
  # -----------------------------------------------------------------------------
 
61
  """Discover images in assets/examples for the UI."""
62
  _ensure_dir(EXAMPLES_DIR)
63
 
64
+ # Check manifest.json first
65
  manifest_path = EXAMPLES_DIR / "manifest.json"
66
  if manifest_path.exists():
67
  try:
 
77
  except Exception as e:
78
  print(f"Manifest error: {e}")
79
 
80
+ # Fallback: simple file scan
81
  examples = []
82
  for ext in IMAGE_EXTS:
83
  for img in sorted(EXAMPLES_DIR.glob(f"*{ext}")):
 
86
 
87
  def run_sharp(
88
  image_path: str | None,
89
+ trajectory_type: str,
90
+ output_long_side: int,
91
+ num_frames: int,
92
+ fps: int,
93
  render_video: bool,
94
  progress=gr.Progress()
95
  ) -> tuple[str | None, str | None, str]:
 
99
  if not image_path:
100
  raise gr.Error("Please upload an image first.")
101
 
102
+ # Validate inputs
103
+ out_long_side_val = None if int(output_long_side) <= 0 else int(output_long_side)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
+ # Convert trajectory string to Enum or pass as is
106
+ traj_enum = TrajectoryType[trajectory_type.upper()] if hasattr(TrajectoryType, trajectory_type.upper()) else trajectory_type
107
+
108
  try:
109
+ progress(0.1, desc="Initializing SHARP model...")
110
 
111
  video_path, ply_path = predict_and_maybe_render_gpu(
112
  image_path,
113
  trajectory_type=traj_enum,
114
+ num_frames=int(num_frames),
115
+ fps=int(fps),
116
  output_long_side=out_long_side_val,
117
  render_video=bool(render_video),
118
  )
 
120
  status_msg = f"✅ **Success**\n\nPLY: `{ply_path.name}`"
121
  if video_path:
122
  status_msg += f"\nVideo: `{video_path.name}`"
 
 
123
 
124
  return (
125
  str(video_path) if video_path else None,
 
128
  )
129
 
130
  except Exception as e:
131
+ raise gr.Error(f"Error: {str(e)}")
 
132
 
133
  # -----------------------------------------------------------------------------
134
  # UI Construction
 
136
 
137
  def build_demo() -> gr.Blocks:
138
  theme = gr.themes.Default()
 
 
 
 
 
 
139
 
140
+ with gr.Blocks(theme=theme, css=CSS, head=SEO_HEAD, title="SHARP 3D Model Generator") as demo:
141
 
142
  # --- Header ---
143
+ gr.Markdown("# SHARP: Single-Image 3D Generator")
144
+ gr.Markdown("Convert any static image into a 3D Gaussian Splat scene instantly.", elem_classes=["sub-desc"])
 
145
 
146
+ # --- Main Layout (Strict Two Columns) ---
147
+ with gr.Row(equal_height=False, variant="panel"):
148
 
149
+ # --- LEFT COLUMN: Input & Controls ---
150
+ with gr.Column(scale=1):
151
  image_in = gr.Image(
152
  label="Input Image",
153
  type="filepath",
154
  sources=["upload", "clipboard"],
155
+ height=320, # Fixed height to prevent layout shifts
156
+ interactive=True
157
  )
158
 
159
+ # Settings grouped cleanly below image (No Accordion)
160
  with gr.Group():
161
+ with gr.Row():
162
+ trajectory = gr.Dropdown(
163
+ label="Camera Movement",
164
+ choices=["swipe", "shake", "rotate", "rotate_forward"],
165
+ value="rotate_forward",
166
+ scale=2
167
+ )
168
+ output_res = gr.Dropdown(
169
+ label="Output Resolution",
170
+ choices=[("Original", 0), ("512px", 512), ("1024px", 1024)],
171
+ value=0,
172
+ scale=1
173
+ )
174
+ with gr.Row():
175
+ frames = gr.Slider(label="Frames", minimum=24, maximum=120, step=1, value=60)
176
+ fps_in = gr.Slider(label="FPS", minimum=8, maximum=60, step=1, value=30)
177
 
 
 
 
 
 
 
 
 
 
 
178
  render_toggle = gr.Checkbox(label="Render Video Preview", value=True)
179
 
180
  run_btn = gr.Button("🚀 Generate 3D Scene", variant="primary", size="lg")
181
 
182
+ # Examples at the bottom of left column
183
+ example_files = get_example_files()
184
+ if example_files:
185
+ gr.Examples(
186
+ examples=example_files,
187
+ inputs=[image_in],
188
+ label="Examples (Click to Load)",
189
+ # Setting run_on_click=False allows user to change settings before running
190
+ run_on_click=False,
191
+ cache_examples=False
192
+ )
193
+
194
+ # --- RIGHT COLUMN: Output ---
195
+ with gr.Column(scale=1):
196
  video_out = gr.Video(
197
  label="3D Preview",
198
  autoplay=True,
199
+ height=320, # Matches input height
200
+ elem_id="output-video"
201
  )
202
 
203
  with gr.Group():
204
+ status_md = gr.Markdown("Ready to generate.")
205
  ply_download = gr.DownloadButton(
206
+ label="Download .PLY File (For Splat Viewers)",
207
  variant="secondary",
208
  visible=True
209
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
+ # --- Logic Binding ---
212
  run_btn.click(
213
  fn=run_sharp,
214
+ inputs=[image_in, trajectory, output_res, frames, fps_in, render_toggle],
 
 
 
 
 
 
 
215
  outputs=[video_out, ply_download, status_md],
216
  concurrency_limit=1
217
  )
218
+
219
  return demo
220
 
221
  # -----------------------------------------------------------------------------
 
226
 
227
  if __name__ == "__main__":
228
  demo = build_demo()
229
+ demo.queue().launch(
230
+ allowed_paths=[str(ASSETS_DIR)],
231
+ ssr_mode=False
232
+ )