zijian2022 commited on
Commit
4540c16
·
verified ·
1 Parent(s): f4bb1fe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +210 -29
app.py CHANGED
@@ -49,7 +49,7 @@ class RemoteDatasetLoader:
49
  if result.returncode == 0 and '264' in result.stdout:
50
  return True
51
  except Exception as e:
52
- print(f"ffprobe检查视频失败: {e}")
53
  return False
54
 
55
  def _download_video(self, video_url: str, save_path: str) -> str:
@@ -91,7 +91,7 @@ class RemoteDatasetLoader:
91
  local_path = os.path.join(download_dir, video_filename)
92
  # 优先加载本地有效mp4
93
  if self._is_valid_mp4(local_path):
94
- print(f"本地已存在有效视频: {local_path}")
95
  video_paths.append(local_path)
96
  continue
97
  try:
@@ -203,21 +203,135 @@ server = app.server
203
 
204
  # ------------------ 页面布局 ------------------
205
  app.layout = html.Div([
206
- html.H1("机器人数据可视化 - 视频兼容性优化", style={"textAlign": "center", "marginBottom": "20px"}),
207
  html.Div([
208
- html.Label("repo_id:"),
209
- dcc.Input(id="input-repo-id", type="text", value="zijian2022/sortingtest", style={"width": "300px"}),
210
- html.Label("episode_id:", style={"marginLeft": "20px"}),
211
- dcc.Input(id="input-episode-id", type="number", value=0, min=0, style={"width": "80px"}),
212
- html.Button("加载", id="btn-load", n_clicks=0, style={"marginLeft": "20px"}),
213
- ], style={"textAlign": "center", "marginBottom": "30px"}),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  dcc.Loading(
215
  id="loading",
216
- type="default",
 
217
  children=dcc.Store(id="store-data")
218
  ),
219
- html.Div(id="main-content")
220
- ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
  # ------------------ 数据加载回调 ------------------
223
  @app.callback(
@@ -243,7 +357,7 @@ def load_data_callback(n_clicks, repo_id, episode_id):
243
  "timestamps": data_df["timestamp"].tolist()
244
  }
245
  except Exception as e:
246
- print(f"数据加载异常: {e}")
247
  return {}
248
 
249
  # ------------------ 主内容渲染回调 ------------------
@@ -253,17 +367,65 @@ def load_data_callback(n_clicks, repo_id, episode_id):
253
  )
254
  def update_main_content(data):
255
  if not data or "data_df" not in data or len(data["data_df"]) == 0:
256
- return html.Div("请点击上方“加载”按钮获取数据", style={"textAlign": "center", "color": "red"})
 
 
 
 
 
 
 
 
 
 
257
  columns = data["columns"]
258
  rows = []
259
  for i, joint in enumerate(columns):
260
  rows.append(html.Div([
261
- html.Div(dcc.Graph(id=f"graph-{i}"), style={"width": "60%", "display": "inline-block", "verticalAlign": "top"}),
 
 
 
 
 
 
 
 
 
 
 
 
262
  html.Div([
263
- html.Img(id=f"video1-{i}", style={"width": "49%", "height": "180px", "objectFit": "contain", "display": "inline-block"}),
264
- html.Img(id=f"video2-{i}", style={"width": "49%", "height": "180px", "objectFit": "contain", "display": "inline-block"})
265
- ], style={"width": "38%", "display": "inline-block", "paddingLeft": "2%"})
266
- ], style={"marginBottom": "15px"}))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  return html.Div(rows)
268
 
269
  # ------------------ 阴影与高亮工具函数 ------------------
@@ -350,8 +512,8 @@ def generate_joint_graph(joint_name, idx, action_df, delta_t, time_for_plot, all
350
  shadow['end_time'] == h_shadow['end_time']):
351
  is_highlighted = True
352
  break
353
- color = "blue" if is_highlighted else "red"
354
- opacity = 0.6 if is_highlighted else 0.3
355
  shapes.append({
356
  "type": "rect",
357
  "xref": "x",
@@ -369,19 +531,38 @@ def generate_joint_graph(joint_name, idx, action_df, delta_t, time_for_plot, all
369
  go.Scatter(
370
  x=time_for_plot,
371
  y=smoothed_angle,
372
- name="Angle",
373
- line=dict(color='orange')
 
374
  )
375
  ],
376
  "layout": go.Layout(
377
- title=joint_name,
378
- xaxis={"title": "Time (s)"},
379
- yaxis={"title": "Angle (deg)"},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  shapes=shapes,
381
  hovermode="x unified",
382
- height=250,
383
- margin=dict(t=30, b=30, l=50, r=50),
384
  showlegend=False,
 
 
 
385
  )
386
  }
387
 
@@ -493,7 +674,7 @@ for i in range(6):
493
  else:
494
  return no_update, no_update
495
  except Exception as e:
496
- print(f"update_video_frames回调函数异常: {e}")
497
  return no_update, no_update
498
 
499
  # ------------------ 启动应用 ------------------
 
49
  if result.returncode == 0 and '264' in result.stdout:
50
  return True
51
  except Exception as e:
52
+ print(f"ffprobe video check failed: {e}")
53
  return False
54
 
55
  def _download_video(self, video_url: str, save_path: str) -> str:
 
91
  local_path = os.path.join(download_dir, video_filename)
92
  # 优先加载本地有效mp4
93
  if self._is_valid_mp4(local_path):
94
+ print(f"Local valid video found: {local_path}")
95
  video_paths.append(local_path)
96
  continue
97
  try:
 
203
 
204
  # ------------------ 页面布局 ------------------
205
  app.layout = html.Div([
206
+ # Header with gradient background
207
  html.Div([
208
+ html.H1("Robot Data Visualization",
209
+ style={
210
+ "textAlign": "center",
211
+ "marginBottom": "10px",
212
+ "color": "white",
213
+ "fontSize": "2.5rem",
214
+ "fontWeight": "300",
215
+ "textShadow": "2px 2px 4px rgba(0,0,0,0.3)"
216
+ }),
217
+ html.P("Interactive Joint Analysis with Video Synchronization",
218
+ style={
219
+ "textAlign": "center",
220
+ "color": "rgba(255,255,255,0.9)",
221
+ "fontSize": "1.1rem",
222
+ "marginBottom": "0"
223
+ })
224
+ ], style={
225
+ "background": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
226
+ "padding": "30px 20px",
227
+ "marginBottom": "30px",
228
+ "borderRadius": "0 0 15px 15px",
229
+ "boxShadow": "0 4px 20px rgba(0,0,0,0.1)"
230
+ }),
231
+
232
+ # Control Panel
233
+ html.Div([
234
+ html.Div([
235
+ html.Label("Repository ID:",
236
+ style={
237
+ "fontWeight": "600",
238
+ "color": "#333",
239
+ "marginRight": "10px",
240
+ "fontSize": "1rem"
241
+ }),
242
+ dcc.Input(
243
+ id="input-repo-id",
244
+ type="text",
245
+ value="zijian2022/sortingtest",
246
+ style={
247
+ "width": "350px",
248
+ "padding": "12px 15px",
249
+ "border": "2px solid #e1e5e9",
250
+ "borderRadius": "8px",
251
+ "fontSize": "14px",
252
+ "transition": "border-color 0.3s ease",
253
+ "outline": "none"
254
+ },
255
+ placeholder="Enter HuggingFace dataset repository ID"
256
+ ),
257
+ ], style={"marginBottom": "15px"}),
258
+
259
+ html.Div([
260
+ html.Label("Episode ID:",
261
+ style={
262
+ "fontWeight": "600",
263
+ "color": "#333",
264
+ "marginRight": "10px",
265
+ "fontSize": "1rem"
266
+ }),
267
+ dcc.Input(
268
+ id="input-episode-id",
269
+ type="number",
270
+ value=0,
271
+ min=0,
272
+ style={
273
+ "width": "120px",
274
+ "padding": "12px 15px",
275
+ "border": "2px solid #e1e5e9",
276
+ "borderRadius": "8px",
277
+ "fontSize": "14px",
278
+ "transition": "border-color 0.3s ease",
279
+ "outline": "none"
280
+ }
281
+ ),
282
+ html.Button(
283
+ "Load Data",
284
+ id="btn-load",
285
+ n_clicks=0,
286
+ style={
287
+ "marginLeft": "20px",
288
+ "padding": "12px 25px",
289
+ "backgroundColor": "#667eea",
290
+ "color": "white",
291
+ "border": "none",
292
+ "borderRadius": "8px",
293
+ "fontSize": "14px",
294
+ "fontWeight": "600",
295
+ "cursor": "pointer",
296
+ "transition": "all 0.3s ease",
297
+ "boxShadow": "0 2px 10px rgba(102, 126, 234, 0.3)"
298
+ }
299
+ ),
300
+ ]),
301
+ ], style={
302
+ "textAlign": "center",
303
+ "marginBottom": "40px",
304
+ "padding": "25px",
305
+ "backgroundColor": "white",
306
+ "borderRadius": "12px",
307
+ "boxShadow": "0 4px 20px rgba(0,0,0,0.08)",
308
+ "border": "1px solid #f0f0f0"
309
+ }),
310
+
311
+ # Loading and Data Store
312
  dcc.Loading(
313
  id="loading",
314
+ type="circle",
315
+ style={"margin": "20px auto"},
316
  children=dcc.Store(id="store-data")
317
  ),
318
+
319
+ # Main Content Area
320
+ html.Div(
321
+ id="main-content",
322
+ style={
323
+ "backgroundColor": "#f8f9fa",
324
+ "minHeight": "400px",
325
+ "borderRadius": "12px",
326
+ "padding": "20px"
327
+ }
328
+ )
329
+ ], style={
330
+ "fontFamily": "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif",
331
+ "backgroundColor": "#f5f7fa",
332
+ "minHeight": "100vh",
333
+ "padding": "0"
334
+ })
335
 
336
  # ------------------ 数据加载回调 ------------------
337
  @app.callback(
 
357
  "timestamps": data_df["timestamp"].tolist()
358
  }
359
  except Exception as e:
360
+ print(f"Data loading error: {e}")
361
  return {}
362
 
363
  # ------------------ 主内容渲染回调 ------------------
 
367
  )
368
  def update_main_content(data):
369
  if not data or "data_df" not in data or len(data["data_df"]) == 0:
370
+ return html.Div([
371
+ html.Div("📊", style={"fontSize": "3rem", "marginBottom": "20px"}),
372
+ html.H3("No Data Available", style={"color": "#666", "marginBottom": "10px"}),
373
+ html.P("Please click the 'Load Data' button above to get data.",
374
+ style={"color": "#888", "fontSize": "1rem"})
375
+ ], style={
376
+ "textAlign": "center",
377
+ "padding": "60px 20px",
378
+ "color": "#666"
379
+ })
380
+
381
  columns = data["columns"]
382
  rows = []
383
  for i, joint in enumerate(columns):
384
  rows.append(html.Div([
385
+ # 关节图 - 左侧50%
386
+ html.Div([
387
+ dcc.Graph(id=f"graph-{i}")
388
+ ], style={
389
+ "flex": "0 0 50%",
390
+ "backgroundColor": "white",
391
+ "borderRadius": "8px",
392
+ "padding": "8px",
393
+ "boxShadow": "0 2px 10px rgba(0,0,0,0.05)",
394
+ "border": "1px solid #e9ecef",
395
+ "marginRight": "2%"
396
+ }),
397
+ # 视频区域 - 右侧48%
398
  html.Div([
399
+ html.Img(id=f"video1-{i}", style={
400
+ "width": "49%",
401
+ "height": "180px",
402
+ "objectFit": "contain",
403
+ "display": "inline-block",
404
+ "borderRadius": "6px",
405
+ "border": "2px solid #e9ecef"
406
+ }),
407
+ html.Img(id=f"video2-{i}", style={
408
+ "width": "49%",
409
+ "height": "180px",
410
+ "objectFit": "contain",
411
+ "display": "inline-block",
412
+ "borderRadius": "6px",
413
+ "border": "2px solid #e9ecef"
414
+ })
415
+ ], style={
416
+ "flex": "0 0 48%"
417
+ })
418
+ ], style={
419
+ "marginBottom": "25px",
420
+ "backgroundColor": "white",
421
+ "borderRadius": "12px",
422
+ "padding": "12px",
423
+ "boxShadow": "0 4px 15px rgba(0,0,0,0.08)",
424
+ "border": "1px solid #f0f0f0",
425
+ "display": "flex",
426
+ "alignItems": "flex-start",
427
+ "minHeight": "250px"
428
+ }))
429
  return html.Div(rows)
430
 
431
  # ------------------ 阴影与高亮工具函数 ------------------
 
512
  shadow['end_time'] == h_shadow['end_time']):
