gagndeep commited on
Commit
0103eea
·
1 Parent(s): c4d348a
Files changed (1) hide show
  1. app.py +94 -133
app.py CHANGED
@@ -1,8 +1,8 @@
1
  """
2
  SHARP Gradio Demo
3
  - Standard Two-Column Layout
4
- - Controls Visible
5
- - Examples at Bottom
6
  """
7
 
8
  from __future__ import annotations
@@ -13,11 +13,23 @@ from pathlib import Path
13
  from typing import Final
14
  import gradio as gr
15
 
16
- # Suppress internal warnings
17
  warnings.filterwarnings("ignore", category=FutureWarning, module="torch.distributed")
18
 
19
  # Ensure model_utils is present
20
- from model_utils import TrajectoryType, predict_and_maybe_render_gpu
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  # -----------------------------------------------------------------------------
23
  # Paths & Config
@@ -42,7 +54,7 @@ def get_example_files() -> list[list[str]]:
42
  """Discover images in assets/examples for the UI."""
43
  _ensure_dir(EXAMPLES_DIR)
44
 
45
- # Check manifest.json first
46
  manifest_path = EXAMPLES_DIR / "manifest.json"
47
  if manifest_path.exists():
48
  try:
@@ -58,7 +70,7 @@ def get_example_files() -> list[list[str]]:
58
  except Exception as e:
59
  print(f"Manifest error: {e}")
60
 
61
- # Fallback: simple file scan
62
  examples = []
63
  for ext in IMAGE_EXTS:
64
  for img in sorted(EXAMPLES_DIR.glob(f"*{ext}")):
@@ -67,14 +79,10 @@ def get_example_files() -> list[list[str]]:
67
 
68
  def run_sharp(
69
  image_path: str | None,
70
- trajectory_mode: str, # "Preset" or "Custom"
71
- trajectory_preset: str, # e.g., "rotate"
72
- custom_elevation: float,
73
- custom_azimuth: float,
74
- custom_radius: float,
75
- output_long_side: int,
76
- num_frames: int,
77
- fps: int,
78
  render_video: bool,
79
  progress=gr.Progress()
80
  ) -> tuple[str | None, str | None, str]:
