multimodalart HF Staff commited on
Commit
b092ec6
·
verified ·
1 Parent(s): fbaaf1e

Add per-group Reset buttons and grouped vertical slider layout

Browse files
Files changed (1) hide show
  1. app.py +126 -31
app.py CHANGED
@@ -355,6 +355,44 @@ VIEWER_JS_ON_LOAD = (
355
  CSS = """
356
  #col-container { max-width: 1200px; margin: 0 auto; }
357
  .dark .gradio-container { color: var(--body-text-color); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  """
359
 
360
  INTRO = """
@@ -371,6 +409,33 @@ def _slider(label, minimum, maximum, value=0.0, step=None):
371
  return gr.Slider(**kw)
372
 
373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  with gr.Blocks(title="GNM Head") as demo:
375
  with gr.Column(elem_id="col-container"):
376
  gr.Markdown(INTRO)
@@ -389,14 +454,21 @@ with gr.Blocks(title="GNM Head") as demo:
389
  "First 10 components of the linear **identity** "
390
  "basis (head shape). Typical range −3 … +3."
391
  )
392
- with gr.Row():
 
 
393
  for k in range(NUM_IDENTITY):
394
- identity_sliders.append(
395
  _slider(
396
  f"Head shape {k + 1}",
397
  -3.0, 3.0, 0.0, 0.05,
398
  )
399
  )
 
 
 
 
 
400
 
401
  with gr.Tab("Expression"):
402
  gr.Markdown(
@@ -404,9 +476,9 @@ with gr.Blocks(title="GNM Head") as demo:
404
  "per region. Typical range −3 … +3."
405
  )
406
  for group_name, group_idx in EXPRESSION_GROUPS.items():
407
- with gr.Row():
408
- gr.Markdown(f"**{group_name}**")
409
- with gr.Row():
410
  for j in range(len(group_idx)):
411
  # Single-component groups (e.g. pupil
412
  # dilation) don't need a component number.
@@ -415,11 +487,14 @@ with gr.Blocks(title="GNM Head") as demo:
415
  if len(group_idx) == 1
416
  else f"{group_name} {j + 1}"
417
  )
418
- expression_sliders.append(
419
- _slider(
420
- label, -3.0, 3.0, 0.0, 0.05,
421
- )
422
  )
 
 
 
 
 
423
 
424
  with gr.Tab("Pose"):
425
  gr.Markdown(
@@ -428,29 +503,36 @@ with gr.Blocks(title="GNM Head") as demo:
428
  "vergence control)."
429
  )
430
  for joint, limits in POSE_LIMITS.items():
431
- with gr.Row():
432
- gr.Markdown(f"**{joint.capitalize()}**")
433
- with gr.Row():
434
  for name, limit in limits.items():
435
- pose_sliders.append(
436
- _slider(
437
- name, -limit, limit, 0, 1
438
- )
439
  )
 
 
 
 
 
440
 
441
  with gr.Tab("Translation"):
442
  gr.Markdown("Global **translation** of the whole head (meters).")
443
- with gr.Row():
 
 
444
  for d in [
445
  "X (left/right)",
446
  "Y (up/down)",
447
  "Z (forward/back)",
448
  ]:
449
- translation_sliders.append(
450
  _slider(d, -0.2, 0.2, 0.0, 0.01)
451
  )
 
452
 
453
- reset_btn = gr.Button("↺ Reset all sliders", variant="secondary")
 
 
454
 
455
  # --- Viewer ---------------------------------------------------
456
  # The custom Three.js viewer IS the output component. Its value is
@@ -486,13 +568,27 @@ with gr.Blocks(title="GNM Head") as demo:
486
  show_progress="hidden",
487
  )
488
 
489
- def _reset():
490
- return [0.0] * len(all_sliders)
491
-
492
- reset_btn.click(_reset, inputs=None, outputs=all_sliders).then(
493
- fn=positions_json, inputs=all_sliders, outputs=viewer,
494
- show_progress="hidden",
495
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
 
497
  # Presets: full rows (every slider gets a concrete value, no None).
498
  # Offsets into the flat slider vector:
@@ -514,18 +610,17 @@ with gr.Blocks(title="GNM Head") as demo:
514
 
515
  # Expression slider slots (flat order): left eye 0-2, right eye 3-5,
516
  # mouth 6-12, tongue 13-16 (13=tongue_mean, 14-16=tongue_000..002),
517
- # pupil dilation 17. Expression sliders are clamped to [-3, 3].
518
- # Measured directions on this model:
519
  # mouth slot 6 negative -> jaw drops / mouth opens
520
  # tongue slot 14 positive + tongue_mean slot 13 negative -> tongue out
521
  # eye slot 0 (left) / slot 3 (right): positive -> eye wide, negative -> closed
522
  presets = [
523
  # 1) Open mouth with the tongue sticking out.
524
- _preset(expression={6: -3.0, 13: -3.0, 14: 3.0}),
525
  # 2) Surprised: eyes wide open + mouth dropped open.
526
- _preset(expression={0: 3.0, 3: 3.0, 6: -3.0}),
527
  # 3) Wink: left eye closed, right eye left neutral.
528
- _preset(expression={0: -3.0}),
529
  ]
530
 
531
  gr.Examples(
 
355
  CSS = """
356
  #col-container { max-width: 1200px; margin: 0 auto; }
357
  .dark .gradio-container { color: var(--body-text-color); }
358
+
359
+ /* Compact, vertically-stacked grouped sections. Each feature/parameter group
360
+ (Head Shape, Left Eye, Mouth, Neck, Gaze, Translation, ...) is rendered as
361
+ its own bordered section with a header row (group label + per-group Reset)
362
+ and its sliders stacked vertically underneath. */
363
+ .gnm-group {
364
+ border: 1px solid var(--border-color-primary);
365
+ border-radius: 8px;
366
+ padding: 8px 10px 4px 10px;
367
+ margin-bottom: 10px;
368
+ background: var(--block-background-fill);
369
+ }
370
+ .gnm-group-header {
371
+ display: flex;
372
+ align-items: center;
373
+ justify-content: space-between;
374
+ margin-bottom: 4px;
375
+ gap: 8px;
376
+ }
377
+ .gnm-group-title {
378
+ font-weight: 600;
379
+ font-size: 0.95rem;
380
+ margin: 0;
381
+ }
382
+ /* Tighten vertical spacing between stacked sliders inside a group. */
383
+ .gnm-group .gnm-sliders .gr-slider,
384
+ .gnm-group .gnm-sliders > div {
385
+ margin-bottom: 2px;
386
+ }
387
+ /* Small, unobtrusive per-group reset button. */
388
+ .gnm-reset-btn {
389
+ flex: 0 0 auto !important;
390
+ min-width: 0 !important;
391
+ width: auto !important;
392
+ padding: 2px 10px !important;
393
+ font-size: 0.8rem !important;
394
+ line-height: 1.4 !important;
395
+ }
396
  """
397
 
398
  INTRO = """
 
409
  return gr.Slider(**kw)
410
 
411
 
412
+ # Collects (reset_button, [sliders]) pairs so each group's reset is wired to
413
+ # only its own sliders after the full slider list exists.
414
+ GROUP_RESETS = []
415
+
416
+
417
+ def _group_section(title, build_sliders):
418
+ """Render one feature/parameter group as a compact, bordered section.
419
+
420
+ The section has a header (group label + a per-group ↺ Reset button) and its
421
+ sliders stacked vertically underneath. `build_sliders` is a zero-arg
422
+ callable that creates and returns the group's list of sliders (in order).
423
+ The per-group reset button is registered in GROUP_RESETS and wired later to
424
+ reset only this group's sliders back to their defaults.
425
+ """
426
+ with gr.Column(elem_classes="gnm-group"):
427
+ with gr.Row(elem_classes="gnm-group-header"):
428
+ gr.HTML(f'<span class="gnm-group-title">{title}</span>')
429
+ reset = gr.Button(
430
+ "↺ Reset", variant="secondary", size="sm",
431
+ elem_classes="gnm-reset-btn", scale=0, min_width=0,
432
+ )
433
+ with gr.Column(elem_classes="gnm-sliders"):
434
+ group_sliders = build_sliders()
435
+ GROUP_RESETS.append((reset, group_sliders))
436
+ return group_sliders
437
+
438
+
439
  with gr.Blocks(title="GNM Head") as demo:
440
  with gr.Column(elem_id="col-container"):
441
  gr.Markdown(INTRO)
 
454
  "First 10 components of the linear **identity** "
455
  "basis (head shape). Typical range −3 … +3."
456
  )
