Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files
app.py
CHANGED
|
@@ -100,9 +100,7 @@ DAVIS_PALETTE = np.array([
|
|
| 100 |
DEFAULT_FPS = 24
|
| 101 |
DEFAULT_ALPHA = 0.55
|
| 102 |
DEFAULT_CRF = 18
|
| 103 |
-
|
| 104 |
-
PAGE_SIZE = 9 # videos per page in Multi-Video tab
|
| 105 |
-
THUMB_W, THUMB_H = 320, 200 # thumbnail dimensions for Gallery
|
| 106 |
|
| 107 |
# ── Dataset download ───────────────────────────────────────────────────────────
|
| 108 |
|
|
@@ -514,8 +512,6 @@ def build_ui():
|
|
| 514 |
n_2017 = int(DF["in_2017"].sum())
|
| 515 |
_first = ALL_SEQUENCES[0]
|
| 516 |
_first_n = len(_get_frame_paths(_first))
|
| 517 |
-
total_pages = (len(ALL_SEQUENCES) + PAGE_SIZE - 1) // PAGE_SIZE
|
| 518 |
-
|
| 519 |
with gr.Blocks(title="DAVIS Dataset Explorer") as demo:
|
| 520 |
|
| 521 |
gr.Markdown(
|
|
@@ -648,14 +644,36 @@ def build_ui():
|
|
| 648 |
btn_play.click(get_video, [seq_dd, v_ov, v_a, v_fps], [video_out, v_status])
|
| 649 |
|
| 650 |
# ──────────────────────────────────────────────────────────────
|
| 651 |
-
# Tab 3 · Gallery (
|
| 652 |
# ──────────────────────────────────────────────────────────────
|
| 653 |
with gr.TabItem("🖼 Gallery"):
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
"to
|
|
|
|
| 658 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 659 |
with gr.Row():
|
| 660 |
g_year = gr.Dropdown(["All years","2016 only","2017 only"],
|
| 661 |
value="All years", label="Year", scale=1)
|
|
@@ -666,345 +684,146 @@ def build_ui():
|
|
| 666 |
g_srch = gr.Textbox(placeholder="Search…", label="Search", scale=2)
|
| 667 |
with gr.Row():
|
| 668 |
g_fmin = gr.Slider(int(DF["frames"].min()), int(DF["frames"].max()),
|
| 669 |
-
int(DF["frames"].min()), step=1,
|
|
|
|
| 670 |
g_fmax = gr.Slider(int(DF["frames"].min()), int(DF["frames"].max()),
|
| 671 |
-
int(DF["frames"].max()), step=1,
|
|
|
|
| 672 |
with gr.Row():
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 676 |
|
| 677 |
-
# Gallery
|
| 678 |
gallery = gr.Gallery(
|
| 679 |
value=_ALL_THUMBS,
|
| 680 |
label="Sequences",
|
| 681 |
-
columns=
|
| 682 |
rows=None,
|
| 683 |
height="auto",
|
| 684 |
allow_preview=False,
|
| 685 |
show_label=False,
|
|
|
|
| 686 |
)
|
| 687 |
|
| 688 |
-
#
|
| 689 |
-
g_seq_state
|
|
|
|
| 690 |
|
| 691 |
-
|
| 692 |
-
with gr.Row():
|
| 693 |
-
g_info_md = gr.Markdown("*Click a thumbnail to play.*")
|
| 694 |
-
with gr.Row():
|
| 695 |
-
g_fps = gr.Slider(1, 30, DEFAULT_FPS, step=1,
|
| 696 |
-
label="FPS", scale=2)
|
| 697 |
-
g_vid_ov = gr.Checkbox(value=True, label="Burn overlay", scale=1)
|
| 698 |
-
g_vid_a = gr.Slider(0.1, 1.0, DEFAULT_ALPHA, step=0.05,
|
| 699 |
-
label="Opacity", scale=2)
|
| 700 |
-
g_btn_play = gr.Button("▶ Play selected", variant="primary", scale=1)
|
| 701 |
-
|
| 702 |
-
with gr.Column(scale=5):
|
| 703 |
-
g_vid_status = gr.Markdown("")
|
| 704 |
-
g_video = gr.Video(label="Playback", height=400, autoplay=True)
|
| 705 |
-
g_selected = gr.State("")
|
| 706 |
-
|
| 707 |
-
# Filter → rebuild gallery
|
| 708 |
g_f_inputs = [g_year, g_split, g_obj, g_fmin, g_fmax, g_srch]
|
| 709 |
|
| 710 |
def _on_g_filter(*args):
|
| 711 |
-
ov
|
| 712 |
-
fargs = args[:-1]
|
| 713 |
fdf = filter_df(*fargs)
|
| 714 |
seqs = fdf["sequence"].tolist()
|
| 715 |
items = build_gallery_items(seqs, overlay=ov)
|
| 716 |
-
return items, seqs,
|
|
|
|
| 717 |
|
| 718 |
-
for inp in g_f_inputs + [
|
| 719 |
-
inp.change(_on_g_filter, g_f_inputs + [
|
| 720 |
[gallery, g_seq_state, g_count_md])
|
| 721 |
|
| 722 |
-
#
|
| 723 |
-
def
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 730 |
|
| 731 |
gallery.select(
|
| 732 |
-
|
| 733 |
-
inputs=[g_seq_state, g_vid_ov, g_vid_a, g_fps],
|
| 734 |
-
outputs=[
|
| 735 |
-
|
| 736 |
-
g_btn_play.click(
|
| 737 |
-
lambda seq, ov, a, fps: get_video(seq, ov, a, fps),
|
| 738 |
-
inputs=[g_selected, g_vid_ov, g_vid_a, g_fps],
|
| 739 |
-
outputs=[g_video, g_vid_status],
|
| 740 |
)
|
| 741 |
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
with gr.TabItem("📺 Multi-Video"):
|
| 746 |
-
gr.Markdown(
|
| 747 |
-
f"Watch **{PAGE_SIZE} sequences at once** in a 3×3 grid. "
|
| 748 |
-
f"Page through all {len(ALL_SEQUENCES)} sequences. "
|
| 749 |
-
"Videos are served from the permanent MP4 cache."
|
| 750 |
-
)
|
| 751 |
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
gr.Markdown(
|
| 758 |
-
"All 90 sequences are being encoded as MP4s in the background "
|
| 759 |
-
"(raw + overlay variant each). The grid will unlock automatically "
|
| 760 |
-
"when encoding finishes. You can watch other tabs in the meantime."
|
| 761 |
)
|
| 762 |
-
mv_refresh_btn = gr.Button("↻ Refresh status", size="sm")
|
| 763 |
-
|
| 764 |
-
# ── Main grid (hidden until cache ready) ──────────────────
|
| 765 |
-
with gr.Column(visible=_initially_done) as mv_grid_col:
|
| 766 |
-
with gr.Row():
|
| 767 |
-
mv_year = gr.Dropdown(["All years","2016 only","2017 only"],
|
| 768 |
-
value="All years", label="Year", scale=1)
|
| 769 |
-
mv_split = gr.Dropdown(["All splits","Train only","Val only"],
|
| 770 |
-
value="All splits", label="Split", scale=1)
|
| 771 |
-
mv_obj = gr.Dropdown(["Any # objects","1 object","2 objects","3+ objects"],
|
| 772 |
-
value="Any # objects", label="Objects", scale=1)
|
| 773 |
-
mv_srch = gr.Textbox(placeholder="Search…", label="Search", scale=2)
|
| 774 |
-
with gr.Row():
|
| 775 |
-
mv_fps = gr.Slider(1, 30, DEFAULT_FPS, step=1, label="FPS", scale=2)
|
| 776 |
-
mv_ov = gr.Checkbox(value=True, label="Burn overlay", scale=1)
|
| 777 |
-
mv_a = gr.Slider(0.1, 1.0, DEFAULT_ALPHA, step=0.05,
|
| 778 |
-
label="Opacity", scale=2)
|
| 779 |
-
mv_load = gr.Button("▶ Load Page", variant="primary", scale=1)
|
| 780 |
-
|
| 781 |
-
with gr.Row():
|
| 782 |
-
mv_prev = gr.Button("◀ Prev", scale=1)
|
| 783 |
-
with gr.Column(scale=3):
|
| 784 |
-
mv_page_lbl = gr.Markdown(f"**Page 1 / {total_pages}**")
|
| 785 |
-
mv_next = gr.Button("Next ▶", scale=1)
|
| 786 |
-
|
| 787 |
-
mv_status = gr.Markdown("")
|
| 788 |
-
|
| 789 |
-
# 9 fixed video slots, 3 rows × 3 cols
|
| 790 |
-
mv_vids = []
|
| 791 |
-
mv_lbls = []
|
| 792 |
-
for row_i in range(3):
|
| 793 |
-
with gr.Row():
|
| 794 |
-
for col_i in range(3):
|
| 795 |
-
with gr.Column():
|
| 796 |
-
lbl = gr.Markdown("—")
|
| 797 |
-
vid = gr.Video(height=260, autoplay=True, label="")
|
| 798 |
-
mv_lbls.append(lbl)
|
| 799 |
-
mv_vids.append(vid)
|
| 800 |
-
|
| 801 |
-
# State: list of sequences currently matching filter, page index
|
| 802 |
-
mv_seq_state = gr.State(ALL_SEQUENCES.copy())
|
| 803 |
-
mv_page_state = gr.State(0)
|
| 804 |
-
|
| 805 |
-
def _mv_filter(*args):
|
| 806 |
-
fdf = filter_df(*args)
|
| 807 |
-
seqs = fdf["sequence"].tolist()
|
| 808 |
-
tp = max(1, (len(seqs) + PAGE_SIZE - 1) // PAGE_SIZE)
|
| 809 |
-
return seqs, 0, f"**Page 1 / {tp}**"
|
| 810 |
-
|
| 811 |
-
mv_f_inputs = [mv_year, mv_split, mv_obj,
|
| 812 |
-
gr.Slider(int(DF["frames"].min()), int(DF["frames"].max()),
|
| 813 |
-
int(DF["frames"].min()), step=1, label=""),
|
| 814 |
-
gr.Slider(int(DF["frames"].min()), int(DF["frames"].max()),
|
| 815 |
-
int(DF["frames"].max()), step=1, label=""),
|
| 816 |
-
mv_srch]
|
| 817 |
-
|
| 818 |
-
# Simpler: just use the three dropdowns + search for multi-video filter
|
| 819 |
-
def _mv_filter_simple(yr, sp, ob, sr):
|
| 820 |
-
fdf = filter_df(yr, sp, ob,
|
| 821 |
-
int(DF["frames"].min()), int(DF["frames"].max()), sr)
|
| 822 |
-
seqs = fdf["sequence"].tolist()
|
| 823 |
-
tp = max(1, (len(seqs) + PAGE_SIZE - 1) // PAGE_SIZE)
|
| 824 |
-
return seqs, 0, f"**Page 1 / {tp}**"
|
| 825 |
-
|
| 826 |
-
mv_simple_f = [mv_year, mv_split, mv_obj, mv_srch]
|
| 827 |
-
for inp in mv_simple_f:
|
| 828 |
-
inp.change(_mv_filter_simple, mv_simple_f,
|
| 829 |
-
[mv_seq_state, mv_page_state, mv_page_lbl])
|
| 830 |
-
|
| 831 |
-
def _load_page(seqs, page, ov, a, fps):
|
| 832 |
-
# Block if pre-cache not yet finished
|
| 833 |
-
if not _is_cache_complete():
|
| 834 |
-
with _cache_lock:
|
| 835 |
-
done = sum(1 for v in _cache_progress.values() if v == "done")
|
| 836 |
-
total = len(ALL_SEQUENCES)
|
| 837 |
-
pct = int(done / total * 100) if total else 0
|
| 838 |
-
bar = "█" * (pct // 5) + "░" * (20 - pct // 5)
|
| 839 |
-
status = (f"⏳ `[{bar}]` {done}/{total} sequences cached ({pct}%) — "
|
| 840 |
-
"please wait for caching to finish then click Load Page again.")
|
| 841 |
-
out = []
|
| 842 |
-
for _ in range(PAGE_SIZE):
|
| 843 |
-
out.append("—")
|
| 844 |
-
out.append(None)
|
| 845 |
-
out.append(status)
|
| 846 |
-
out.append(f"**Page {page + 1} / ?**")
|
| 847 |
-
return out
|
| 848 |
-
|
| 849 |
-
start = page * PAGE_SIZE
|
| 850 |
-
chunk = seqs[start: start + PAGE_SIZE]
|
| 851 |
-
tp = max(1, (len(seqs) + PAGE_SIZE - 1) // PAGE_SIZE)
|
| 852 |
-
pg_lbl = f"**Page {page + 1} / {tp}**"
|
| 853 |
-
|
| 854 |
-
# Sequential — encode_sequence() returns in <1 ms when already cached
|
| 855 |
-
res, lbs = [], []
|
| 856 |
-
for s in chunk:
|
| 857 |
-
try:
|
| 858 |
-
p, _ = get_video(s, ov, a, fps)
|
| 859 |
-
res.append(str(p) if p else None)
|
| 860 |
-
except Exception:
|
| 861 |
-
res.append(None)
|
| 862 |
-
lbs.append(s)
|
| 863 |
-
|
| 864 |
-
while len(res) < PAGE_SIZE: # pad
|
| 865 |
-
res.append(None); lbs.append("—")
|
| 866 |
-
|
| 867 |
-
n_loaded = sum(1 for r in res if r)
|
| 868 |
-
status = f"✅ {n_loaded}/{len(chunk)} videos loaded (page {page+1}/{tp})"
|
| 869 |
-
|
| 870 |
-
out = []
|
| 871 |
-
for lb, r in zip(lbs, res):
|
| 872 |
-
out.append(f"**{lb}**" if lb and lb != "—" else "—")
|
| 873 |
-
out.append(r)
|
| 874 |
-
out.append(status)
|
| 875 |
-
out.append(pg_lbl)
|
| 876 |
-
return out
|
| 877 |
-
|
| 878 |
-
mv_page_outputs = []
|
| 879 |
-
for l, v in zip(mv_lbls, mv_vids):
|
| 880 |
-
mv_page_outputs.append(l)
|
| 881 |
-
mv_page_outputs.append(v)
|
| 882 |
-
mv_page_outputs.append(mv_status)
|
| 883 |
-
mv_page_outputs.append(mv_page_lbl)
|
| 884 |
-
|
| 885 |
-
mv_load.click(
|
| 886 |
-
_load_page,
|
| 887 |
-
inputs=[mv_seq_state, mv_page_state, mv_ov, mv_a, mv_fps],
|
| 888 |
-
outputs=mv_page_outputs,
|
| 889 |
-
)
|
| 890 |
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
tp = max(1, (len(seqs) + PAGE_SIZE - 1) // PAGE_SIZE)
|
| 894 |
-
return new_p, f"**Page {new_p+1} / {tp}**"
|
| 895 |
-
|
| 896 |
-
def _next(seqs, page):
|
| 897 |
-
tp = max(1, (len(seqs) + PAGE_SIZE - 1) // PAGE_SIZE)
|
| 898 |
-
new_p = min(tp - 1, page + 1)
|
| 899 |
-
return new_p, f"**Page {new_p+1} / {tp}**"
|
| 900 |
-
|
| 901 |
-
mv_prev.click(_prev, [mv_seq_state, mv_page_state],
|
| 902 |
-
[mv_page_state, mv_page_lbl])
|
| 903 |
-
mv_next.click(_next, [mv_seq_state, mv_page_state],
|
| 904 |
-
[mv_page_state, mv_page_lbl])
|
| 905 |
-
|
| 906 |
-
# ── Cache progress wiring (timer + manual refresh) ────────
|
| 907 |
-
def _mv_cache_tick():
|
| 908 |
-
"""Auto-refresh: hides the wait panel and shows the grid when done."""
|
| 909 |
-
done_now = _is_cache_complete()
|
| 910 |
-
status = cache_status_md()
|
| 911 |
return (
|
| 912 |
-
|
| 913 |
-
gr.update(visible=
|
| 914 |
-
gr.update(visible=
|
| 915 |
-
gr.update(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 916 |
)
|
| 917 |
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
mv_timer = gr.Timer(value=4, active=not _initially_done)
|
| 923 |
-
mv_timer.tick(_mv_cache_tick,
|
| 924 |
-
outputs=[mv_wait_status, mv_wait_col,
|
| 925 |
-
mv_grid_col, mv_timer])
|
| 926 |
-
|
| 927 |
-
# ──────────────────────────────────────────────────────────────
|
| 928 |
-
# Tab 5 · Compare (up to 6 side-by-side)
|
| 929 |
-
# ──────────────────────────────────────────────────────────────
|
| 930 |
-
with gr.TabItem("⚖️ Compare"):
|
| 931 |
-
gr.Markdown(
|
| 932 |
-
"Pick up to **6 sequences**, set FPS/overlay, "
|
| 933 |
-
"then **Load All** — encoded in parallel and cached."
|
| 934 |
)
|
| 935 |
-
with gr.Row():
|
| 936 |
-
cmp_fps = gr.Slider(1, 30, DEFAULT_FPS, step=1, label="FPS", scale=2)
|
| 937 |
-
cmp_ov = gr.Checkbox(value=True, label="Burn overlay", scale=1)
|
| 938 |
-
cmp_a = gr.Slider(0.1, 1.0, DEFAULT_ALPHA, step=0.05,
|
| 939 |
-
label="Opacity", scale=2)
|
| 940 |
-
cmp_btn = gr.Button("▶ Load All", variant="primary", scale=1)
|
| 941 |
-
|
| 942 |
-
cmp_dds = []
|
| 943 |
-
cmp_vids = []
|
| 944 |
-
cmp_lbls = []
|
| 945 |
-
default_seqs = (ALL_SEQUENCES + [None] * MAX_COMPARE)[:MAX_COMPARE]
|
| 946 |
-
|
| 947 |
-
for row_i in range(2):
|
| 948 |
-
with gr.Row():
|
| 949 |
-
for col_i in range(3):
|
| 950 |
-
si = row_i * 3 + col_i
|
| 951 |
-
with gr.Column():
|
| 952 |
-
dd = gr.Dropdown([""] + ALL_SEQUENCES,
|
| 953 |
-
value=default_seqs[si] or "",
|
| 954 |
-
label=f"Slot {si+1}")
|
| 955 |
-
vid = gr.Video(height=270, autoplay=True, label="")
|
| 956 |
-
lbl = gr.Markdown(
|
| 957 |
-
f"*{default_seqs[si]}*" if default_seqs[si] else "*empty*")
|
| 958 |
-
cmp_dds.append(dd)
|
| 959 |
-
cmp_vids.append(vid)
|
| 960 |
-
cmp_lbls.append(lbl)
|
| 961 |
-
|
| 962 |
-
cmp_status = gr.Markdown("")
|
| 963 |
-
|
| 964 |
-
cmp_outputs = []
|
| 965 |
-
for v, l in zip(cmp_vids, cmp_lbls):
|
| 966 |
-
cmp_outputs.append(v)
|
| 967 |
-
cmp_outputs.append(l)
|
| 968 |
-
cmp_outputs.append(cmp_status)
|
| 969 |
-
|
| 970 |
-
def _load_all(*args):
|
| 971 |
-
ov, a, fps = args[0], args[1], args[2]
|
| 972 |
-
slots = list(args[3:])
|
| 973 |
-
res = [None] * MAX_COMPARE
|
| 974 |
-
lbs = [""] * MAX_COMPARE
|
| 975 |
-
|
| 976 |
-
# Sequential — fast when pre-cached, safe in all environments
|
| 977 |
-
for i, seq in enumerate(slots):
|
| 978 |
-
if seq:
|
| 979 |
-
try:
|
| 980 |
-
p, _ = get_video(seq, ov, a, fps)
|
| 981 |
-
res[i] = str(p) if p else None
|
| 982 |
-
except Exception:
|
| 983 |
-
res[i] = None
|
| 984 |
-
lbs[i] = seq
|
| 985 |
-
|
| 986 |
-
n_ok = sum(1 for r in res if r)
|
| 987 |
-
out = []
|
| 988 |
-
for r, l in zip(res, lbs):
|
| 989 |
-
out.append(r)
|
| 990 |
-
out.append(f"**{l}**" if l else "*empty*")
|
| 991 |
-
out.append(f"✅ {n_ok}/{len([s for s in slots if s])} slots loaded")
|
| 992 |
-
return out
|
| 993 |
-
|
| 994 |
-
cmp_btn.click(_load_all,
|
| 995 |
-
inputs=[cmp_ov, cmp_a, cmp_fps] + cmp_dds,
|
| 996 |
-
outputs=cmp_outputs)
|
| 997 |
-
|
| 998 |
-
for i, (dd, vid, lbl) in enumerate(zip(cmp_dds, cmp_vids, cmp_lbls)):
|
| 999 |
-
def _mk(idx):
|
| 1000 |
-
def _single(seq, ov, a, fps):
|
| 1001 |
-
p, _ = get_video(seq, ov, a, fps)
|
| 1002 |
-
return p, f"**{seq}**" if seq else "*empty*"
|
| 1003 |
-
return _single
|
| 1004 |
-
dd.change(_mk(i), [dd, cmp_ov, cmp_a, cmp_fps], [vid, lbl])
|
| 1005 |
|
| 1006 |
# ──────────────────────────────────────────────────────────────
|
| 1007 |
-
# Tab
|
| 1008 |
# ──────────────────────────────────────────────────────────────
|
| 1009 |
with gr.TabItem("📊 Statistics"):
|
| 1010 |
gr.Markdown("### Dataset Overview")
|
|
@@ -1025,7 +844,7 @@ def build_ui():
|
|
| 1025 |
""")
|
| 1026 |
|
| 1027 |
# ──────────────────────────────────────────────────────────────
|
| 1028 |
-
# Tab
|
| 1029 |
# ──────────────────────────────────────────────────────────────
|
| 1030 |
with gr.TabItem("ℹ️ About"):
|
| 1031 |
gr.Markdown(f"""
|
|
|
|
| 100 |
DEFAULT_FPS = 24
|
| 101 |
DEFAULT_ALPHA = 0.55
|
| 102 |
DEFAULT_CRF = 18
|
| 103 |
+
THUMB_W, THUMB_H = 427, 240 # 16:9 thumbnails (half of 854×480 DAVIS frames)
|
|
|
|
|
|
|
| 104 |
|
| 105 |
# ── Dataset download ───────────────────────────────────────────────────────────
|
| 106 |
|
|
|
|
| 512 |
n_2017 = int(DF["in_2017"].sum())
|
| 513 |
_first = ALL_SEQUENCES[0]
|
| 514 |
_first_n = len(_get_frame_paths(_first))
|
|
|
|
|
|
|
| 515 |
with gr.Blocks(title="DAVIS Dataset Explorer") as demo:
|
| 516 |
|
| 517 |
gr.Markdown(
|
|
|
|
| 644 |
btn_play.click(get_video, [seq_dd, v_ov, v_a, v_fps], [video_out, v_status])
|
| 645 |
|
| 646 |
# ──────────────────────────────────────────────────────────────
|
| 647 |
+
# Tab 3 · Gallery (toggle up to 4 sequences — videos at top)
|
| 648 |
# ──────────────────────────────────────────────────────────────
|
| 649 |
with gr.TabItem("🖼 Gallery"):
|
| 650 |
+
|
| 651 |
+
# ── Video playback area (top) ──────────────────────────────
|
| 652 |
+
g_placeholder = gr.Markdown(
|
| 653 |
+
"### 🎬 Choose up to 4 sequences from the gallery below",
|
| 654 |
+
visible=True,
|
| 655 |
)
|
| 656 |
+
g_sel_info = gr.Markdown("", visible=False)
|
| 657 |
+
|
| 658 |
+
with gr.Row():
|
| 659 |
+
g_vid_0 = gr.Video(visible=False, autoplay=True,
|
| 660 |
+
height=320, label="")
|
| 661 |
+
g_vid_1 = gr.Video(visible=False, autoplay=True,
|
| 662 |
+
height=320, label="")
|
| 663 |
+
with gr.Row():
|
| 664 |
+
g_vid_2 = gr.Video(visible=False, autoplay=True,
|
| 665 |
+
height=320, label="")
|
| 666 |
+
g_vid_3 = gr.Video(visible=False, autoplay=True,
|
| 667 |
+
height=320, label="")
|
| 668 |
+
|
| 669 |
+
with gr.Row():
|
| 670 |
+
g_clr_btn = gr.Button("✕ Clear selection", size="sm",
|
| 671 |
+
visible=False, scale=1)
|
| 672 |
+
gr.Markdown("", scale=4)
|
| 673 |
+
|
| 674 |
+
gr.Markdown("---")
|
| 675 |
+
|
| 676 |
+
# ── Filter + video options ─────────────────────────────────
|
| 677 |
with gr.Row():
|
| 678 |
g_year = gr.Dropdown(["All years","2016 only","2017 only"],
|
| 679 |
value="All years", label="Year", scale=1)
|
|
|
|
| 684 |
g_srch = gr.Textbox(placeholder="Search…", label="Search", scale=2)
|
| 685 |
with gr.Row():
|
| 686 |
g_fmin = gr.Slider(int(DF["frames"].min()), int(DF["frames"].max()),
|
| 687 |
+
int(DF["frames"].min()), step=1,
|
| 688 |
+
label="Min frames", scale=3)
|
| 689 |
g_fmax = gr.Slider(int(DF["frames"].min()), int(DF["frames"].max()),
|
| 690 |
+
int(DF["frames"].max()), step=1,
|
| 691 |
+
label="Max frames", scale=3)
|
| 692 |
with gr.Row():
|
| 693 |
+
g_fps = gr.Slider(1, 30, DEFAULT_FPS, step=1, label="FPS", scale=2)
|
| 694 |
+
g_vid_ov = gr.Checkbox(value=True, label="Burn overlay", scale=1)
|
| 695 |
+
g_vid_a = gr.Slider(0.1, 1.0, DEFAULT_ALPHA, step=0.05,
|
| 696 |
+
label="Opacity", scale=2)
|
| 697 |
+
g_ov_th = gr.Checkbox(value=False, label="Overlay on thumbnails",
|
| 698 |
+
scale=1)
|
| 699 |
+
|
| 700 |
+
g_count_md = gr.Markdown(
|
| 701 |
+
f"**{len(ALL_SEQUENCES)} sequences** — click thumbnails to toggle (max 4)"
|
| 702 |
+
)
|
| 703 |
|
| 704 |
+
# ── Gallery thumbnails (bottom) ────────────────────────────
|
| 705 |
gallery = gr.Gallery(
|
| 706 |
value=_ALL_THUMBS,
|
| 707 |
label="Sequences",
|
| 708 |
+
columns=4,
|
| 709 |
rows=None,
|
| 710 |
height="auto",
|
| 711 |
allow_preview=False,
|
| 712 |
show_label=False,
|
| 713 |
+
object_fit="contain",
|
| 714 |
)
|
| 715 |
|
| 716 |
+
# States
|
| 717 |
+
g_seq_state = gr.State(ALL_SEQUENCES.copy())
|
| 718 |
+
g_selected_state = gr.State([]) # list[str], max 4
|
| 719 |
|
| 720 |
+
# ── Filter → rebuild gallery ───────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
g_f_inputs = [g_year, g_split, g_obj, g_fmin, g_fmax, g_srch]
|
| 722 |
|
| 723 |
def _on_g_filter(*args):
|
| 724 |
+
ov = args[-1]
|
| 725 |
+
fargs = args[:-1]
|
| 726 |
fdf = filter_df(*fargs)
|
| 727 |
seqs = fdf["sequence"].tolist()
|
| 728 |
items = build_gallery_items(seqs, overlay=ov)
|
| 729 |
+
return (items, seqs,
|
| 730 |
+
f"**{len(seqs)} sequences** — click thumbnails to toggle (max 4)")
|
| 731 |
|
| 732 |
+
for inp in g_f_inputs + [g_ov_th]:
|
| 733 |
+
inp.change(_on_g_filter, g_f_inputs + [g_ov_th],
|
| 734 |
[gallery, g_seq_state, g_count_md])
|
| 735 |
|
| 736 |
+
# ── Toggle helpers ─────────────────────────────────────────
|
| 737 |
+
def _build_video_updates(sel_seqs, ov, a, fps):
|
| 738 |
+
"""Return 4 gr.update() objects for video slots 0-3."""
|
| 739 |
+
updates = []
|
| 740 |
+
for i in range(4):
|
| 741 |
+
if i < len(sel_seqs):
|
| 742 |
+
try:
|
| 743 |
+
p, _ = get_video(sel_seqs[i], ov, a, fps)
|
| 744 |
+
path = str(p) if p else None
|
| 745 |
+
except Exception:
|
| 746 |
+
path = None
|
| 747 |
+
updates.append(gr.update(
|
| 748 |
+
visible=True, value=path, label=sel_seqs[i]))
|
| 749 |
+
else:
|
| 750 |
+
updates.append(gr.update(visible=False, value=None))
|
| 751 |
+
return updates
|
| 752 |
+
|
| 753 |
+
# ── Gallery click → toggle selection ──────────────────────
|
| 754 |
+
def _on_gallery_toggle(evt: gr.SelectData,
|
| 755 |
+
sel_seqs, g_seqs, ov, a, fps):
|
| 756 |
+
if evt is None or not g_seqs:
|
| 757 |
+
return (sel_seqs,
|
| 758 |
+
gr.update(visible=True), gr.update(visible=False),
|
| 759 |
+
gr.update(visible=False, value=None),
|
| 760 |
+
gr.update(visible=False, value=None),
|
| 761 |
+
gr.update(visible=False, value=None),
|
| 762 |
+
gr.update(visible=False, value=None),
|
| 763 |
+
gr.update(visible=False))
|
| 764 |
+
|
| 765 |
+
seq = g_seqs[evt.index]
|
| 766 |
+
if seq in sel_seqs:
|
| 767 |
+
sel_seqs = [s for s in sel_seqs if s != seq]
|
| 768 |
+
elif len(sel_seqs) < 4:
|
| 769 |
+
sel_seqs = sel_seqs + [seq]
|
| 770 |
+
# else: already 4 selected — silently ignore
|
| 771 |
+
|
| 772 |
+
n = len(sel_seqs)
|
| 773 |
+
vid_updates = _build_video_updates(sel_seqs, ov, a, fps)
|
| 774 |
+
info_txt = ("▶ " +
|
| 775 |
+
" · ".join(f"**{s}**" for s in sel_seqs) +
|
| 776 |
+
" *(click a thumbnail to deselect)*") if n > 0 else ""
|
| 777 |
+
return (
|
| 778 |
+
sel_seqs,
|
| 779 |
+
gr.update(visible=(n == 0)), # placeholder
|
| 780 |
+
gr.update(visible=(n > 0), value=info_txt), # sel_info
|
| 781 |
+
vid_updates[0],
|
| 782 |
+
vid_updates[1],
|
| 783 |
+
vid_updates[2],
|
| 784 |
+
vid_updates[3],
|
| 785 |
+
gr.update(visible=(n > 0)), # clear button
|
| 786 |
+
)
|
| 787 |
|
| 788 |
gallery.select(
|
| 789 |
+
_on_gallery_toggle,
|
| 790 |
+
inputs=[g_selected_state, g_seq_state, g_vid_ov, g_vid_a, g_fps],
|
| 791 |
+
outputs=[g_selected_state, g_placeholder, g_sel_info,
|
| 792 |
+
g_vid_0, g_vid_1, g_vid_2, g_vid_3, g_clr_btn],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 793 |
)
|
| 794 |
|
| 795 |
+
# Re-encode when overlay / FPS settings change
|
| 796 |
+
def _reload_settings(sel_seqs, ov, a, fps):
|
| 797 |
+
return _build_video_updates(sel_seqs, ov, a, fps)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 798 |
|
| 799 |
+
for _inp in [g_vid_ov, g_vid_a, g_fps]:
|
| 800 |
+
_inp.change(
|
| 801 |
+
_reload_settings,
|
| 802 |
+
inputs=[g_selected_state, g_vid_ov, g_vid_a, g_fps],
|
| 803 |
+
outputs=[g_vid_0, g_vid_1, g_vid_2, g_vid_3],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 804 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 805 |
|
| 806 |
+
# Clear selection button
|
| 807 |
+
def _clear_selection():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 808 |
return (
|
| 809 |
+
[],
|
| 810 |
+
gr.update(visible=True),
|
| 811 |
+
gr.update(visible=False, value=""),
|
| 812 |
+
gr.update(visible=False, value=None),
|
| 813 |
+
gr.update(visible=False, value=None),
|
| 814 |
+
gr.update(visible=False, value=None),
|
| 815 |
+
gr.update(visible=False, value=None),
|
| 816 |
+
gr.update(visible=False),
|
| 817 |
)
|
| 818 |
|
| 819 |
+
g_clr_btn.click(
|
| 820 |
+
_clear_selection,
|
| 821 |
+
outputs=[g_selected_state, g_placeholder, g_sel_info,
|
| 822 |
+
g_vid_0, g_vid_1, g_vid_2, g_vid_3, g_clr_btn],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 823 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 824 |
|
| 825 |
# ──────────────────────────────────────────────────────────────
|
| 826 |
+
# Tab 4 · Statistics
|
| 827 |
# ──────────────────────────────────────────────────────────────
|
| 828 |
with gr.TabItem("📊 Statistics"):
|
| 829 |
gr.Markdown("### Dataset Overview")
|
|
|
|
| 844 |
""")
|
| 845 |
|
| 846 |
# ──────────────────────────────────────────────────────────────
|
| 847 |
+
# Tab 5 · About
|
| 848 |
# ──────────────────────────────────────────────────────────────
|
| 849 |
with gr.TabItem("ℹ️ About"):
|
| 850 |
gr.Markdown(f"""
|