Youngsun Lim commited on
Commit
426d1fc
·
1 Parent(s): c6237e5

3rd revision

Browse files
Files changed (2) hide show
  1. app.py +90 -72
  2. requirements.txt +1 -0
app.py CHANGED
@@ -1,103 +1,121 @@
1
- import os, csv, json, random
 
2
  import gradio as gr
 
3
 
4
- # É • X (D”Ü ì0Ì )
 
 
 
5
  EVAL_AXES = [
6
  ("motion_consistency", "Motion Consistency"),
7
  ("appearance_consistency", "Appearance Consistency"),
8
  ("anatomy_consistency", "Anatomy Consistency"),
9
  ]
 
10
 
11
- # °ü ¥ ½\ (HF SpacesX |ܤ4¸ ¤ ¬À ¬© Œ¥)
12
- RESULTS_PATH = os.getenv("RESULTS_CSV", "/data/results.csv")
13
-
14
- # D$ ©]
15
  with open("videos.json", "r", encoding="utf-8") as f:
16
  VIDEOS = json.load(f)
17
 
18
- # °ü | äT ô¥
19
- os.makedirs(os.path.dirname(RESULTS_PATH), exist_ok=True)
20
- if not os.path.exists(RESULTS_PATH):
21
- with open(RESULTS_PATH, "w", newline="", encoding="utf-8") as f:
22
- writer = csv.writer(f)
23
- writer.writerow(["ts_iso", "participant_id", "video_id"] + [k for k, _ in EVAL_AXES] + ["notes"])
24
-
25
- def next_video(state):
26
- """state: dict ¥©; 8 Ä œd  À"""
27
- if not state or "order" not in state:
28
- order = list(range(len(VIDEOS)))
29
- random.shuffle(order)
30
- state = {"order": order, "idx": 0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  idx = state["idx"]
32
  if idx >= len(state["order"]):
33
- return state, None, "(completed)"
34
  v = VIDEOS[state["order"][idx]]
35
- label = f'{v["id"]} ({idx+1}/{len(state["order"])})'
36
- return state, v["url"], label
37
-
38
- def submit(participant_id, video_id, *scores_and_notes):
39
- *scores, notes = scores_and_notes
40
- if not video_id or not all(scores):
41
- return gr.update(visible=True, value="Please fill all required fields.")
42
- from datetime import datetime
43
- with open(RESULTS_PATH, "a", newline="", encoding="utf-8") as f:
44
- writer = csv.writer(f)
45
- writer.writerow([datetime.utcnow().isoformat(), participant_id or "", video_id] + list(map(int, scores)) + [notes or ""])
46
- return gr.update(visible=True, value="Saved.")
47
-
48
- def save_and_next(participant_id, state, video_id, *scores_and_notes):
49
- status = submit(participant_id, video_id, *scores_and_notes)
50
- if "Saved." not in (status["value"] or ""):
51
- return status, state, gr.update(), gr.update()
 
 
 
 
 
 
 
52
  state["idx"] += 1
53
- state, url, label = next_video(state)
54
- #  0T
55
- reset_scores = [gr.update(value=None) for _ in EVAL_AXES]
56
- return status, state, gr.update(value=url), gr.update(value=label), *reset_scores, gr.update(value="")
57
 
58
  with gr.Blocks(fill_width=True) as demo:
59
- gr.Markdown("# Human Evaluation (Video)")
60
- gr.Markdown("Please rate each video on the axes below. 1 = poor, 5 = excellent.")
61
 
62
- participant_id = gr.Textbox(label="Participant ID (optional)", placeholder="e.g., P001")
63
  state = gr.State({})
64
-
65
  video = gr.Video(label="Video")
66
  vid_label = gr.Markdown()
67
 
68
- # ¬|tTä
69
  sliders = []
70
  for key, title in EVAL_AXES:
71
  sliders.append(gr.Slider(1, 5, value=None, step=1, label=title, interactive=True))
72
  notes = gr.Textbox(label="Notes (optional)", lines=3)
73
-
74
- with gr.Row():
75
- prev_btn = gr.Button("À Prev", variant="secondary")
76
- save_next_btn = gr.Button("Save & Next ¶", variant="primary")
77
-
78
  status = gr.Alert(visible=False)
79
 
80
- # 0
81
- def init():
82
- st, url, label = next_video({})
83
- return st, url, label
84
- demo.load(init, [], [state, video, vid_label])
85
-
86
- # Prev (è t<\ tÙ, ¥@ XÀ JL)
87
- def prev(st):
88
- if st and st.get("idx", 0) > 0:
89
- st["idx"] -= 1
90
- st, url, label = next_video(st)
91
- resets = [gr.update(value=None) for _ in EVAL_AXES]
92
- return st, gr.update(value=url), gr.update(value=label), *resets, gr.update(value="")
93
- prev_btn.click(prev, [state], [state, video, vid_label, *sliders, notes])
94
-
95
- # Save & Next
96
- save_next_btn.click(
97
- save_and_next,
98
- inputs=[participant_id, state, vid_label] + sliders + [notes],
99
- outputs=[status, state, video, vid_label] + sliders + [notes],
100
- )
101
 
102
  if __name__ == "__main__":
103
  demo.launch()
 
1
+ import os, io, csv, json, random
2
+ from datetime import datetime
3
  import gradio as gr
4
+ from huggingface_hub import HfApi, hf_hub_download
5
 
6
+ # ===== 사용자 설정 =====
7
+ REPO_ID = os.getenv("RESULTS_REPO", "SGTLIM/videoeval_results") # 생성한 dataset repo
8
+ RESULTS_FILE = "results.csv" # repo 안에 저장될 파일명
9
+ HF_TOKEN = os.getenv("HF_TOKEN") # Space Variables에 저장
10
  EVAL_AXES = [
11
  ("motion_consistency", "Motion Consistency"),
12
  ("appearance_consistency", "Appearance Consistency"),
13
  ("anatomy_consistency", "Anatomy Consistency"),
14
  ]
15
+ # ======================
16
 
17
+ # 비디오 목록
 
 
 
18
  with open("videos.json", "r", encoding="utf-8") as f:
19
  VIDEOS = json.load(f)
20
 
21
+ api = HfApi()
22
+
23
+ def _read_current_results():
24
+ """Hub에서 기존 results.csv를 받아와 텍스트(byte) 리턴. 없으면 None."""
25
+ try:
26
+ p = hf_hub_download(repo_id=REPO_ID, filename=RESULTS_FILE,
27
+ repo_type="dataset", token=HF_TOKEN, local_dir="/tmp",
28
+ local_dir_use_symlinks=False)
29
+ with open(p, "rb") as f:
30
+ return f.read()
31
+ except Exception:
32
+ return None
33
+
34
+ def _append_csv_bytes(old_bytes: bytes | None, row: list[str]) -> bytes:
35
+ """기존 CSV bytes에 한 줄 append하여 새로운 bytes 리턴 (없으면 헤더 포함 생성)."""
36
+ output = io.StringIO()
37
+ writer = csv.writer(output)
38
+ if not old_bytes:
39
+ header = ["ts_iso", "participant_id", "video_id"] + [k for k, _ in EVAL_AXES] + ["notes"]
40
+ writer.writerow(header)
41
+ else:
42
+ output.write(old_bytes.decode("utf-8", errors="ignore"))
43
+ writer.writerow(row)
44
+ return output.getvalue().encode("utf-8")
45
+
46
+ def push_row_to_hub(participant_id: str, video_id: str, scores: list[int], notes: str):
47
+ """results.csv를 다운로드→append→업로드 (commit)"""
48
+ old = _read_current_results()
49
+ row = [datetime.utcnow().isoformat(), participant_id or "", video_id] + [int(s) for s in scores] + [notes or ""]
50
+ new_bytes = _append_csv_bytes(old, row)
51
+ api.upload_file(
52
+ path_or_fileobj=io.BytesIO(new_bytes),
53
+ path_in_repo=RESULTS_FILE,
54
+ repo_id=REPO_ID,
55
+ repo_type="dataset",
56
+ token=HF_TOKEN,
57
+ commit_message="append eval row",
58
+ )
59
+
60
+ # ===== Gradio UI =====
61
+ def init_state():
62
+ order = list(range(len(VIDEOS)))
63
+ random.shuffle(order)
64
+ return {"order": order, "idx": 0}
65
+
66
+ def get_current_video(state):
67
  idx = state["idx"]
68
  if idx >= len(state["order"]):
69
+ return None, f"(completed {idx}/{len(state['order'])})"
70
  v = VIDEOS[state["order"][idx]]
71
+ return v, f"{v['id']} ({idx+1}/{len(state['order'])})"
72
+
73
+ def load_first():
74
+ st = init_state()
75
+ v, label = get_current_video(st)
76
+ return st, (v["url"] if v else None), label
77
+
78
+ def save_and_next(participant_id, state, video_label, *vals):
79
+ # video_id는 label에서 앞부분만 추출
80
+ video_id = (video_label or "").split(" (", 1)[0]
81
+ scores = [vals[i] for i in range(len(EVAL_AXES))]
82
+ notes = vals[-1]
83
+
84
+ # 필수값 체크
85
+ if not video_id or any(s is None for s in scores):
86
+ return gr.update(visible=True, value="Please rate all axes."), state, gr.update(), gr.update()
87
+
88
+ try:
89
+ push_row_to_hub(participant_id, video_id, scores, notes)
90
+ status = "Saved to Hub."
91
+ except Exception as e:
92
+ status = f"Save failed: {e}"
93
+
94
+ # 다음 영상
95
  state["idx"] += 1
96
+ v, label = get_current_video(state)
97
+ resets = [gr.update(value=None) for _ in EVAL_AXES]
98
+ return gr.update(visible=True, value=status), state, gr.update(value=(v["url"] if v else None)), gr.update(value=label), *resets, gr.update(value="")
 
99
 
100
  with gr.Blocks(fill_width=True) as demo:
101
+ gr.Markdown("# 🎥 Human Evaluation (Video)")
102
+ gr.Markdown("Rate each video (1=poor, 5=excellent).")
103
 
104
+ participant_id = gr.Textbox(label="Participant ID (optional)")
105
  state = gr.State({})
 
106
  video = gr.Video(label="Video")
107
  vid_label = gr.Markdown()
108
 
 
109
  sliders = []
110
  for key, title in EVAL_AXES:
111
  sliders.append(gr.Slider(1, 5, value=None, step=1, label=title, interactive=True))
112
  notes = gr.Textbox(label="Notes (optional)", lines=3)
113
+ save_next = gr.Button("Save & Next ▶", variant="primary")
 
 
 
 
114
  status = gr.Alert(visible=False)
115
 
116
+ demo.load(load_first, [], [state, video, vid_label])
117
+ save_next.click(save_and_next, [participant_id, state, vid_label] + sliders + [notes],
118
+ [status, state, video, vid_label] + sliders + [notes])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
  if __name__ == "__main__":
121
  demo.launch()
requirements.txt CHANGED
@@ -1,2 +1,3 @@
1
  gradio==4.44.0
 
2
 
 
1
  gradio==4.44.0
2
+ huggingface_hub>=0.25.0
3