Raffael-Kultyshev commited on
Commit
045f914
·
1 Parent(s): ac90379

Restore full app with better error handling

Browse files
Files changed (1) hide show
  1. app.py +377 -6
app.py CHANGED
@@ -1,16 +1,387 @@
1
  """
2
- Minimal test version
 
3
  """
 
4
  import gradio as gr
 
 
 
5
  from pathlib import Path
 
 
 
6
 
 
7
  DATA_DIR = Path(__file__).parent / "data"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  video_path = DATA_DIR / "video.mp4"
9
 
10
- with gr.Blocks() as demo:
11
- gr.Markdown("# Test")
12
- gr.Video(value=str(video_path) if video_path.exists() else None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- if __name__ == "__main__":
15
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ Dynamic Intelligence - Human Demo Visualizer
3
+ Egocentric hand tracking dataset visualizer for robot training data
4
  """
5
+
6
  import gradio as gr
7
+ import json
8
+ import html
9
+ import numpy as np
10
  from pathlib import Path
11
+ import plotly.graph_objects as go
12
+ import plotly.io as pio
13
+ from typing import Dict, List
14
 
15
+ # Load pipeline data
16
  DATA_DIR = Path(__file__).parent / "data"
17
+
18
+ def load_json_safe(path):
19
+ try:
20
+ with open(path, 'r') as f:
21
+ return json.load(f)
22
+ except Exception as e:
23
+ print(f"Error loading {path}: {e}")
24
+ return {}
25
+
26
+ # Load data at startup
27
+ print("Loading data...")
28
+ metadata = load_json_safe(DATA_DIR / "metadata.json")
29
+ end_effector = load_json_safe(DATA_DIR / "end_effector.json")
30
+ actions = load_json_safe(DATA_DIR / "actions.json")
31
+ hands_2d = load_json_safe(DATA_DIR / "hands_2d.json")
32
+ print(f"Loaded: metadata={len(metadata)}, ee={len(end_effector)}, actions={len(actions)}, hands={len(hands_2d)}")
33
+
34
+ # Stats
35
+ total_frames = max(1, len(metadata.get('poses', [])))
36
+ fps = metadata.get('fps', 60)
37
+ hand_detection_rate = len(hands_2d) / total_frames * 100
38
+
39
+ # Count poses safely
40
+ left_poses = 0
41
+ right_poses = 0
42
+ for frame_data in end_effector.values():
43
+ if frame_data and isinstance(frame_data, dict):
44
+ if frame_data.get('left_hand'):
45
+ left_poses += 1
46
+ if frame_data.get('right_hand'):
47
+ right_poses += 1
48
+
49
+ print(f"Stats: frames={total_frames}, left={left_poses}, right={right_poses}")
50
+
51
+ # Video path
52
  video_path = DATA_DIR / "video.mp4"
53
 
54
+ # Prepare time-series data
55
+ def prepare_data():
56
+ """Prepare time-series data for plots."""
57
+ times = []
58
+ left_pos = {'x': [], 'y': [], 'z': []}
59
+ left_rot = {'yaw': [], 'pitch': [], 'roll': []}
60
+ right_pos = {'x': [], 'y': [], 'z': []}
61
+ right_rot = {'yaw': [], 'pitch': [], 'roll': []}
62
+
63
+ frame_keys = sorted([int(k) for k in end_effector.keys() if k.isdigit()])
64
+
65
+ for frame_idx in frame_keys:
66
+ frame_key = str(frame_idx)
67
+ t = frame_idx / fps
68
+ times.append(t)
69
+
70
+ ee_data = end_effector.get(frame_key, {}) or {}
71
+
72
+ # Left hand
73
+ left_hand_data = ee_data.get('left_hand')
74
+ if left_hand_data and isinstance(left_hand_data, dict):
75
+ pose = left_hand_data.get('pose_6dof')
76
+ if pose and len(pose) >= 6:
77
+ left_pos['x'].append(pose[0] * 100) # m to cm
78
+ left_pos['y'].append(pose[1] * 100)
79
+ left_pos['z'].append(pose[2] * 100)
80
+ left_rot['roll'].append(pose[3] * 57.3) # rad to deg
81
+ left_rot['pitch'].append(pose[4] * 57.3)
82
+ left_rot['yaw'].append(pose[5] * 57.3)
83
+ else:
84
+ for k in left_pos: left_pos[k].append(None)
85
+ for k in left_rot: left_rot[k].append(None)
86
+ else:
87
+ for k in left_pos: left_pos[k].append(None)
88
+ for k in left_rot: left_rot[k].append(None)
89
+
90
+ # Right hand
91
+ right_hand_data = ee_data.get('right_hand')
92
+ if right_hand_data and isinstance(right_hand_data, dict):
93
+ pose = right_hand_data.get('pose_6dof')
94
+ if pose and len(pose) >= 6:
95
+ right_pos['x'].append(pose[0] * 100)
96
+ right_pos['y'].append(pose[1] * 100)
97
+ right_pos['z'].append(pose[2] * 100)
98
+ right_rot['roll'].append(pose[3] * 57.3)
99
+ right_rot['pitch'].append(pose[4] * 57.3)
100
+ right_rot['yaw'].append(pose[5] * 57.3)
101
+ else:
102
+ for k in right_pos: right_pos[k].append(None)
103
+ for k in right_rot: right_rot[k].append(None)
104
+ else:
105
+ for k in right_pos: right_pos[k].append(None)
106
+ for k in right_rot: right_rot[k].append(None)
107
+
108
+ return {
109
+ 'times': times,
110
+ 'left_pos': left_pos,
111
+ 'left_rot': left_rot,
112
+ 'right_pos': right_pos,
113
+ 'right_rot': right_rot
114
+ }
115
 
116
+ try:
117
+ plot_data = prepare_data()
118
+ except Exception as e:
119
+ print(f"Error preparing plot data: {e}")
120
+ plot_data = {
121
+ 'times': [],
122
+ 'left_pos': {'x': [], 'y': [], 'z': []},
123
+ 'left_rot': {'yaw': [], 'pitch': [], 'roll': []},
124
+ 'right_pos': {'x': [], 'y': [], 'z': []},
125
+ 'right_rot': {'yaw': [], 'pitch': [], 'roll': []}
126
+ }
127
+
128
+ METRIC_LABELS = {
129
+ "x_cm": "X (cm)",
130
+ "y_cm": "Y (cm)",
131
+ "z_cm": "Z (cm)",
132
+ "yaw_deg": "Yaw (°)",
133
+ "pitch_deg": "Pitch (°)",
134
+ "roll_deg": "Roll (°)",
135
+ }
136
+
137
+ PLOT_GRID = [
138
+ ["x_cm", "y_cm", "z_cm"],
139
+ ["yaw_deg", "pitch_deg", "roll_deg"],
140
+ ]
141
+
142
+ PLOT_ORDER = [metric for row in PLOT_GRID for metric in row]
143
+
144
+ CUSTOM_CSS = """
145
+ :root, .gradio-container, body {
146
+ background-color: #050a18 !important;
147
+ color: #f8fafc !important;
148
+ font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
149
+ }
150
+ .side-panel {
151
+ background: #0f172a;
152
+ padding: 20px;
153
+ border-radius: 18px;
154
+ border: 1px solid #1f2b47;
155
+ min-height: 100%;
156
+ }
157
+ .stats-card ul {
158
+ list-style: none;
159
+ padding: 0;
160
+ margin: 0;
161
+ font-size: 0.92rem;
162
+ }
163
+ .stats-card li {
164
+ margin-bottom: 10px;
165
+ color: #e2e8f0;
166
+ }
167
+ .stats-card span {
168
+ display: inline-block;
169
+ margin-right: 6px;
170
+ color: #7dd3fc;
171
+ }
172
+ .main-panel {
173
+ padding-top: 8px;
174
+ }
175
+ .instruction-card {
176
+ background: #0f172a;
177
+ padding: 18px 20px;
178
+ border-radius: 18px;
179
+ border: 1px solid #1f2b47;
180
+ }
181
+ .instruction-label {
182
+ font-size: 0.75rem;
183
+ letter-spacing: 0.12em;
184
+ text-transform: uppercase;
185
+ color: #94a3b8;
186
+ margin-bottom: 10px;
187
+ }
188
+ .instruction-text {
189
+ font-size: 1.1rem;
190
+ line-height: 1.5;
191
+ }
192
+ .video-card {
193
+ background: #0f172a;
194
+ border: 1px solid #1f2b47;
195
+ border-radius: 18px;
196
+ padding: 18px 20px;
197
+ margin-top: 18px;
198
+ }
199
+ .video-title {
200
+ font-size: 0.78rem;
201
+ text-transform: uppercase;
202
+ letter-spacing: 0.18em;
203
+ color: #94a3b8;
204
+ margin-bottom: 8px;
205
+ }
206
+ .video-panel video {
207
+ border-radius: 12px;
208
+ border: 1px solid #1f2b47;
209
+ background: #030712;
210
+ }
211
+ .download-button button {
212
+ border-radius: 999px;
213
+ border: 1px solid #334155;
214
+ background: #1e293b;
215
+ color: #f8fafc;
216
+ font-size: 0.85rem;
217
+ padding: 8px 24px;
218
+ }
219
+ .download-button button:hover {
220
+ border-color: #67e8f9;
221
+ color: #67e8f9;
222
+ }
223
+ .plots-wrap {
224
+ margin-top: 18px;
225
+ }
226
+ .plots-wrap .gr-row {
227
+ gap: 16px;
228
+ }
229
+ .plot-html {
230
+ background: #111a2c;
231
+ border-radius: 12px;
232
+ padding: 10px;
233
+ border: 1px solid #1f2b47;
234
+ min-height: 320px;
235
+ }
236
+ .plot-html iframe {
237
+ width: 100%;
238
+ height: 300px;
239
+ border: none;
240
+ }
241
+ """
242
+
243
+ def build_plot_fig(metric: str, hand: str = "left") -> go.Figure:
244
+ """Build Plotly figure for a metric."""
245
+ try:
246
+ times = plot_data.get('times', [])
247
+
248
+ if hand == "left":
249
+ if "cm" in metric:
250
+ key = metric.replace('_cm', '')
251
+ data = plot_data.get('left_pos', {}).get(key, [])
252
+ else:
253
+ key = metric.replace('_deg', '')
254
+ data = plot_data.get('left_rot', {}).get(key, [])
255
+ name = "Left Hand"
256
+ else:
257
+ if "cm" in metric:
258
+ key = metric.replace('_cm', '')
259
+ data = plot_data.get('right_pos', {}).get(key, [])
260
+ else:
261
+ key = metric.replace('_deg', '')
262
+ data = plot_data.get('right_rot', {}).get(key, [])
263
+ name = "Right Hand"
264
+
265
+ if not times or not data:
266
+ # Return empty plot
267
+ times = [0]
268
+ data = [0]
269
+ except Exception as e:
270
+ print(f"Error in build_plot_fig: {e}")
271
+ times = [0]
272
+ data = [0]
273
+ name = "Error"
274
+
275
+ fig = go.Figure()
276
+ fig.add_trace(
277
+ go.Scatter(
278
+ x=times,
279
+ y=data,
280
+ mode="lines",
281
+ name=name,
282
+ line=dict(color="#67e8f9", width=2)
283
+ )
284
+ )
285
+ fig.update_layout(
286
+ margin=dict(l=20, r=20, t=30, b=20),
287
+ height=250,
288
+ template="plotly_dark",
289
+ legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
290
+ xaxis_title="Time (s)",
291
+ yaxis_title=METRIC_LABELS[metric],
292
+ )
293
+ fig.update_xaxes(showgrid=True, gridwidth=0.5, gridcolor="rgba(255,255,255,0.1)")
294
+ fig.update_yaxes(showgrid=True, gridwidth=0.5, gridcolor="rgba(255,255,255,0.1)")
295
+ return fig
296
+
297
+ def build_plot_html(metric: str, hand: str = "left") -> str:
298
+ """Build Plotly HTML for a metric."""
299
+ fig = build_plot_fig(metric, hand)
300
+ return pio.to_html(fig, include_plotlyjs="cdn", full_html=False)
301
+
302
+ # Build interface
303
+ stats_html = f"""
304
+ <div class="stats-card">
305
+ <ul>
306
+ <li><span>Number of samples/frames:</span> {total_frames:,}</li>
307
+ <li><span>Hand detection rate:</span> {hand_detection_rate:.1f}%</li>
308
+ <li><span>Left hand poses:</span> {left_poses}</li>
309
+ <li><span>Right hand poses:</span> {right_poses}</li>
310
+ <li><span>Frames per second:</span> {fps:.1f}</li>
311
+ </ul>
312
+ </div>
313
+ """
314
+
315
+ instruction_text = "LiDAR-based egocentric hand tracking for robot training data"
316
 
317
+ theme = gr.themes.Soft(
318
+ primary_hue="cyan", secondary_hue="blue", neutral_hue="slate"
319
+ ).set(
320
+ body_background_fill="#0c1424",
321
+ body_text_color="#f8fafc",
322
+ block_background_fill="#111a2c",
323
+ block_title_text_color="#f8fafc",
324
+ input_background_fill="#151f33",
325
+ border_color_primary="#1f2b47",
326
+ shadow_drop="none",
327
+ )
328
+
329
+ def format_instruction_html(text: str) -> str:
330
+ safe_text = html.escape(text)
331
+ return (
332
+ '<div class="instruction-card">'
333
+ '<p class="instruction-label">Language Instruction</p>'
334
+ f'<p class="instruction-text">{safe_text}</p>'
335
+ "</div>"
336
+ )
337
+
338
+ with gr.Blocks(theme=theme, css=CUSTOM_CSS) as demo:
339
+ gr.Markdown("# 🤖 Dynamic Intelligence - Human Demo Visualizer")
340
+ gr.Markdown(
341
+ "Egocentric hand tracking dataset for humanoid robot training. "
342
+ "Pipeline: iPhone LiDAR → MediaPipe → 6DoF End-Effector → Robot Training Data"
343
+ )
344
+
345
+ with gr.Row(equal_height=True):
346
+ with gr.Column(scale=1, min_width=260, elem_classes=["side-panel"]):
347
+ gr.HTML(stats_html)
348
+ with gr.Column(scale=2, min_width=640, elem_classes=["main-panel"]):
349
+ instruction_box = gr.HTML(
350
+ format_instruction_html(instruction_text),
351
+ label="Language Instruction",
352
+ )
353
+ with gr.Column(elem_classes=["video-card"]):
354
+ gr.HTML('<div class="video-title">RGB Video</div>')
355
+ video = gr.Video(
356
+ height=360,
357
+ value=str(video_path) if video_path.exists() else None,
358
+ elem_classes=["video-panel"],
359
+ show_label=False,
360
+ show_download_button=False,
361
+ )
362
+ download_button = gr.DownloadButton(
363
+ label="Download",
364
+ value=str(video_path) if video_path.exists() else None,
365
+ elem_classes=["download-button"],
366
+ )
367
+
368
+ gr.Markdown("### Left Hand Trajectories", elem_classes=["plots-title"])
369
+ plot_outputs_left = []
370
+ with gr.Column(elem_classes=["plots-wrap"]):
371
+ for row in PLOT_GRID:
372
+ with gr.Row():
373
+ for metric in row:
374
+ plot = gr.HTML(value=build_plot_html(metric, "left"), elem_classes=["plot-html"])
375
+ plot_outputs_left.append(plot)
376
+
377
+ gr.Markdown("### Right Hand Trajectories", elem_classes=["plots-title"])
378
+ plot_outputs_right = []
379
+ with gr.Column(elem_classes=["plots-wrap"]):
380
+ for row in PLOT_GRID:
381
+ with gr.Row():
382
+ for metric in row:
383
+ plot = gr.HTML(value=build_plot_html(metric, "right"), elem_classes=["plot-html"])
384
+ plot_outputs_right.append(plot)
385
+
386
+ if __name__ == "__main__":
387
+ demo.queue().launch(show_api=False)