457
+
458
+ def _build_head_shape():
459
+ sliders = []
460
  for k in range(NUM_IDENTITY):
461
+ sliders.append(
462
  _slider(
463
  f"Head shape {k + 1}",
464
  -3.0, 3.0, 0.0, 0.05,
465
  )
466
  )
467
+ return sliders
468
+
469
+ identity_sliders = _group_section(
470
+ "Head Shape", _build_head_shape
471
+ )
472
 
473
  with gr.Tab("Expression"):
474
  gr.Markdown(
 
476
  "per region. Typical range −3 … +3."
477
  )
478
  for group_name, group_idx in EXPRESSION_GROUPS.items():
479
+ def _build_expr(group_name=group_name,
480
+ group_idx=group_idx):
481
+ sliders = []
482
  for j in range(len(group_idx)):
483
  # Single-component groups (e.g. pupil
484
  # dilation) don't need a component number.
 
487
  if len(group_idx) == 1
488
  else f"{group_name} {j + 1}"
489
  )
490
+ sliders.append(
491
+ _slider(label, -3.0, 3.0, 0.0, 0.05)
 
 
492
  )
493
+ return sliders
494
+
495
+ expression_sliders += _group_section(
496
+ group_name, _build_expr
497
+ )
498
 
499
  with gr.Tab("Pose"):
