HongzeFu commited on
Commit
3dff563
·
1 Parent(s): f4b4e6c
Files changed (1) hide show
  1. app.py +385 -73
app.py CHANGED
@@ -1,96 +1,408 @@
1
- """Hugging Face Spaces entrypoint for RoboMME Gradio app."""
2
 
3
  import os
4
- import sys
5
- import tempfile
6
- from pathlib import Path
7
-
8
- APP_DIR = Path(__file__).resolve().parent
9
- GRADIO_WEB_DIR = APP_DIR / "gradio-web"
10
- SRC_DIR = APP_DIR / "src"
11
- VIDEOS_DIR = GRADIO_WEB_DIR / "videos"
12
- TEMP_DEMOS_DIR = APP_DIR / "temp_demos"
13
- CWD_TEMP_DEMOS_DIR = Path.cwd() / "temp_demos"
14
-
15
- # Ensure local modules are importable when running from repository root (HF Spaces).
16
- for import_path in (GRADIO_WEB_DIR, SRC_DIR, APP_DIR):
17
- resolved = str(import_path.resolve())
18
- if resolved not in sys.path:
19
- sys.path.insert(0, resolved)
20
-
21
- from state_manager import start_timeout_monitor
22
- from ui_layout import create_ui_blocks
23
-
24
-
25
- def ensure_media_dirs() -> None:
26
- """Create temp media directories before first write."""
27
- TEMP_DEMOS_DIR.mkdir(parents=True, exist_ok=True)
28
- CWD_TEMP_DEMOS_DIR.mkdir(parents=True, exist_ok=True)
29
-
30
-
31
- def build_allowed_paths() -> list[str]:
32
- """Build Gradio file access allowlist (absolute, deduplicated)."""
33
- candidates = [
34
- Path.cwd(),
35
- APP_DIR,
36
- GRADIO_WEB_DIR,
37
- SRC_DIR,
38
- VIDEOS_DIR,
39
- TEMP_DEMOS_DIR,
40
- CWD_TEMP_DEMOS_DIR,
41
- Path(tempfile.gettempdir()),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  ]
43
 
44
- deduped = []
45
- seen = set()
46
- for path in candidates:
47
- normalized = str(path.resolve())
48
- if normalized not in seen:
49
- seen.add(normalized)
50
- deduped.append(normalized)
51
- return deduped
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- _APP_BOOTSTRAPPED = False
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- def bootstrap_runtime() -> None:
58
- """Initialize runtime side effects once per process."""
59
- global _APP_BOOTSTRAPPED
60
- if _APP_BOOTSTRAPPED:
61
- return
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
- ensure_media_dirs()
64
- start_timeout_monitor()
65
- os.environ.setdefault("ROBOMME_TEMP_DEMOS_DIR", str(TEMP_DEMOS_DIR))
66
- _APP_BOOTSTRAPPED = True
67
 
 
 
 
 
 
68
 
69
- # Force SSR off before Gradio reads the setting (env var is the only
70
- # reliable way — attribute assignment on Blocks is ignored by launch()).
71
- os.environ["GRADIO_SSR_MODE"] = "false"
 
 
72
 
73
- bootstrap_runtime()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- demo = create_ui_blocks()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
- _allowed_paths = build_allowed_paths()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- # Wrap launch() so that HF Spaces (which calls demo.launch() directly)
80
- # always gets our required kwargs.
81
- _original_launch = demo.launch
82
 
83
- def _patched_launch(**kwargs):
84
- kwargs.setdefault("ssr_mode", False)
85
- kwargs.setdefault("allowed_paths", _allowed_paths)
86
- kwargs.setdefault("show_error", True)
87
- return _original_launch(**kwargs)
88
 
89
- demo.launch = _patched_launch
90
 
91
 
92
  if __name__ == "__main__":
