File size: 7,548 Bytes
c3c48e6
26c9f29
c3c48e6
 
 
eb39aa1
26c9f29
 
eb39aa1
715c080
 
 
 
 
 
 
 
 
5aea41f
 
 
 
 
 
 
3403b3b
715c080
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5aea41f
 
 
 
 
 
3403b3b
 
 
 
 
 
 
 
 
 
 
 
 
 
c3c48e6
 
 
 
 
 
5aea41f
 
c3c48e6
 
 
5aea41f
 
c3c48e6
 
3403b3b
99e35a0
 
 
3403b3b
99e35a0
 
 
3403b3b
99e35a0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c3c48e6
99e35a0
c3c48e6
 
99e35a0
 
 
 
 
3403b3b
99e35a0
 
 
 
 
 
3403b3b
99e35a0
 
 
 
 
 
 
715c080
99e35a0
3403b3b
99e35a0
3403b3b
c3c48e6
715c080
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c3c48e6
715c080
 
 
 
 
 
 
 
 
 
 
c3c48e6
715c080
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
786d956
715c080
786d956
715c080
 
c3c48e6
715c080
 
264b6be
 
 
 
 
 
 
 
 
 
eb39aa1
26c9f29
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
import json
from pathlib import Path
import gradio as gr
import plotly.graph_objects as go
import plotly.io as pio

DATA_DIR = Path(__file__).parent / "data"
video_path = DATA_DIR / "video.mp4"

METRIC_LABELS = {
    "x_cm": "X (cm)",
    "y_cm": "Y (cm)",
    "z_cm": "Z (cm)",
    "yaw_deg": "Yaw (°)",
    "pitch_deg": "Pitch (°)",
    "roll_deg": "Roll (°)",
}

PLOT_GRID = [
    ["x_cm", "y_cm", "z_cm"],
    ["yaw_deg", "pitch_deg", "roll_deg"],
]

PLOT_ORDER = [metric for row in PLOT_GRID for metric in row]

CUSTOM_CSS = """
:root, .gradio-container, body {
    background-color: #050a18 !important;
    color: #f8fafc !important;
    font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
}
.side-panel {
    background: #0f172a;
    padding: 20px;
    border-radius: 18px;
    border: 1px solid #1f2b47;
    min-height: 100%;
}
.stats-card ul {
    list-style: none;
    padding: 0;
    margin: 0;
    font-size: 0.92rem;
}
.stats-card li {
    margin-bottom: 10px;
    color: #e2e8f0;
}
.stats-card span {
    display: inline-block;
    margin-right: 6px;
    color: #7dd3fc;
}
.main-panel {
    padding-top: 8px;
}
.video-card {
    background: #0f172a;
    border: 1px solid #1f2b47;
    border-radius: 18px;
    padding: 18px 20px;
    margin-top: 18px;
}
.video-title {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.18em;
    color: #94a3b8;
    margin-bottom: 8px;
}
.video-panel video {
    border-radius: 12px;
    border: 1px solid #1f2b47;
    background: #030712;
}
.plots-wrap {
    margin-top: 18px;
}
.plots-wrap .gr-row {
    gap: 16px;
}
.plot-html {
    background: #111a2c;
    border-radius: 12px;
    padding: 10px;
    border: 1px solid #1f2b47;
    min-height: 320px;
}
.plot-html iframe {
    width: 100%;
    height: 300px;
    border: none;
}
"""

def load_data():
    metadata = {}
    end_effector = {}
    try:
        with open(DATA_DIR / "metadata.json", 'r') as f:
            metadata = json.load(f)
    except:
        pass
    try:
        with open(DATA_DIR / "end_effector.json", 'r') as f:
            end_effector = json.load(f)
    except:
        pass
    return metadata, end_effector