500
  gr.Markdown(
 
503
  "vergence control)."
504
  )
505
  for joint, limits in POSE_LIMITS.items():
506
+ def _build_pose(limits=limits):
507
+ sliders = []
 
508
  for name, limit in limits.items():
509
+ sliders.append(
510
+ _slider(name, -limit, limit, 0, 1)
 
 
511
  )
512
+ return sliders
513
+
514
+ pose_sliders += _group_section(
515
+ joint.capitalize(), _build_pose
516
+ )
517
 
518
  with gr.Tab("Translation"):
519
  gr.Markdown("Global **translation** of the whole head (meters).")
520
+
521
+ def _build_translation():
522
+ sliders = []
523
  for d in [
524
  "X (left/right)",
525
  "Y (up/down)",
526
  "Z (forward/back)",
527
  ]:
528
+ sliders.append(
529
  _slider(d, -0.2, 0.2, 0.0, 0.01)
530
  )
531
+ return sliders
532
 
533
+ translation_sliders = _group_section(
534
+ "Translation", _build_translation
535
+ )
536
 
537
  # --- Viewer ---------------------------------------------------
538
  # The custom Three.js viewer IS the output component. Its value is
 
568
  show_progress="hidden",
569
  )
570
 
571
+ # Per-group Reset: each group has its own ↺ Reset button that resets
572
+ # ONLY that group's sliders back to their defaults, then recomputes the
573
+ # mesh from the full (partially-reset) slider vector. There is no
574
+ # single global reset.
575
+ def _make_group_reset(sliders):
576
+ defaults = [s.value for s in sliders]
577
+
578
+ def _reset_group():
579
+ return defaults if len(defaults) != 1 else defaults[0]
580
+
581
+ return _reset_group
582
+
583
+ for reset_btn, group_sliders in GROUP_RESETS:
584
+ reset_btn.click(
585
+ _make_group_reset(group_sliders),
586
+ inputs=None,
587
+ outputs=group_sliders,
588
+ ).then(
589
+ fn=positions_json, inputs=all_sliders, outputs=viewer,
590
+ show_progress="hidden",
591
+ )
592
 
593
  # Presets: full rows (every slider gets a concrete value, no None).
594
  # Offsets into the flat slider vector:
 
610
 
611
  # Expression slider slots (flat order): left eye 0-2, right eye 3-5,
612
  # mouth 6-12, tongue 13-16 (13=tongue_mean, 14-16=tongue_000..002),
613
+ # pupil dilation 17. Measured directions on this model:
 
614
  # mouth slot 6 negative -> jaw drops / mouth opens
615
  # tongue slot 14 positive + tongue_mean slot 13 negative -> tongue out
616
  # eye slot 0 (left) / slot 3 (right): positive -> eye wide, negative -> closed
617
  presets = [
618
  # 1) Open mouth with the tongue sticking out.
619
+ _preset(expression={6: -5.0, 13: -3.0, 14: 3.0}),
620
  # 2) Surprised: eyes wide open + mouth dropped open.
621
+ _preset(expression={0: 4.0, 3: 4.0, 6: -3.0}),
622
  # 3) Wink: left eye closed, right eye left neutral.
623
+ _preset(expression={0: -5.0}),
624
  ]
625
 
626
  gr.Examples(