Spaces:
Running
Running
Commit ·
3b57e9a
1
Parent(s): 65895ab
Add a Rerun viewer tab
Browse filesLogs the window to an .rrd and renders it with gradio_rerun: each camera's
GT/generated/diff as image streams and every action channel as a scalar
series, all on one shared frame timeline. This is the thing the composed mp4
could not do — the action plots were a static PNG that could not be scrubbed
against the video.
Frames are JPEG-compressed into the recording (~1.5 MB rather than ~16 MB for
a 3-camera window). The recording is built only when the tab is opened, and
is evicted along with its window like the composed mp4.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- README.md +11 -0
- app.py +142 -16
- requirements.txt +1 -0
README.md
CHANGED
|
@@ -29,6 +29,10 @@ URL. `DATASET_REPO` sets what is prefilled at startup.
|
|
| 29 |
frame when the dataset marks one.
|
| 30 |
- **Driving actions** — the action chunk fed to the model. A 14-D chunk is split into grippers /
|
| 31 |
left-arm / right-arm; any other width is plotted as raw channels.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
- **All windows** — sortable table of every window; click a row to load it.
|
| 33 |
|
| 34 |
Filter by task, gripper edge and arm; sort by critical-frame PSNR to jump to the hardest or
|
|
@@ -69,6 +73,13 @@ too large to fit on the Space.
|
|
| 69 |
`SESSION_TTL` and directories orphaned by an earlier process, so nothing accumulates if a tab
|
| 70 |
dies without a clean shutdown.
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
The sidebar shows live occupancy (`cached this session: n/N windows`).
|
| 73 |
|
| 74 |
## Configuration
|
|
|
|
| 29 |
frame when the dataset marks one.
|
| 30 |
- **Driving actions** — the action chunk fed to the model. A 14-D chunk is split into grippers /
|
| 31 |
left-arm / right-arm; any other width is plotted as raw channels.
|
| 32 |
+
- **Rerun** — the same window in a [Rerun](https://rerun.io) viewer: every camera and every
|
| 33 |
+
action channel on one shared frame timeline, so scrubbing moves all of them together. One row
|
| 34 |
+
per camera (GT | generated | optional diff) over a time-series plot of the actions, with the
|
| 35 |
+
critical frame marked. Built only when you open the tab, and only for the cameras selected.
|
| 36 |
- **All windows** — sortable table of every window; click a row to load it.
|
| 37 |
|
| 38 |
Filter by task, gripper edge and arm; sort by critical-frame PSNR to jump to the hardest or
|
|
|
|
| 73 |
`SESSION_TTL` and directories orphaned by an earlier process, so nothing accumulates if a tab
|
| 74 |
dies without a clean shutdown.
|
| 75 |
|
| 76 |
+
Rerun recordings follow the same rule: built on first view of the tab, cached beside the
|
| 77 |
+
composed mp4, and evicted with their window. Frames go into the recording as JPEG (quality 90) —
|
| 78 |
+
a raw recording of one 3-camera window is ~16 MB against ~1.5 MB compressed, which is the
|
| 79 |
+
difference between usable and not over a browser connection. The trade-off is that extreme zoom
|
| 80 |
+
in the Rerun viewer shows JPEG artifacts; the **Frame stepper** tab renders from the raw decoded
|
| 81 |
+
frames if you need a pixel-exact look.
|
| 82 |
+
|
| 83 |
The sidebar shows live occupancy (`cached this session: n/N windows`).
|
| 84 |
|
| 85 |
## Configuration
|
app.py
CHANGED
|
@@ -40,6 +40,9 @@ import cv2
|
|
| 40 |
import gradio as gr
|
| 41 |
import imageio_ffmpeg
|
| 42 |
import numpy as np
|
|
|
|
|
|
|
|
|
|
| 43 |
from huggingface_hub import HfApi, RepoFolder, hf_hub_download
|
| 44 |
from matplotlib.figure import Figure
|
| 45 |
|
|
@@ -63,6 +66,7 @@ NO_CRIT = -1
|
|
| 63 |
FONT = cv2.FONT_HERSHEY_SIMPLEX
|
| 64 |
HDR, FTR, SEP = 26, 26, 4
|
| 65 |
SCALE = 2 # source frames are small; upscale so overlays stay legible
|
|
|
|
| 66 |
|
| 67 |
GT_COLOR = (130, 225, 140)
|
| 68 |
GEN_COLOR = (120, 180, 255)
|
|
@@ -147,7 +151,7 @@ class Session:
|
|
| 147 |
self._pairs.pop(old, None)
|
| 148 |
for key in [k for k in self._composed if k[0] == old]:
|
| 149 |
self._composed.pop(key, None)
|
| 150 |
-
for f in self.render.glob(f"{old}_*
|
| 151 |
f.unlink(missing_ok=True)
|
| 152 |
|
| 153 |
def disk_windows(self) -> int:
|
|
@@ -416,11 +420,21 @@ def n_cameras(frames: np.ndarray) -> int:
|
|
| 416 |
|
| 417 |
|
| 418 |
def camera_choices(n: int) -> list[tuple[str, int]]:
|
| 419 |
-
|
| 420 |
-
choices = [(name, i) for i, name in enumerate(names)]
|
| 421 |
return ([("All cameras", ALL_CAMS)] + choices) if n > 1 else choices
|
| 422 |
|
| 423 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
def cam_slices(frames: np.ndarray, view: int) -> list[np.ndarray]:
|
| 425 |
n = n_cameras(frames)
|
| 426 |
h = frames.shape[1] // n
|
|
@@ -546,15 +560,21 @@ def render_video(session: Session, episode_id: str, view: int, show_diff: bool,
|
|
| 546 |
# --------------------------------------------------------------------------- actions plot
|
| 547 |
|
| 548 |
|
| 549 |
-
def
|
| 550 |
path = session.window_file(episode_id, "actions.json", optional=True)
|
| 551 |
if not path:
|
| 552 |
return None
|
| 553 |
try:
|
| 554 |
-
|
| 555 |
except Exception:
|
| 556 |
return None
|
| 557 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
t = np.arange(len(actions))
|
| 559 |
dim = actions.shape[1]
|
| 560 |
if dim == 14: # bimanual: [L arm 6, L grip, R arm 6, R grip]
|
|
@@ -585,6 +605,75 @@ def plot_actions(session: Session, episode_id: str, crit_idx: int, cursor: int |
|
|
| 585 |
return fig
|
| 586 |
|
| 587 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 588 |
# --------------------------------------------------------------------------- ui glue
|
| 589 |
|
| 590 |
|
|
@@ -710,6 +799,21 @@ def on_frame(sid, episode_id, view, show_diff, frame_idx):
|
|
| 710 |
return frames[i], plot_actions(session, episode_id, crit, i)
|
| 711 |
|
| 712 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 713 |
def step(ids, current, delta):
|
| 714 |
if not ids:
|
| 715 |
return None
|
|
@@ -732,6 +836,7 @@ with gr.Blocks(title="Rollout vs. ground truth", fill_width=True, delete_cache=(
|
|
| 732 |
|
| 733 |
sid_state = gr.State("", time_to_live=SESSION_TTL, delete_callback=release)
|
| 734 |
ids_state = gr.State([])
|
|
|
|
| 735 |
|
| 736 |
with gr.Row():
|
| 737 |
repo_box = gr.Textbox(
|
|
@@ -763,7 +868,7 @@ with gr.Blocks(title="Rollout vs. ground truth", fill_width=True, delete_cache=(
|
|
| 763 |
|
| 764 |
with gr.Column(scale=2):
|
| 765 |
with gr.Tabs():
|
| 766 |
-
with gr.Tab("Playback"):
|
| 767 |
video = gr.Video(
|
| 768 |
label="GT vs generated",
|
| 769 |
autoplay=True,
|
|
@@ -771,12 +876,18 @@ with gr.Blocks(title="Rollout vs. ground truth", fill_width=True, delete_cache=(
|
|
| 771 |
buttons=["download", "fullscreen"],
|
| 772 |
elem_classes="window-video",
|
| 773 |
)
|
| 774 |
-
with gr.Tab("Frame stepper"):
|
| 775 |
frame_idx = gr.Slider(0, 32, value=16, step=1, label="Frame in window")
|
| 776 |
still = gr.Image(label="Frame comparison", elem_classes="window-still")
|
| 777 |
-
with gr.Tab("Driving actions"):
|
| 778 |
actions_plot = gr.Plot(label="Driving action chunk")
|
| 779 |
-
with gr.Tab("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
table = gr.Dataframe(
|
| 781 |
headers=["episode", "task", "edge", "arm", "crit PSNR", "full PSNR"],
|
| 782 |
datatype=["str", "str", "str", "str", "number", "number"],
|
|
@@ -789,12 +900,19 @@ with gr.Blocks(title="Rollout vs. ground truth", fill_width=True, delete_cache=(
|
|
| 789 |
filter_outputs = [picker, table, ids_state]
|
| 790 |
view_inputs = [sid_state, picker, view, show_diff, fps, frame_idx]
|
| 791 |
view_outputs = [video, still, info, actions_plot, frame_idx, view]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 792 |
|
| 793 |
def wire_load(event):
|
| 794 |
-
return (
|
| 795 |
event(on_load, [repo_box, sid_state], [sid_state, status])
|
| 796 |
.then(on_filter, filter_inputs + [picker], filter_outputs)
|
| 797 |
-
.then
|
| 798 |
)
|
| 799 |
|
| 800 |
wire_load(load_btn.click)
|
|
@@ -802,13 +920,21 @@ with gr.Blocks(title="Rollout vs. ground truth", fill_width=True, delete_cache=(
|
|
| 802 |
wire_load(demo.load)
|
| 803 |
|
| 804 |
for control in (query, edge, arm, sort):
|
| 805 |
-
control.change(on_filter, filter_inputs + [picker], filter_outputs).then
|
| 806 |
-
on_select, view_inputs, view_outputs
|
| 807 |
-
)
|
| 808 |
|
| 809 |
-
picker.change
|
| 810 |
for control in (view, show_diff, fps):
|
| 811 |
-
control.change
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 812 |
frame_idx.change(on_frame, [sid_state, picker, view, show_diff, frame_idx], [still, actions_plot])
|
| 813 |
|
| 814 |
prev_btn.click(lambda i, c: step(i, c, -1), [ids_state, picker], picker)
|
|
|
|
| 40 |
import gradio as gr
|
| 41 |
import imageio_ffmpeg
|
| 42 |
import numpy as np
|
| 43 |
+
import rerun as rr
|
| 44 |
+
import rerun.blueprint as rrb
|
| 45 |
+
from gradio_rerun import Rerun
|
| 46 |
from huggingface_hub import HfApi, RepoFolder, hf_hub_download
|
| 47 |
from matplotlib.figure import Figure
|
| 48 |
|
|
|
|
| 66 |
FONT = cv2.FONT_HERSHEY_SIMPLEX
|
| 67 |
HDR, FTR, SEP = 26, 26, 4
|
| 68 |
SCALE = 2 # source frames are small; upscale so overlays stay legible
|
| 69 |
+
JPEG_QUALITY = 90 # raw frames make a ~16 MB recording; JPEG brings it to ~1.5 MB
|
| 70 |
|
| 71 |
GT_COLOR = (130, 225, 140)
|
| 72 |
GEN_COLOR = (120, 180, 255)
|
|
|
|
| 151 |
self._pairs.pop(old, None)
|
| 152 |
for key in [k for k in self._composed if k[0] == old]:
|
| 153 |
self._composed.pop(key, None)
|
| 154 |
+
for f in self.render.glob(f"{old}_*"):
|
| 155 |
f.unlink(missing_ok=True)
|
| 156 |
|
| 157 |
def disk_windows(self) -> int:
|
|
|
|
| 420 |
|
| 421 |
|
| 422 |
def camera_choices(n: int) -> list[tuple[str, int]]:
|
| 423 |
+
choices = [(name, i) for i, name in enumerate(camera_names(n))]
|
|
|
|
| 424 |
return ([("All cameras", ALL_CAMS)] + choices) if n > 1 else choices
|
| 425 |
|
| 426 |
|
| 427 |
+
def camera_names(n: int) -> list[str]:
|
| 428 |
+
return THREE_CAM_NAMES if n == 3 else [f"Camera {i + 1}" for i in range(n)]
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
def selected_cameras(n: int, view: int) -> list[tuple[int, str, str]]:
|
| 432 |
+
"""(band index, entity-path key, display label) for the cameras `view` selects."""
|
| 433 |
+
names = camera_names(n)
|
| 434 |
+
idxs = range(n) if view == ALL_CAMS or not 0 <= view < n else [view]
|
| 435 |
+
return [(i, re.sub(r"[^a-z0-9]+", "_", names[i].lower()), names[i]) for i in idxs]
|
| 436 |
+
|
| 437 |
+
|
| 438 |
def cam_slices(frames: np.ndarray, view: int) -> list[np.ndarray]:
|
| 439 |
n = n_cameras(frames)
|
| 440 |
h = frames.shape[1] // n
|
|
|
|
| 560 |
# --------------------------------------------------------------------------- actions plot
|
| 561 |
|
| 562 |
|
| 563 |
+
def load_actions(session: Session, episode_id: str) -> np.ndarray | None:
|
| 564 |
path = session.window_file(episode_id, "actions.json", optional=True)
|
| 565 |
if not path:
|
| 566 |
return None
|
| 567 |
try:
|
| 568 |
+
return np.atleast_2d(np.asarray(json.load(open(path)), dtype=float))
|
| 569 |
except Exception:
|
| 570 |
return None
|
| 571 |
|
| 572 |
+
|
| 573 |
+
def plot_actions(session: Session, episode_id: str, crit_idx: int, cursor: int | None) -> Figure | None:
|
| 574 |
+
actions = load_actions(session, episode_id)
|
| 575 |
+
if actions is None:
|
| 576 |
+
return None
|
| 577 |
+
|
| 578 |
t = np.arange(len(actions))
|
| 579 |
dim = actions.shape[1]
|
| 580 |
if dim == 14: # bimanual: [L arm 6, L grip, R arm 6, R grip]
|
|
|
|
| 605 |
return fig
|
| 606 |
|
| 607 |
|
| 608 |
+
# --------------------------------------------------------------------------- rerun recording
|
| 609 |
+
|
| 610 |
+
|
| 611 |
+
def action_channels(dim: int) -> list[tuple[str, int]]:
|
| 612 |
+
"""(entity path, column) for each action channel."""
|
| 613 |
+
if dim == 14: # bimanual: [L arm 6, L grip, R arm 6, R grip]
|
| 614 |
+
return (
|
| 615 |
+
[("actions/gripper/L", 6), ("actions/gripper/R", 13)]
|
| 616 |
+
+ [(f"actions/arm_L/j{i}", i) for i in range(6)]
|
| 617 |
+
+ [(f"actions/arm_R/j{i - 7}", i) for i in range(7, 13)]
|
| 618 |
+
)
|
| 619 |
+
return [(f"actions/a{i}", i) for i in range(dim)]
|
| 620 |
+
|
| 621 |
+
|
| 622 |
+
def blueprint_for(cams: list[tuple[int, str, str]], show_diff: bool, actions_origin: str | None):
|
| 623 |
+
"""One row per camera (GT | generated | diff), with the actions plot underneath."""
|
| 624 |
+
rows, shares = [], []
|
| 625 |
+
for _, key, label in cams:
|
| 626 |
+
views = [
|
| 627 |
+
rrb.Spatial2DView(origin=f"gt/{key}", name=f"GT · {label}"),
|
| 628 |
+
rrb.Spatial2DView(origin=f"generated/{key}", name=f"Generated · {label}"),
|
| 629 |
+
]
|
| 630 |
+
if show_diff:
|
| 631 |
+
views.append(rrb.Spatial2DView(origin=f"diff/{key}", name=f"|diff| · {label}"))
|
| 632 |
+
rows.append(rrb.Horizontal(*views))
|
| 633 |
+
shares.append(3)
|
| 634 |
+
if actions_origin:
|
| 635 |
+
rows.append(rrb.TimeSeriesView(origin=actions_origin, name="Driving actions"))
|
| 636 |
+
shares.append(2)
|
| 637 |
+
return rrb.Blueprint(rrb.Vertical(*rows, row_shares=shares), collapse_panels=True)
|
| 638 |
+
|
| 639 |
+
|
| 640 |
+
def build_rrd(session: Session, episode_id: str, view: int, show_diff: bool, crit: int) -> str:
|
| 641 |
+
"""Log the window to a .rrd: every stream on one shared frame timeline."""
|
| 642 |
+
path = session.render / f"{episode_id}_{view}_{int(show_diff)}.rrd"
|
| 643 |
+
if path.exists():
|
| 644 |
+
return str(path)
|
| 645 |
+
|
| 646 |
+
gt, gen = session.pair(episode_id)
|
| 647 |
+
n = n_cameras(gt)
|
| 648 |
+
band = gt.shape[1] // n
|
| 649 |
+
cams = selected_cameras(n, view)
|
| 650 |
+
actions = load_actions(session, episode_id)
|
| 651 |
+
channels = action_channels(actions.shape[1]) if actions is not None else []
|
| 652 |
+
actions_origin = None
|
| 653 |
+
if actions is not None:
|
| 654 |
+
actions_origin = "actions/gripper" if actions.shape[1] == 14 else "actions"
|
| 655 |
+
|
| 656 |
+
rec = rr.RecordingStream("wmviz", recording_id=path.stem)
|
| 657 |
+
for i in range(len(gt)):
|
| 658 |
+
rr.set_time("frame", sequence=i, recording=rec)
|
| 659 |
+
for c, key, _ in cams:
|
| 660 |
+
g = gt[i, c * band : (c + 1) * band]
|
| 661 |
+
p = gen[i, c * band : (c + 1) * band]
|
| 662 |
+
rr.log(f"gt/{key}", rr.Image(g).compress(jpeg_quality=JPEG_QUALITY), recording=rec)
|
| 663 |
+
rr.log(f"generated/{key}", rr.Image(p).compress(jpeg_quality=JPEG_QUALITY), recording=rec)
|
| 664 |
+
if show_diff:
|
| 665 |
+
d = diff_frames(g[None], p[None])[0]
|
| 666 |
+
rr.log(f"diff/{key}", rr.Image(d).compress(jpeg_quality=JPEG_QUALITY), recording=rec)
|
| 667 |
+
if actions is not None and i < len(actions):
|
| 668 |
+
for entity, col in channels:
|
| 669 |
+
rr.log(entity, rr.Scalars(float(actions[i, col])), recording=rec)
|
| 670 |
+
if i == crit:
|
| 671 |
+
rr.log("critical_frame", rr.TextLog("critical frame"), recording=rec)
|
| 672 |
+
|
| 673 |
+
rec.save(str(path), default_blueprint=blueprint_for(cams, show_diff, actions_origin))
|
| 674 |
+
return str(path)
|
| 675 |
+
|
| 676 |
+
|
| 677 |
# --------------------------------------------------------------------------- ui glue
|
| 678 |
|
| 679 |
|
|
|
|
| 799 |
return frames[i], plot_actions(session, episode_id, crit, i)
|
| 800 |
|
| 801 |
|
| 802 |
+
def on_rerun(sid, episode_id, view, show_diff):
|
| 803 |
+
session = get_session(sid)
|
| 804 |
+
if not session.rows or not episode_id:
|
| 805 |
+
return None
|
| 806 |
+
crit = critical_index(row_by_id(session, episode_id))
|
| 807 |
+
return build_rrd(session, episode_id, view, show_diff, crit)
|
| 808 |
+
|
| 809 |
+
|
| 810 |
+
def on_rerun_if_active(active, sid, episode_id, view, show_diff):
|
| 811 |
+
"""Only build a recording while the Rerun tab is the one on screen."""
|
| 812 |
+
if active != "Rerun":
|
| 813 |
+
return gr.skip()
|
| 814 |
+
return on_rerun(sid, episode_id, view, show_diff)
|
| 815 |
+
|
| 816 |
+
|
| 817 |
def step(ids, current, delta):
|
| 818 |
if not ids:
|
| 819 |
return None
|
|
|
|
| 836 |
|
| 837 |
sid_state = gr.State("", time_to_live=SESSION_TTL, delete_callback=release)
|
| 838 |
ids_state = gr.State([])
|
| 839 |
+
active_tab = gr.State("Playback")
|
| 840 |
|
| 841 |
with gr.Row():
|
| 842 |
repo_box = gr.Textbox(
|
|
|
|
| 868 |
|
| 869 |
with gr.Column(scale=2):
|
| 870 |
with gr.Tabs():
|
| 871 |
+
with gr.Tab("Playback") as tab_playback:
|
| 872 |
video = gr.Video(
|
| 873 |
label="GT vs generated",
|
| 874 |
autoplay=True,
|
|
|
|
| 876 |
buttons=["download", "fullscreen"],
|
| 877 |
elem_classes="window-video",
|
| 878 |
)
|
| 879 |
+
with gr.Tab("Frame stepper") as tab_stepper:
|
| 880 |
frame_idx = gr.Slider(0, 32, value=16, step=1, label="Frame in window")
|
| 881 |
still = gr.Image(label="Frame comparison", elem_classes="window-still")
|
| 882 |
+
with gr.Tab("Driving actions") as tab_actions:
|
| 883 |
actions_plot = gr.Plot(label="Driving action chunk")
|
| 884 |
+
with gr.Tab("Rerun") as tab_rerun:
|
| 885 |
+
gr.Markdown(
|
| 886 |
+
"Every stream on one shared timeline — scrub once and the cameras and "
|
| 887 |
+
"action plots move together. Frames are JPEG-compressed for transfer."
|
| 888 |
+
)
|
| 889 |
+
rerun_view = Rerun(height=720, streaming=False)
|
| 890 |
+
with gr.Tab("All windows") as tab_table:
|
| 891 |
table = gr.Dataframe(
|
| 892 |
headers=["episode", "task", "edge", "arm", "crit PSNR", "full PSNR"],
|
| 893 |
datatype=["str", "str", "str", "str", "number", "number"],
|
|
|
|
| 900 |
filter_outputs = [picker, table, ids_state]
|
| 901 |
view_inputs = [sid_state, picker, view, show_diff, fps, frame_idx]
|
| 902 |
view_outputs = [video, still, info, actions_plot, frame_idx, view]
|
| 903 |
+
rerun_inputs = [sid_state, picker, view, show_diff]
|
| 904 |
+
|
| 905 |
+
def wire_select(event):
|
| 906 |
+
"""Render the window, then refresh the recording only if that tab is open."""
|
| 907 |
+
return event(on_select, view_inputs, view_outputs).then(
|
| 908 |
+
on_rerun_if_active, [active_tab] + rerun_inputs, rerun_view
|
| 909 |
+
)
|
| 910 |
|
| 911 |
def wire_load(event):
|
| 912 |
+
return wire_select(
|
| 913 |
event(on_load, [repo_box, sid_state], [sid_state, status])
|
| 914 |
.then(on_filter, filter_inputs + [picker], filter_outputs)
|
| 915 |
+
.then
|
| 916 |
)
|
| 917 |
|
| 918 |
wire_load(load_btn.click)
|
|
|
|
| 920 |
wire_load(demo.load)
|
| 921 |
|
| 922 |
for control in (query, edge, arm, sort):
|
| 923 |
+
wire_select(control.change(on_filter, filter_inputs + [picker], filter_outputs).then)
|
|
|
|
|
|
|
| 924 |
|
| 925 |
+
wire_select(picker.change)
|
| 926 |
for control in (view, show_diff, fps):
|
| 927 |
+
wire_select(control.change)
|
| 928 |
+
|
| 929 |
+
for tab, name in (
|
| 930 |
+
(tab_playback, "Playback"),
|
| 931 |
+
(tab_stepper, "Frame stepper"),
|
| 932 |
+
(tab_actions, "Driving actions"),
|
| 933 |
+
(tab_table, "All windows"),
|
| 934 |
+
):
|
| 935 |
+
tab.select(lambda name=name: name, None, active_tab)
|
| 936 |
+
# opening the Rerun tab is what triggers the first build for a window
|
| 937 |
+
tab_rerun.select(lambda: "Rerun", None, active_tab).then(on_rerun, rerun_inputs, rerun_view)
|
| 938 |
frame_idx.change(on_frame, [sid_state, picker, view, show_diff, frame_idx], [still, actions_plot])
|
| 939 |
|
| 940 |
prev_btn.click(lambda i, c: step(i, c, -1), [ids_state, picker], picker)
|
requirements.txt
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
gradio==6.22.0
|
|
|
|
| 2 |
huggingface_hub>=0.28
|
| 3 |
numpy
|
| 4 |
opencv-python-headless
|
|
|
|
| 1 |
gradio==6.22.0
|
| 2 |
+
gradio_rerun>=0.35.0
|
| 3 |
huggingface_hub>=0.28
|
| 4 |
numpy
|
| 5 |
opencv-python-headless
|