93
- print("launching demo")
94
  demo.launch(
95
  server_name="0.0.0.0",
96
  server_port=int(os.getenv("PORT", "7860")),
 
1
+ """Dummy Gradio entrypoint with layout similar to original RoboMME UI."""
2
 
3
  import os
4
+
5
+ import gradio as gr
6
+ import numpy as np
7
+
8
+
9
+ PHASE_DEMO_VIDEO = "demo_video"
10
+ PHASE_ACTION_KEYPOINT = "action_keypoint"
11
+
12
+ DUMMY_TASKS = [
13
+ "Peg Insertion Side",
14
+ "Pick Cube and Place",
15
+ "Stack Green Block",
16
+ "Open Cabinet Door",
17
+ ]
18
+
19
+ CSS = """
20
+ #loading_overlay_group {
21
+ position: fixed !important;
22
+ inset: 0 !important;
23
+ z-index: 9999 !important;
24
+ background: rgba(255, 255, 255, 0.92) !important;
25
+ text-align: center !important;
26
+ }
27
+
28
+ #loading_overlay_group > div {
29
+ min-height: 100%;
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ }
34
+ """
35
+
36
+
37
+ def _task_goal(task_name: str) -> str:
38
+ return f"Dummy goal for {task_name}"
39
+
40
+
41
+ def _task_hint(task_name: str) -> str:
42
+ return (
43
+ f"[DUMMY] Current task: {task_name}\n"
44
+ "1) Select an action option.\n"
45
+ "2) Click image when action needs coordinates.\n"
46
+ "3) Press EXECUTE to simulate one step."
47
+ )
48
+
49
+
50
+ def _task_actions(task_name: str) -> list[str]:
51
+ return [
52
+ f"{task_name}: Move",
53
+ f"{task_name}: Grasp",
54
+ f"{task_name}: Place",
55
+ "Reset Arm Pose",
56
  ]
57
 
 
 
 
 
 
 
 
 
58
 
59
+ def _dummy_frame(task_name: str, step: int) -> np.ndarray:
60
+ seed = sum(ord(ch) for ch in task_name) + step * 31
61
+ r = 80 + (seed % 120)
62
+ g = 80 + ((seed * 3) % 120)
63
+ b = 80 + ((seed * 7) % 120)
64
+ frame = np.zeros((360, 640, 3), dtype=np.uint8)
65
+ frame[:, :] = [r, g, b]
66
+ frame[20:340, 20:620] = [min(r + 25, 255), min(g + 25, 255), min(b + 25, 255)]
67
+ frame[170:190, 40:600] = [245, 245, 245]
68
+ return frame
69
+
70
+
71
+ def _build_task_updates(task_name: str, step: int, phase: str) -> tuple:
72
+ in_video_phase = phase == PHASE_DEMO_VIDEO
73
+ log_text = (
74
+ f"[DUMMY] Loaded task: {task_name}\n"
75
+ + ("[DUMMY] Demo video phase. Click 'Skip Demo Video' to continue." if in_video_phase else "[DUMMY] Action phase ready.")
76
+ )
77
+ return (
78
+ _task_goal(task_name),
79
+ gr.update(choices=_task_actions(task_name), value=None),
80
+ "",
81
+ log_text,
82
+ _task_hint(task_name),
83
+ f"Episode progress: {step}/5 (dummy)",
84
+ _dummy_frame(task_name, step),
85
+ gr.update(visible=in_video_phase),
86
+ gr.update(visible=not in_video_phase),
87
+ gr.update(visible=not in_video_phase),
88
+ phase,
89
+ )
90
+
91
+
92
+ def create_dummy_demo() -> gr.Blocks:
93
+ """Build a dummy app that mimics original layout without ManiSkill."""
94
+
95
+ def on_task_change(task_name: str):
96
+ task = task_name if task_name in DUMMY_TASKS else DUMMY_TASKS[0]
97
+ step = 0
98
+ return (step,) + _build_task_updates(task, step, PHASE_DEMO_VIDEO)
99
+
100
+ def skip_video(task_name: str, step: int):
101
+ task = task_name if task_name in DUMMY_TASKS else DUMMY_TASKS[0]
102
+ return _build_task_updates(task, step, PHASE_ACTION_KEYPOINT)
103
+
104
+ def on_reference_action(task_name: str):
105
+ actions = _task_actions(task_name)
106
+ return (
107
+ gr.update(value=actions[0]),
108
+ "[DUMMY] Filled with reference action.",
109
+ )
110
+
111
+ def on_map_click(evt: gr.SelectData):
112
+ if evt is None or evt.index is None:
113
+ return ""
114
+ x, y = evt.index
115
+ return f"({x}, {y})"
116
+
117
+ def execute_step(task_name: str, action_name: str, coords_text: str, step: int):
118
+ task = task_name if task_name in DUMMY_TASKS else DUMMY_TASKS[0]
119
+ next_step = min(step + 1, 5)
120
+ action = action_name or "No action selected"
121
+ coords = coords_text.strip() if coords_text else "N/A"
122
+ log = (
123
+ f"[DUMMY] Execute step {next_step}/5\n"
124
+ f"- task: {task}\n"
125
+ f"- action: {action}\n"
126
+ f"- coords: {coords}"
127
+ )
128
+ progress = f"Episode progress: {next_step}/5 (dummy)"
129
+ return next_step, _dummy_frame(task, next_step), log, progress
130
+
131
+ def restart_episode(task_name: str):
132
+ task = task_name if task_name in DUMMY_TASKS else DUMMY_TASKS[0]
133
+ step = 0
134
+ return (step,) + _build_task_updates(task, step, PHASE_DEMO_VIDEO)
135
+
136
+ def next_task(current_task: str):
137
+ try:
138
+ idx = DUMMY_TASKS.index(current_task)
139
+ except ValueError:
140
+ idx = 0
141
+ nxt = DUMMY_TASKS[(idx + 1) % len(DUMMY_TASKS)]
142
+ step = 0
143
+ return (nxt, step) + _build_task_updates(nxt, step, PHASE_DEMO_VIDEO)
144
+
145
+ with gr.Blocks(title="Oracle Planner Interface") as demo:
146
+ demo.theme = gr.themes.Soft()
147
+ demo.css = CSS
148
+
149
+ step_state = gr.State(0)
150
+ ui_phase_state = gr.State(PHASE_DEMO_VIDEO)
151
+
152
+ gr.Markdown("## RoboMME Human Evaluation", elem_id="header_title")
153
+ with gr.Row():
154
+ with gr.Column(scale=1):
155
+ header_task_box = gr.Dropdown(
156
+ choices=DUMMY_TASKS,
157
+ value=DUMMY_TASKS[0],
158
+ label="Current Task",
159
+ show_label=True,
160
+ interactive=True,
161
+ elem_id="header_task",
162
+ )
163
+ with gr.Column(scale=2):
164
+ header_goal_box = gr.Textbox(
165
+ value=_task_goal(DUMMY_TASKS[0]),
166
+ label="Goal",
167
+ show_label=True,
168
+ interactive=False,
169
+ lines=1,
170
+ elem_id="header_goal",
171
+ )
172
+
173
+ with gr.Column(visible=False, elem_id="loading_overlay_group") as loading_overlay:
174
+ gr.Markdown("### Logging in and setting up environment... Please wait.")
175
+
176
+ with gr.Column(visible=True, elem_id="main_interface_root") as main_interface:
177
+ with gr.Row(elem_id="main_layout_row"):
178
+ with gr.Column(scale=5):
179
+ with gr.Column(elem_classes=["native-card"], elem_id="media_card"):
180
+ with gr.Column(visible=True, elem_id="video_phase_group") as video_phase_group:
181
+ video_display = gr.Video(
182
+ label="Demonstration Video",
183
+ interactive=False,
184
+ elem_id="demo_video",
185
+ autoplay=False,
186
+ show_label=True,
187
+ value=None,
188
+ visible=True,
189
+ )
190
+ skip_video_btn = gr.Button("Skip Demo Video", variant="secondary")
191
+
192
+ with gr.Column(visible=False, elem_id="action_phase_group") as action_phase_group:
193
+ img_display = gr.Image(
194
+ label="Keypoint Selection",
195
+ interactive=False,
196
+ type="numpy",
197
+ elem_id="live_obs",
198
+ show_label=True,
199
+ buttons=[],
200
+ sources=[],
201
+ value=_dummy_frame(DUMMY_TASKS[0], 0),
202
+ )
203
+
204
+ with gr.Column(scale=4):
205
+ with gr.Column(visible=False, elem_id="control_panel_group") as control_panel_group:
206
+ with gr.Row(elem_id="right_top_row", equal_height=False):
207
+ with gr.Column(scale=3, elem_id="right_action_col"):
208
+ with gr.Column(elem_classes=["native-card"], elem_id="action_selection_card"):
209
+ options_radio = gr.Radio(
210
+ choices=_task_actions(DUMMY_TASKS[0]),
211
+ label=" Action Selection",
212
+ type="value",
213
+ show_label=True,
214
+ elem_id="action_radio",
215
+ )
216
+ coords_box = gr.Textbox(
217
+ label="Coords",
218
+ value="",
219
+ interactive=False,
220
+ show_label=False,
221
+ visible=False,
222
+ elem_id="coords_box",
223
+ )
224
+
225
+ with gr.Column(scale=2, elem_id="right_log_col"):
226
+ with gr.Column(elem_classes=["native-card"], elem_id="log_card"):
227
+ log_output = gr.Textbox(
228
+ value="[DUMMY] Demo video phase. Click 'Skip Demo Video' to continue.",
229
+ lines=4,
230
+ max_lines=None,
231
+ show_label=True,
232
+ interactive=False,
233
+ elem_id="log_output",
234
+ label="System Log",
235
+ )
236
+
237
+ with gr.Row(elem_id="action_buttons_row"):
238
+ with gr.Column(elem_classes=["native-card", "native-button-card"], elem_id="exec_btn_card"):
239
+ exec_btn = gr.Button("EXECUTE", variant="stop", size="lg", elem_id="exec_btn")
240
+
241
+ with gr.Column(
242
+ elem_classes=["native-card", "native-button-card"],
243
+ elem_id="reference_btn_card",
244
+ ):
245
+ reference_action_btn = gr.Button(
246
+ "Ground Truth Action",
247
+ variant="secondary",
248
+ interactive=True,
249
+ elem_id="reference_action_btn",
250
+ )
251
+
252
+ with gr.Column(
253
+ elem_classes=["native-card", "native-button-card"],
254
+ elem_id="restart_episode_btn_card",
255
+ ):
256
+ restart_episode_btn = gr.Button(
257
+ "restart episode",
258
+ variant="secondary",
259
+ interactive=True,
260
+ elem_id="restart_episode_btn",
261
+ )
262
+
263
+ with gr.Column(
264
+ elem_classes=["native-card", "native-button-card"],
265
+ elem_id="next_task_btn_card",
266
+ ):
267
+ next_task_btn = gr.Button(
268
+ "change episode",
269
+ variant="primary",
270
+ interactive=True,
271
+ elem_id="next_task_btn",
272
+ )
273
+
274
+ with gr.Column(visible=True, elem_classes=["native-card"], elem_id="task_hint_card"):
275
+ task_hint_display = gr.Textbox(
276
+ value=_task_hint(DUMMY_TASKS[0]),
277
+ lines=8,
278
+ max_lines=16,
279
+ show_label=True,
280
+ label="Task Hint",
281
+ interactive=True,
282
+ elem_id="task_hint_display",
283
+ )
284
 
285
+ progress_info_box = gr.Textbox(value="Episode progress: 0/5 (dummy)", visible=False)
286
 
287
+ header_task_box.change(
288
+ fn=on_task_change,
289
+ inputs=[header_task_box],
290
+ outputs=[
291
+ step_state,
292
+ header_goal_box,
293
+ options_radio,
294
+ coords_box,
295
+ log_output,
296
+ task_hint_display,
297
+ progress_info_box,
298
+ img_display,
299
+ video_phase_group,
300
+ action_phase_group,
301
+ control_panel_group,
302
+ ui_phase_state,
303
+ ],
304
+ )
305
 
306
+ skip_video_btn.click(
307
+ fn=skip_video,
308
+ inputs=[header_task_box, step_state],
309
+ outputs=[
310
+ header_goal_box,
311
+ options_radio,
312
+ coords_box,
313
+ log_output,
314
+ task_hint_display,
315
+ progress_info_box,
316
+ img_display,
317
+ video_phase_group,
318
+ action_phase_group,
319
+ control_panel_group,
320
+ ui_phase_state,
321
+ ],
322
+ )
323
 
324
+ img_display.select(
325
+ fn=on_map_click,
326
+ outputs=[coords_box],
327
+ )
328
 
329
+ reference_action_btn.click(
330
+ fn=on_reference_action,
331
+ inputs=[header_task_box],
332
+ outputs=[options_radio, log_output],
333
+ )
334
 
335
+ exec_btn.click(
336
+ fn=execute_step,
337
+ inputs=[header_task_box, options_radio, coords_box, step_state],
338
+ outputs=[step_state, img_display, log_output, progress_info_box],
339
+ )
340
 
341
+ restart_episode_btn.click(
342
+ fn=restart_episode,
343
+ inputs=[header_task_box],
344
+ outputs=[
345
+ step_state,
346
+ header_goal_box,
347
+ options_radio,
348
+ coords_box,
349
+ log_output,
350
+ task_hint_display,
351
+ progress_info_box,
352
+ img_display,
353
+ video_phase_group,
354
+ action_phase_group,
355
+ control_panel_group,
356
+ ui_phase_state,
357
+ ],
358
+ )
359
 
360
+ next_task_btn.click(
361
+ fn=next_task,
362
+ inputs=[header_task_box],
363
+ outputs=[
364
+ header_task_box,
365
+ step_state,
366
+ header_goal_box,
367
+ options_radio,
368
+ coords_box,
369
+ log_output,
370
+ task_hint_display,
371
+ progress_info_box,
372
+ img_display,
373
+ video_phase_group,
374
+ action_phase_group,
375
+ control_panel_group,
376
+ ui_phase_state,
377
+ ],
378
+ )
379
 
380
+ demo.load(
381
+ fn=on_task_change,
382
+ inputs=[header_task_box],
383
+ outputs=[
384
+ step_state,
385
+ header_goal_box,
386
+ options_radio,
387
+ coords_box,
388
+ log_output,
389
+ task_hint_display,
390
+ progress_info_box,
391
+ img_display,
392
+ video_phase_group,
393
+ action_phase_group,
394
+ control_panel_group,
395
+ ui_phase_state,
396
+ ],
397
+ )
398
 
399
+ return demo
 
 
400
 
 
 
 
 
 
401
 
402
+ demo = create_dummy_demo()
403
 
404
 
405
  if __name__ == "__main__":
 
406
  demo.launch(
407
  server_name="0.0.0.0",
408
  server_port=int(os.getenv("PORT", "7860")),