def build_plot_html(metadata, end_effector, hand, metric):
    try:
        fps = metadata.get('fps', 60)
        if not end_effector:
            return "<p>No data</p>"
        
        frame_keys = sorted([int(k) for k in end_effector.keys() if str(k).isdigit()])
        if not frame_keys:
            return "<p>No frames</p>"
        
        times = [i/fps for i in frame_keys]
        values = []
        
        for k in frame_keys:
            ee = end_effector.get(str(k), {}) or {}
            hd = ee.get(hand + "_hand")
            if hd and isinstance(hd, dict):
                pose = hd.get('pose_6dof', [])
                if len(pose) >= 6:
                    if metric == "x_cm":
                        values.append(pose[0] * 100)
                    elif metric == "y_cm":
                        values.append(pose[1] * 100)
                    elif metric == "z_cm":
                        values.append(pose[2] * 100)
                    elif metric == "roll_deg":
                        values.append(pose[3] * 57.3)
                    elif metric == "pitch_deg":
                        values.append(pose[4] * 57.3)
                    elif metric == "yaw_deg":
                        values.append(pose[5] * 57.3)
                else:
                    values.append(None)
            else:
                values.append(None)
        
        valid_times = [t for t, v in zip(times, values) if v is not None]
        valid_values = [v for v in values if v is not None]
        
        if not valid_times or not valid_values:
            return "<p>No valid data</p>"
        
        fig = go.Figure()
        fig.add_trace(go.Scatter(
            x=valid_times, 
            y=valid_values, 
            mode="lines",
            name=f"{hand} {metric}",
            line=dict(width=2)
        ))
        fig.update_layout(
            template="plotly_dark", 
            height=250, 
            margin=dict(l=20, r=20, t=30, b=20),
            xaxis_title="Time (s)",
            yaxis_title=METRIC_LABELS[metric]
        )
        return pio.to_html(fig, include_plotlyjs="cdn", full_html=False)
    except Exception as e:
        return f"<p>Error: {str(e)}</p>"

def build_interface():
    metadata, end_effector = load_data()
    total_frames = len(metadata.get('poses', []))
    fps = metadata.get('fps', 60)
    
    # Build plots for left and right hands
    left_figs = {metric: build_plot_html(metadata, end_effector, "left", metric) for metric in PLOT_ORDER}
    right_figs = {metric: build_plot_html(metadata, end_effector, "right", metric) for metric in PLOT_ORDER}
    
    stats_html = f"""
<div class="stats-card">
  <ul>
    <li><span>Number of samples/frames:</span> {total_frames:,}</li>
    <li><span>Frames per second:</span> {fps:.1f}</li>
  </ul>
</div>
"""
    
    theme = gr.themes.Soft(
        primary_hue="cyan", secondary_hue="blue", neutral_hue="slate"
    ).set(
        body_background_fill="#0c1424",
        body_text_color="#f8fafc",
        block_background_fill="#111a2c",
        block_title_text_color="#f8fafc",
        input_background_fill="#151f33",
        border_color_primary="#1f2b47",
        shadow_drop="none",
    )
    
    with gr.Blocks(theme=theme, css=CUSTOM_CSS) as demo:
        gr.Markdown("# 🤖 Dynamic Intelligence - Human Demo Visualizer")
        gr.Markdown("Egocentric hand tracking dataset for humanoid robot training.")
        
        with gr.Row(equal_height=True):
            with gr.Column(scale=1, min_width=260, elem_classes=["side-panel"]):
                gr.HTML(stats_html)
                gr.HTML('<div class="episodes-title">Hands</div>')
                hand_radio = gr.Radio(
                    choices=["Left Hand", "Right Hand"],
                    value="Left Hand",
                    label="Hand Selection",
                )
            with gr.Column(scale=2, min_width=640, elem_classes=["main-panel"]):
                with gr.Column(elem_classes=["video-card"]):
                    gr.HTML('<div class="video-title">RGB Video</div>')
                    video = gr.Video(
                        height=360,
                        value=str(video_path) if video_path.exists() else None,
                        elem_classes=["video-panel"],
                        show_label=False,
                    )
        
        plot_outputs = []
        gr.Markdown("### Hand Trajectories")
        with gr.Column(elem_classes=["plots-wrap"]):
            for row in PLOT_GRID:
                with gr.Row():
                    for metric in row:
                        plot = gr.HTML(value=left_figs[metric], elem_classes=["plot-html"])
                        plot_outputs.append(plot)
        
        def update_plots(hand_choice):
            if hand_choice == "Left Hand":
                return [left_figs[metric] for metric in PLOT_ORDER]
            else:
                return [right_figs[metric] for metric in PLOT_ORDER]
        
        hand_radio.change(fn=update_plots, inputs=hand_radio, outputs=plot_outputs)
    
    return demo

# For Gradio Spaces
try:
    demo = build_interface()
except Exception as e:
    print(f"Error: {e}")
    import traceback
    traceback.print_exc()
    with gr.Blocks() as demo:
        gr.Markdown("# Error")
        gr.Markdown(str(e))

if __name__ == "__main__":
    demo.launch()