@@ -84,51 +92,41 @@ def run_sharp(
84
  if not image_path:
85
  raise gr.Error("Please upload an image first.")
86
 
87
- # Validate inputs
88
- out_long_side_val = None if int(output_long_side) <= 0 else int(output_long_side)
89
-
90
- # Handle Trajectory Logic
91
- # If mode is custom, you would ideally construct a custom camera path here.
92
- # For now, we fallback to the preset if custom logic isn't fully implemented in model_utils.
93
- final_traj_str = trajectory_preset
94
-
95
- if trajectory_mode == "Custom":
96
- # Placeholder: If your model_utils supports custom params, pass them here.
97
- # For this demo, we'll log it and default to a standard rotation.
98
- print(f"Custom Trajectory Requested: Elev={custom_elevation}, Azim={custom_azimuth}, Rad={custom_radius}")
99
- final_traj_str = "rotate" # Fallback/Default
100
 
101
- # Convert string to Enum safely
102
- # Mapping new UI names to model standard names if necessary
103
- traj_map = {
104
- "Orbit (Standard)": "rotate",
105
- "Orbit (Forward)": "rotate_forward",
106
- "Swipe Left": "swipe",
107
- "Shake": "shake",
108
- "Zoom In": "zoom", # Assuming model supports 'zoom', else map to closest
109
- "Dolly": "dolly"
110
- }
111
-
112
- # Get the internal key, or use the raw string if not in map
113
- internal_traj_name = traj_map.get(final_traj_str, final_traj_str)
114
-
115
- # Resolve Enum
116
- if hasattr(TrajectoryType, internal_traj_name.upper()):
117
- traj_enum = TrajectoryType[internal_traj_name.upper()]
118
- else:
119
- # Fallback for mapped names that might match Enum directly (e.g. 'rotate')
120
- traj_enum = TrajectoryType.ROTATE # Default safe fallback
121
- if hasattr(TrajectoryType, internal_traj_name):
122
- traj_enum = TrajectoryType[internal_traj_name]
123
 
 
 
 
 
 
 
 
 
124
  try:
125
- progress(0.1, desc="Initializing SHARP model...")
126
 
127
  video_path, ply_path = predict_and_maybe_render_gpu(
128
  image_path,
129
  trajectory_type=traj_enum,
130
- num_frames=int(num_frames),
131
- fps=int(fps),
132
  output_long_side=out_long_side_val,
133
  render_video=bool(render_video),
134
  )
@@ -136,6 +134,8 @@ def run_sharp(
136
  status_msg = f"✅ **Success**\n\nPLY: `{ply_path.name}`"
137
  if video_path:
138
  status_msg += f"\nVideo: `{video_path.name}`"
 
 
139
 
140
  return (
141
  str(video_path) if video_path else None,
@@ -144,7 +144,8 @@ def run_sharp(
144
  )
145
 
146
  except Exception as e:
147
- raise gr.Error(f"Error: {str(e)}")
 
148
 
149
  # -----------------------------------------------------------------------------
150
  # UI Construction
@@ -153,25 +154,24 @@ def run_sharp(
153
  def build_demo() -> gr.Blocks:
154
  theme = gr.themes.Default()
155
 
156
- # Custom CSS only for subtle spacing, relying on Standard Gradio for layout
157
  css = """
158
  .container { max-width: 1200px; margin: auto; }
159
- h1 { text-align: center; margin-bottom: 5px; }
160
- .description { text-align: center; color: #666; margin-bottom: 20px; }
161
  """
162
 
163
  with gr.Blocks(theme=theme, css=css, title="SHARP 3D") as demo:
164
 
165
  # --- Header ---
166
- with gr.Column(elem_classes=["container"]):
167
  gr.Markdown("# SHARP: Single-Image 3D Generator")
168
- gr.Markdown("Convert any static image into a 3D Gaussian Splat scene instantly.", elem_classes=["description"])
169
 
170
- # --- Main Two-Column Layout ---
171
  with gr.Row(equal_height=False):
172
 
173
- # --- LEFT COLUMN: Input & Controls ---
174
- with gr.Column(variant="panel"):
175
  image_in = gr.Image(
176
  label="Input Image",
177
  type="filepath",
@@ -179,55 +179,32 @@ def build_demo() -> gr.Blocks:
179
  height=350
180
  )
181
 
182
- # VISIBLE Primary Configuration (Not hidden in accordion)
183
- gr.Markdown("### 🎥 Camera & Quality")
184
  with gr.Group():
185
- with gr.Tabs():
186
- with gr.Tab("Presets"):
187
- trajectory_mode_preset = gr.State("Preset") # Hidden state tracker
188
- trajectory_preset = gr.Dropdown(
189
- label="Movement Style",
190
- choices=[
191
- "Orbit (Standard)",
192
- "Orbit (Forward)",
193
- "Swipe Left",
194
- "Shake",
195
- "Zoom In",
196
- "Dolly"
197
- ],
198
- value="Orbit (Forward)",
199
- interactive=True
200
- )
201
-
202
- with gr.Tab("Custom Path"):
203
- trajectory_mode_custom = gr.State("Custom")
204
- with gr.Row():
205
- cust_elev = gr.Slider(-90, 90, value=0, label="Elevation", interactive=True)
206
- cust_azim = gr.Slider(-180, 180, value=30, label="Azimuth", interactive=True)
207
- cust_rad = gr.Slider(0.5, 5.0, value=1.5, label="Radius/Distance", interactive=True)
208
-
209
- # Logic to toggle mode based on tab switch
210
- # (Simplified: We'll pass a 'mode' flag from the button click)
211
-
212
  output_res = gr.Dropdown(
213
  label="Output Resolution",
214
- choices=[("Original", 0), ("512px", 512), ("1024px", 1024)],
215
  value=0,
216
  interactive=True
217
  )
218
 
219
- # Advanced Configuration (Hidden)
220
- with gr.Accordion("⚙️ Advanced Settings", open=False):
221
- with gr.Row():
222
- frames = gr.Slider(label="Duration (Frames)", minimum=24, maximum=120, step=1, value=60)
223
- fps_in = gr.Slider(label="Frame Rate (FPS)", minimum=8, maximum=60, step=1, value=30)
224
  render_toggle = gr.Checkbox(label="Render Video Preview", value=True)
225
 
226
- # Main Action Button
227
  run_btn = gr.Button("🚀 Generate 3D Scene", variant="primary", size="lg")
228
 
229
- # --- RIGHT COLUMN: Output ---
230
- with gr.Column(variant="panel"):
231
  video_out = gr.Video(
232
  label="3D Preview",
233
  autoplay=True,
@@ -236,51 +213,37 @@ def build_demo() -> gr.Blocks:
236
 
237
  with gr.Group():
238
  ply_download = gr.DownloadButton(
239
- label="Download .PLY File (For Splat Viewers)",
240
  variant="secondary",
241
  visible=True
242
  )
243
- status_md = gr.Markdown("Waiting for input...", elem_id="status")
244
 
245
- # --- Footer: Examples (Full Width) ---
246
- gr.Markdown("### 📝 Examples")
 
247
  example_files = get_example_files()
248
-
249
- # We need a hidden component to capture the mode (Preset vs Custom)
250
- # For simplicity in this demo, we default to 'Preset' mode for the button click
251
- mode_state = gr.State("Preset")
252
-
253
  if example_files:
254
  gr.Examples(
255
  examples=example_files,
256
  inputs=[image_in],
257
- # We do NOT run immediately here to allow user to tweak settings if they want
258
- # Or set run_on_click=True if you prefer instant results
259
- run_on_click=True,
260
- fn=run_sharp,
261
- outputs=[video_out, ply_download, status_md],
262
- # We need to pass dummy defaults for the extra params since gr.Examples
263
- # usually maps strictly to inputs.
264
- # To fix the "Not enough arguments" error in Examples, we usually wrap the fn.
265
- # But here, we just rely on the button for complex custom logic.
266
- # For basic examples, we bind the simple run.
267
  )
268
-
269
  # --- Event Binding ---
270
-
271
- # Helper to determine mode based on which inputs are active?
272
- # Gradio doesn't easily tell us which Tab is active in the backend function.
273
- # We will assume "Preset" unless the user explicitly changes values in Custom.
274
- # For this robust UI, we will just pass "Preset" mode by default.
275
-
276
  run_btn.click(
277
  fn=run_sharp,
278
  inputs=[
279
  image_in,
280
- mode_state, # Always "Preset" for now, unless we add complex JS to track tabs
281
- trajectory_preset,
282
- cust_elev, cust_azim, cust_rad,
283
- output_res, frames, fps_in, render_toggle
 
284
  ],
285
  outputs=[video_out, ply_download, status_md],
286
  concurrency_limit=1
@@ -296,7 +259,5 @@ _ensure_dir(OUTPUTS_DIR)
296
 
297
  if __name__ == "__main__":
298
  demo = build_demo()
299
- demo.queue().launch(
300
- allowed_paths=[str(ASSETS_DIR)],
301
- ssr_mode=False
302
- )
 
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
 
13
  from typing import Final
14
  import gradio as gr
15
 
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
 
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
  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
 
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
  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
  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
  )
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
 
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",
 
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,
 
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
 
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)