Raffael-Kultyshev commited on
Commit
f85c4b8
·
1 Parent(s): d7e3453

Rebuild app using exact reference structure with build_interface() function

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