Raffael-Kultyshev commited on
Commit
b6b74d1
·
1 Parent(s): 679953e

Simplified version - remove lru_cache and complex CSS

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