513
  is_highlighted = True
514
  break
515
+ color = "#3b82f6" if is_highlighted else "#ef4444" # Blue for highlighted, red for normal
516
+ opacity = 0.7 if is_highlighted else 0.4
517
  shapes.append({
518
  "type": "rect",
519
  "xref": "x",
 
531
  go.Scatter(
532
  x=time_for_plot,
533
  y=smoothed_angle,
534
+ name="Joint Angle",
535
+ line=dict(color='#f59e0b', width=2),
536
+ hovertemplate='<b>Time:</b> %{x:.2f}s<br><b>Angle:</b> %{y:.2f}°<extra></extra>'
537
  )
538
  ],
539
  "layout": go.Layout(
540
+ title={
541
+ 'text': joint_name.replace('_', ' ').title(),
542
+ 'font': {'size': 16, 'color': '#374151'}
543
+ },
544
+ xaxis={
545
+ "title": "Time (seconds)",
546
+ "titlefont": {"color": "#6b7280"},
547
+ "tickfont": {"color": "#6b7280"},
548
+ "gridcolor": "#f3f4f6",
549
+ "zerolinecolor": "#e5e7eb"
550
+ },
551
+ yaxis={
552
+ "title": "Angle (degrees)",
553
+ "titlefont": {"color": "#6b7280"},
554
+ "tickfont": {"color": "#6b7280"},
555
+ "gridcolor": "#f3f4f6",
556
+ "zerolinecolor": "#e5e7eb"
557
+ },
558
  shapes=shapes,
559
  hovermode="x unified",
560
+ height=220,
561
+ margin=dict(t=30, b=30, l=50, r=30),
562
  showlegend=False,
563
+ plot_bgcolor='white',
564
+ paper_bgcolor='white',
565
+ font={'family': "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif"}
566
  )
567
  }
568
 
 
674
  else:
675
  return no_update, no_update
676
  except Exception as e:
677
+ print(f"update_video_frames callback error: {e}")
678
  return no_update, no_update
679
 
680
  # ------------------ 启动应用 ------------------