AhmadRH commited on
Commit
43835a5
·
0 Parent(s):

Clean history without videos

Browse files
Files changed (5) hide show
  1. .gitattributes +1 -0
  2. .gitignore +9 -0
  3. README.md +10 -0
  4. app.py +348 -0
  5. requirements.txt +3 -0
.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+
.gitignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # IDE configuration
2
+ .idea/
3
+
4
+ # Local data and results
5
+ data/
6
+
7
+ # Python cache files
8
+ __pycache__/
9
+ *.pyc
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Sequential Driving World Model evaluation
3
+ emoji: 🎬
4
+ colorFrom: indigo
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: "4.44.1"
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
app.py ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import os
4
+ import random
5
+ from datetime import datetime
6
+ from huggingface_hub import list_repo_files
7
+
8
+ # --- 1. CONFIGURATION ---
9
+ DATASET_REPO = "AhmadRH/SeqWMVideos"
10
+ DATASET_BRANCH = "main" # you can pin to a commit SHA for stability
11
+ # VIDEO_DIR = "videos/opendv"
12
+ VIDEO_BASE_URL = f"https://huggingface.co/datasets/{DATASET_REPO}/resolve/{DATASET_BRANCH}"
13
+ RESULTS_FILE = "data/results.csv"
14
+ ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', 'password_for_local_test')
15
+
16
+ # Create the data directory if it doesn't exist
17
+ os.makedirs("data", exist_ok=True)
18
+
19
+
20
+ # --- 2. LOAD AND PREPARE VIDEO PAIRS ---
21
+ def load_video_pairs():
22
+ """Scans the video directory and creates a list of video pairs."""
23
+ # videos = os.listdir(os.path.join(VIDEO_DIR, "ltx_2B_2step"))
24
+ files = list_repo_files(DATASET_REPO, repo_type="dataset", revision=DATASET_BRANCH)
25
+ # Keep only the files under /videos/... with typical video extensions
26
+ video_exts = {".mp4", ".webm", ".mov"}
27
+ dataset_videos = [f for f in files if os.path.splitext(f)[1].lower() in video_exts]
28
+
29
+ # Build: model -> set of filenames in that model folder (e.g., "scene1.mp4")
30
+ # Also remember full relative path per file.
31
+ from collections import defaultdict
32
+ model_to_files = defaultdict(set)
33
+ model_to_paths = defaultdict(dict) # (filename -> "videos/<model>/<filename>")
34
+
35
+ for relpath in dataset_videos:
36
+ # relpath like: "ltx_2B_2step/scene1.mp4"
37
+ parts = relpath.split("/") # ["videos", "<model>", "<filename>"]
38
+ model_name, filename = parts
39
+ model_to_files[model_name].add(filename)
40
+ model_to_paths[model_name][filename] = relpath
41
+
42
+ models_to_compare = [("ltx_2B_2step", "ltx_2B_unconditional"),
43
+ ("ltx_13B_2step", "ltx_13B_unconditional"),
44
+ ("ltx_2B_2step", "predict1-7B"),
45
+ ("ltx_13B_2step", "predict2-2B"),
46
+ ("ltx_2B_2step", "ltx_2B_2step_from13B"),
47
+ ("ltx_2B_2step", "ltx_13B_2step_from2B"),
48
+ ("ltx_2B_2step", "ltx_13B_2step"),
49
+ ("ltx_2B_2step_from13B", "ltx_13B_2step_from2B"),
50
+ ("ltx_2B_2step_from13B", "ltx_13B_2step"),
51
+ ("ltx_13B_2step_from2B", "ltx_13B_2step")]
52
+ video_pairs = []
53
+ for model_a, model_b in models_to_compare:
54
+ if model_a not in model_to_files or model_b not in model_to_files:
55
+ continue
56
+
57
+ common_filenames = sorted(model_to_files[model_a].intersection(model_to_files[model_b]))
58
+ for filename in common_filenames:
59
+ path_a = model_to_paths[model_a][filename] # e.g., "videos/model_a/scene1.mp4"
60
+ path_b = model_to_paths[model_b][filename]
61
+
62
+ url_a = f"{VIDEO_BASE_URL}/{path_a}" # direct HTTPS URL to the file in the dataset
63
+ url_b = f"{VIDEO_BASE_URL}/{path_b}"
64
+
65
+ scene_name = os.path.splitext(filename)[0]
66
+ pair_key = f"{scene_name}${model_a} vs {model_b}"
67
+
68
+ video_pairs.append((url_a, url_b, pair_key, model_a, model_b, scene_name))
69
+
70
+ print(f"Loaded {len(video_pairs)} video pairs.")
71
+ return video_pairs
72
+
73
+
74
+ # Load the pairs once when the app starts
75
+ ALL_PAIRS = load_video_pairs()
76
+ ALL_PAIR_KEYS = {key for _, _, key, _, _, _ in ALL_PAIRS}
77
+
78
+ # --- 3. CORE APP LOGIC ---
79
+ def start_session(email):
80
+ """
81
+ Initializes a user session. Hides login, shows eval UI, and loads the first pair.
82
+ """
83
+ if not email:
84
+ # You can add more robust email validation here if needed
85
+ raise gr.Error("Please enter your email to start.")
86
+
87
+ # NEW: Check for previously completed pairs for this user.
88
+ seen_pair_keys = set()
89
+ if os.path.exists(RESULTS_FILE):
90
+ df_results = pd.read_csv(RESULTS_FILE)
91
+ # Filter results for the current user's email
92
+ user_results = df_results[df_results["email"] == email]
93
+ seen_pair_keys = set(user_results["pair_key"])
94
+
95
+ # NEW: Determine which pairs the user has not seen yet.
96
+ unseen_pair_keys = ALL_PAIR_KEYS - seen_pair_keys
97
+
98
+ if not unseen_pair_keys:
99
+ return None, gr.update(visible=False), gr.update(visible=False), gr.update(
100
+ visible=True), None, None, "You have already completed all evaluations. Thank you!"
101
+
102
+ unseen_pairs = [pair for pair in ALL_PAIRS if pair[2] in unseen_pair_keys]
103
+ random.shuffle(unseen_pairs)
104
+
105
+ user_state = [email, unseen_pairs, []]
106
+
107
+ first_pair = user_state[1].pop()
108
+ path_a, path_b, pair_key, model_a_name, model_b_name, scene_name = first_pair
109
+
110
+ # Random assignment: which one goes to Video A / Video B in the UI
111
+ a_first = random.choice([True, False])
112
+ if a_first:
113
+ video_a_path, video_b_path = path_a, path_b
114
+ video_a_model, video_b_model = model_a_name, model_b_name
115
+ else:
116
+ video_a_path, video_b_path = path_b, path_a
117
+ video_a_model, video_b_model = model_b_name, model_a_name
118
+
119
+ # Store the actual assignment in the state for saving results
120
+ user_state.append({
121
+ "current_pair_key": pair_key,
122
+ "scene_name": scene_name,
123
+ "video_a_model": video_a_model,
124
+ "video_b_model": video_b_model,
125
+ "video_a_path": video_a_path,
126
+ "video_b_path": video_b_path,
127
+ })
128
+
129
+ progress = f"Progress: {len(seen_pair_keys) + 1} / {len(ALL_PAIRS)}"
130
+
131
+ return (
132
+ user_state,
133
+ gr.update(visible=False), # Hide login UI
134
+ gr.update(visible=True), # Show evaluation UI
135
+ gr.update(visible=False), # Hide thank you message
136
+ video_a_path,
137
+ video_b_path,
138
+ progress
139
+ )
140
+
141
+
142
+ # def record_choice(user_state, choice):
143
+ # """
144
+ # Records the user's choice, saves it, and loads the next pair.
145
+ # """
146
+ # # Unpack user state
147
+ # email, unseen_pairs, results = user_state[:3]
148
+ # current_pair_info = user_state[3]
149
+ #
150
+ # is_baseline_a = current_pair_info["is_baseline_a"]
151
+ #
152
+ # # Determine which video type was preferred
153
+ # if choice == "A":
154
+ # preferred_type = "baseline" if is_baseline_a else "mymethod"
155
+ # elif choice == "B":
156
+ # preferred_type = "mymethod" if is_baseline_a else "baseline"
157
+ # else: # choice == "None"
158
+ # preferred_type = "no_preference"
159
+ # # print("User choice:", choice, "-> preferred_type:", preferred_type)
160
+ #
161
+ # # Create a result record
162
+ # result = {
163
+ # "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
164
+ # "email": email,
165
+ # "pair_key": current_pair_info["current_pair_key"],
166
+ # "preferred_type": preferred_type,
167
+ # "video_a": "baseline" if is_baseline_a else "mymethod",
168
+ # "video_b": "mymethod" if is_baseline_a else "baseline",
169
+ # }
170
+ # results.append(result)
171
+ #
172
+ # # Save to CSV immediately
173
+ # df_new = pd.DataFrame([result])
174
+ # df_new.to_csv(RESULTS_FILE, mode='a', header=not os.path.exists(RESULTS_FILE), index=False)
175
+ #
176
+ # # Check if there are more pairs
177
+ # if not unseen_pairs:
178
+ # # No more pairs, end the session
179
+ # return user_state, gr.update(visible=False), gr.update(visible=True), None, None, "Complete!"
180
+ #
181
+ # # Load the next pair
182
+ # next_pair = unseen_pairs.pop()
183
+ #
184
+ # # Randomly decide if baseline is video A or B
185
+ # is_baseline_a = random.choice([True, False])
186
+ # video_a, video_b = (next_pair[1], next_pair[0]) if is_baseline_a else (next_pair[0], next_pair[1])
187
+ #
188
+ # # Update the state with the new pair's info
189
+ # user_state[3] = {"current_pair_key": next_pair[2], "is_baseline_a": is_baseline_a}
190
+ #
191
+ # progress = f"Progress: {len(ALL_PAIRS) - len(unseen_pairs)} / {len(ALL_PAIRS)}"
192
+ #
193
+ # return user_state, gr.update(visible=True), gr.update(visible=False), video_a, video_b, progress
194
+ def record_choice(user_state, choice):
195
+ """
196
+ Records the user's choice, saves it, and loads the next pair.
197
+ """
198
+ # Unpack user state
199
+ email, unseen_pairs, results = user_state[:3]
200
+ current = user_state[3]
201
+
202
+ video_a_model = current["video_a_model"]
203
+ video_b_model = current["video_b_model"]
204
+ video_a_path = current["video_a_path"]
205
+ video_b_path = current["video_b_path"]
206
+ pair_key = current["current_pair_key"]
207
+ scene_name = current.get("scene_name", "")
208
+
209
+ # Determine which model was preferred
210
+ if choice == "A":
211
+ winner_model, loser_model = video_a_model, video_b_model
212
+ elif choice == "B":
213
+ winner_model, loser_model = video_b_model, video_a_model
214
+ else: # "None"
215
+ winner_model, loser_model = "no_preference", "no_preference"
216
+
217
+ # Create a result record
218
+ result = {
219
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
220
+ "email": email,
221
+ "pair_key": pair_key,
222
+ "scene": scene_name,
223
+ "choice": choice, # "A" / "B" / "None"
224
+ "winner_model": winner_model,
225
+ "loser_model": loser_model,
226
+ "video_a_model": video_a_model,
227
+ "video_b_model": video_b_model,
228
+ "video_a_path": video_a_path,
229
+ "video_b_path": video_b_path,
230
+ }
231
+ results.append(result)
232
+
233
+ # Save to CSV immediately
234
+ df_new = pd.DataFrame([result])
235
+ df_new.to_csv(RESULTS_FILE, mode='a', header=not os.path.exists(RESULTS_FILE), index=False)
236
+
237
+ # Check if there are more pairs
238
+ if not unseen_pairs:
239
+ # No more pairs, end the session
240
+ return user_state, gr.update(visible=False), gr.update(visible=True), None, None, "Complete!"
241
+
242
+ # Load the next pair
243
+ next_pair = unseen_pairs.pop()
244
+ path_a, path_b, pair_key, model_a_name, model_b_name, scene_name = next_pair
245
+
246
+ # Random assignment for the next pair
247
+ a_first = random.choice([True, False])
248
+ if a_first:
249
+ video_a_path, video_b_path = path_a, path_b
250
+ video_a_model, video_b_model = model_a_name, model_b_name
251
+ else:
252
+ video_a_path, video_b_path = path_b, path_a
253
+ video_a_model, video_b_model = model_b_name, model_a_name
254
+
255
+ # Update the state with the new pair's info
256
+ user_state[3] = {
257
+ "current_pair_key": pair_key,
258
+ "scene_name": scene_name,
259
+ "video_a_model": video_a_model,
260
+ "video_b_model": video_b_model,
261
+ "video_a_path": video_a_path,
262
+ "video_b_path": video_b_path,
263
+ }
264
+
265
+ progress = f"Progress: {len(ALL_PAIRS) - len(unseen_pairs)} / {len(ALL_PAIRS)}"
266
+
267
+ return user_state, gr.update(visible=True), gr.update(visible=False), video_a_path, video_b_path, progress
268
+
269
+ def get_results_file(password):
270
+ if password == ADMIN_PASSWORD:
271
+ if os.path.exists(RESULTS_FILE):
272
+ return gr.File(value=RESULTS_FILE, visible=True)
273
+ else:
274
+ gr.Warning("Results file not found. Has anyone submitted an evaluation yet?")
275
+ return gr.File(visible=False)
276
+ else:
277
+ gr.Warning("Incorrect password.")
278
+ return gr.File(visible=False)
279
+
280
+ # --- 4. GRADIO UI DEFINITION ---
281
+ with gr.Blocks(theme=gr.themes.Soft(), css="footer {display: none !important}") as demo:
282
+ # This component holds all the user-specific data during a session
283
+ user_state = gr.State()
284
+
285
+ gr.Markdown("# 🎬 Video Generation Model Evaluation")
286
+ with gr.Tabs():
287
+ with gr.TabItem("Evaluation"):
288
+ gr.Markdown(
289
+ "Thank you for participating! Please enter your email to begin. You will be shown pairs of videos that loop automatically. Please select the one you prefer or choose 'No Preference'.")
290
+
291
+ with gr.Column(visible=True) as login_ui:
292
+ email_input = gr.Textbox(label="Your Email", placeholder="user@example.com")
293
+ start_button = gr.Button("Start / Resume Evaluation", variant="primary")
294
+
295
+ with gr.Column(visible=False) as eval_ui:
296
+ progress_text = gr.Markdown(f"Progress: 1 / {len(ALL_PAIRS)}")
297
+ with gr.Row():
298
+ video_a = gr.Video(label="Video A", width=1280, height=704, autoplay=True, loop=True)
299
+ video_b = gr.Video(label="Video B", width=1280, height=704, autoplay=True, loop=True)
300
+ with gr.Row():
301
+ choice_a_btn = gr.Button("I prefer Video A", variant="primary")
302
+ no_pref_btn = gr.Button("No Preference")
303
+ choice_b_btn = gr.Button("I prefer Video B", variant="primary")
304
+
305
+ with gr.Column(visible=False) as thanks_ui:
306
+ thanks_message = gr.Markdown(
307
+ "## ✅ Thank You! \n You have completed the evaluation. Your responses have been saved. You can now close this window.")
308
+
309
+ # --- Admin tab for downloading results ---
310
+ with gr.TabItem("Admin"):
311
+ gr.Markdown("Enter the password to download the current results file.")
312
+ admin_password = gr.Textbox(label="Password", type="password")
313
+ download_button = gr.Button("Download Results", variant="primary")
314
+ results_file_output = gr.File(label="Results File", visible=False)
315
+
316
+ # --- 5. CONNECTING UI TO FUNCTIONS ---
317
+ start_button.click(
318
+ fn=start_session,
319
+ inputs=[email_input],
320
+ outputs=[user_state, login_ui, eval_ui, thanks_ui, video_a, video_b, progress_text]
321
+ )
322
+
323
+ choice_a_btn.click(
324
+ fn=record_choice,
325
+ inputs=[user_state, gr.Textbox("A", visible=False)],
326
+ outputs=[user_state, eval_ui, thanks_ui, video_a, video_b, progress_text]
327
+ ).then(fn=None, inputs=None, outputs=None, js="() => { window.scrollTo(0, 0); }")
328
+
329
+ no_pref_btn.click(
330
+ fn=record_choice,
331
+ inputs=[user_state, gr.Textbox("None", visible=False)],
332
+ outputs=[user_state, eval_ui, thanks_ui, video_a, video_b, progress_text]
333
+ ).then(fn=None, inputs=None, outputs=None, js="() => { window.scrollTo(0, 0); }")
334
+
335
+ choice_b_btn.click(
336
+ fn=record_choice,
337
+ inputs=[user_state, gr.Textbox("B", visible=False)],
338
+ outputs=[user_state, eval_ui, thanks_ui, video_a, video_b, progress_text]
339
+ ).then(fn=None, inputs=None, outputs=None, js="() => { window.scrollTo(0, 0); }")
340
+
341
+ download_button.click(
342
+ fn=get_results_file,
343
+ inputs=[admin_password],
344
+ outputs=[results_file_output]
345
+ )
346
+
347
+ # Launch the app
348
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ pandas
3
+ huggingface_hub