Emilyxml commited on
Commit
cff5e86
·
verified ·
1 Parent(s): a72090c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -55
app.py CHANGED
@@ -24,7 +24,7 @@ scheduler = CommitScheduler(
24
  token=TOKEN
25
  )
26
 
27
- # --- 3. 数据加载逻辑 ---
28
  def load_data():
29
  groups = {}
30
  if not os.path.exists(DATA_FOLDER):
@@ -34,13 +34,21 @@ def load_data():
34
  for filename in os.listdir(DATA_FOLDER):
35
  if filename.startswith('.'): continue
36
  file_path = os.path.join(DATA_FOLDER, filename)
37
- prefix = filename[:5]
38
 
39
  if prefix not in groups:
40
- groups[prefix] = {"images": [], "instruction": "暂无说明"}
 
41
 
 
42
  if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')):
43
- groups[prefix]["images"].append(file_path)
 
 
 
 
 
 
44
  elif filename.lower().endswith('.txt'):
45
  try:
46
  with open(file_path, "r", encoding="utf-8") as f:
@@ -49,7 +57,12 @@ def load_data():
49
  with open(file_path, "r", encoding="gbk") as f:
50
  groups[prefix]["instruction"] = f.read()
51
 
52
- valid_groups = {k: v for k, v in groups.items() if len(v["images"]) > 0}
 
 
 
 
 
53
  group_ids = list(valid_groups.keys())
54
  random.shuffle(group_ids)
55
  print(f"Loaded {len(group_ids)} groups.")
@@ -83,43 +96,48 @@ def save_user_vote(user_id, group_id, choice_labels, method_names):
83
  # --- 5. 交互逻辑 ---
84
 
85
  def get_current_question_ui(user_state):
86
- """根据当前索引刷新界面"""
87
  current_idx = user_state["index"]
88
 
89
- # 1. 检查是否结束
90
  if current_idx >= len(ALL_GROUP_IDS):
91
  return (
92
- gr.update(visible=False),
93
- gr.update(visible=False),
94
- gr.update(visible=False),
95
- gr.update(visible=False),
96
- gr.update(value="## 🎉 测试结束!\n感谢您的参与,所有结果已保存。", visible=True),
97
- user_state,
98
- [],
99
- []
100
  )
101
 
102
- # 2. 获取数据
103
  group_id = ALL_GROUP_IDS[current_idx]
104
  group_data = ALL_GROUPS[group_id]
105
 
106
- # 3. 准备文本
107
  instruction_text = f"### 任务 ({current_idx + 1} / {len(ALL_GROUP_IDS)})\n\n{group_data['instruction']}"
108
 
109
- # 4. 准备图片
110
- original_images = group_data["images"]
111
- shuffled_images = original_images.copy()
112
- random.shuffle(shuffled_images)
113
-
114
- # 构造显示列表
115
  display_list = []
116
- for i, img_path in enumerate(shuffled_images):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  label = f"Option {chr(65+i)}"
118
  display_list.append((img_path, label))
 
119
 
120
- # 动态列数
121
- num_imgs = len(shuffled_images)
122
- cols = 2 if num_imgs == 4 else min(num_imgs, 3)
 
 
 
123
 
124
  return (
125
  gr.update(value=instruction_text, visible=True),
@@ -128,29 +146,50 @@ def get_current_question_ui(user_state):
128
  gr.update(visible=True),
129
  gr.update(visible=False),
130
  user_state,
131
- shuffled_images,
132
- []
 
133
  )
134
 
135
- def toggle_selection(evt: gr.SelectData, current_indices):
 
 
 
 
136
  clicked_idx = evt.index
137
 
 
 
 
 
 
 
 
138
  if clicked_idx in current_indices:
139
  current_indices.remove(clicked_idx)
140
  else:
141
  current_indices.append(clicked_idx)
142
-
143
  current_indices.sort()
144
 
 
145
  if not current_indices:
146
  status_text = "当前未选择任何图片"
147
  else:
148
- labels = [f"Option {chr(65+i)}" for i in current_indices]
 
 
 
 
 
 
 
 
 
149
  status_text = "已选中: " + ", ".join(labels)
150
 
151
  return current_indices, status_text
152
 
153
- def submit_vote(user_state, current_file_paths, current_indices, is_none=False):
154
  user_id = user_state["user_id"]
155
  current_idx = user_state["index"]
156
 
@@ -159,33 +198,44 @@ def submit_vote(user_state, current_file_paths, current_indices, is_none=False):
159
 
160
  group_id = ALL_GROUP_IDS[current_idx]
161
 
162
- # 场景1: 都没有
163
  if is_none:
164
  save_user_vote(user_id, group_id, "Rejected All", "None_Satisfied")
165
  user_state["index"] += 1
166
  return get_current_question_ui(user_state)
167
 
168
- # 场景2: 提交多选
169
  if not current_indices:
170
  return (
171
  gr.update(), gr.update(),
172
- gr.update(value="❌ 请至少选择一张图片,或者点击“都不满意”"),
173
  gr.update(), gr.update(),
174
- user_state, current_file_paths, current_indices
175
  )
176
 
177
  selected_labels = []
178
  selected_methods = []
179
 
180
  for idx in current_indices:
181
- label = f"Option {chr(65+idx)}"
 
 
 
 
 
 
 
182
  selected_labels.append(label)
183
 
184
- real_path = current_file_paths[idx]
185
  filename = os.path.basename(real_path)
186
  name_no_ext = os.path.splitext(filename)[0]
187
- parts = name_no_ext.split('_', 1)
188
- method = parts[1] if len(parts) > 1 else name_no_ext
 
 
 
 
 
 
189
  selected_methods.append(method)
190
 
191
  str_labels = "; ".join(selected_labels)
@@ -196,18 +246,20 @@ def submit_vote(user_state, current_file_paths, current_indices, is_none=False):
196
  user_state["index"] += 1
197
  return get_current_question_ui(user_state)
198
 
199
- # --- 6. 界面构建 (已移除 theme 参数) ---
200
- with gr.Blocks(title="Multi-Select User Study") as demo:
201
 
202
  state_user = gr.State(lambda: {"user_id": str(uuid.uuid4())[:8], "index": 0})
203
  state_files = gr.State([])
204
- state_indices = gr.State([])
 
205
 
206
  with gr.Column():
207
  instruction_md = gr.Markdown("Loading...")
208
 
 
209
  gallery = gr.Gallery(
210
- label="请点击选择图片(可多选)",
211
  allow_preview=True,
212
  object_fit="contain",
213
  height="auto",
@@ -217,33 +269,37 @@ with gr.Blocks(title="Multi-Select User Study") as demo:
217
  status_box = gr.Textbox(value="当前未选择任何图片", label="当前选中状态", interactive=False)
218
 
219
  with gr.Row():
220
- btn_submit = gr.Button("✅ 提交选择 (Confirm Selection)", variant="primary", scale=2)
221
- btn_none = gr.Button("🚫 都不满意 (None of them)", variant="stop", scale=1)
222
 
223
  end_msg = gr.Markdown(visible=False)
224
 
 
225
  demo.load(
226
  fn=get_current_question_ui,
227
  inputs=[state_user],
228
- outputs=[instruction_md, gallery, status_box, btn_submit, end_msg, state_user, state_files, state_indices]
229
  )
230
 
 
231
  gallery.select(
232
  fn=toggle_selection,
233
- inputs=[state_indices],
234
  outputs=[state_indices, status_box]
235
  )
236
 
 
237
  btn_submit.click(
238
- fn=lambda s, f, i: submit_vote(s, f, i, is_none=False),
239
- inputs=[state_user, state_files, state_indices],
240
- outputs=[instruction_md, gallery, status_box, btn_submit, end_msg, state_user, state_files, state_indices]
241
  )
242
 
 
243
  btn_none.click(
244
- fn=lambda s, f, i: submit_vote(s, f, i, is_none=True),
245
- inputs=[state_user, state_files, state_indices],
246
- outputs=[instruction_md, gallery, status_box, btn_submit, end_msg, state_user, state_files, state_indices]
247
  )
248
 
249
  if __name__ == "__main__":
 
24
  token=TOKEN
25
  )
26
 
27
+ # --- 3. 数据加载逻辑 (区分原图和候选图) ---
28
  def load_data():
29
  groups = {}
30
  if not os.path.exists(DATA_FOLDER):
 
34
  for filename in os.listdir(DATA_FOLDER):
35
  if filename.startswith('.'): continue
36
  file_path = os.path.join(DATA_FOLDER, filename)
37
+ prefix = filename[:5] # 以前5个字符作为组ID
38
 
39
  if prefix not in groups:
40
+ # origin 存原图路径,candidates 存其他方法的图
41
+ groups[prefix] = {"origin": None, "candidates": [], "instruction": "暂无说明"}
42
 
43
+ # 图片处理
44
  if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')):
45
+ # 判断是否是原图 (文件名包含 _origin)
46
+ if "_origin" in filename.lower():
47
+ groups[prefix]["origin"] = file_path
48
+ else:
49
+ groups[prefix]["candidates"].append(file_path)
50
+
51
+ # 文本处理
52
  elif filename.lower().endswith('.txt'):
53
  try:
54
  with open(file_path, "r", encoding="utf-8") as f:
 
57
  with open(file_path, "r", encoding="gbk") as f:
58
  groups[prefix]["instruction"] = f.read()
59
 
60
+ # 过滤掉既没有原图也没有候选图的组
61
+ valid_groups = {}
62
+ for k, v in groups.items():
63
+ if v["origin"] is not None or len(v["candidates"]) > 0:
64
+ valid_groups[k] = v
65
+
66
  group_ids = list(valid_groups.keys())
67
  random.shuffle(group_ids)
68
  print(f"Loaded {len(group_ids)} groups.")
 
96
  # --- 5. 交互逻辑 ---
97
 
98
  def get_current_question_ui(user_state):
99
+ """刷新界面:原图置顶 + 候选图乱序"""
100
  current_idx = user_state["index"]
101
 
 
102
  if current_idx >= len(ALL_GROUP_IDS):
103
  return (
104
+ gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
105
+ gr.update(visible=False), gr.update(value="## 🎉 测试结束!\n感谢您的参与。", visible=True),
106
+ user_state, [], [], False # False表示当前没有原图显示
 
 
 
 
 
107
  )
108
 
 
109
  group_id = ALL_GROUP_IDS[current_idx]
110
  group_data = ALL_GROUPS[group_id]
111
 
 
112
  instruction_text = f"### 任务 ({current_idx + 1} / {len(ALL_GROUP_IDS)})\n\n{group_data['instruction']}"
113
 
114
+ # --- 构建显示列表 ---
 
 
 
 
 
115
  display_list = []
116
+ real_paths = [] # 记录真实的路径顺序
117
+
118
+ # 1. 先放原图 (如果有)
119
+ has_origin = False
120
+ if group_data["origin"]:
121
+ display_list.append((group_data["origin"], "Reference (原图)"))
122
+ real_paths.append(group_data["origin"])
123
+ has_origin = True
124
+
125
+ # 2. 再放候选图 (打乱顺序)
126
+ candidates = group_data["candidates"].copy()
127
+ random.shuffle(candidates)
128
+
129
+ for i, img_path in enumerate(candidates):
130
+ # 标签从 Option A 开始
131
  label = f"Option {chr(65+i)}"
132
  display_list.append((img_path, label))
133
+ real_paths.append(img_path)
134
 
135
+ # 动态计算列数 (原图+候选图的总数)
136
+ total_imgs = len(display_list)
137
+ # 如果总数是2(1原图+1候选),显示2列
138
+ # 如果总数是3,显示3列
139
+ # 如果总数是4或以上,显示3列换行,或者你可以改成 4
140
+ cols = min(total_imgs, 3)
141
 
142
  return (
143
  gr.update(value=instruction_text, visible=True),
 
146
  gr.update(visible=True),
147
  gr.update(visible=False),
148
  user_state,
149
+ real_paths, # 存下当前界面所有图片的真实路径
150
+ [], # 清空选中项
151
+ has_origin # 告诉前端第一张是不是原图
152
  )
153
 
154
+ def toggle_selection(evt: gr.SelectData, current_indices, has_origin):
155
+ """
156
+ 处理点击。
157
+ has_origin: 如果为 True,说明 Index 0 是原图,禁止选中。
158
+ """
159
  clicked_idx = evt.index
160
 
161
+ # --- 保护机制:如果第一张是原图,点击无效 ---
162
+ if has_origin and clicked_idx == 0:
163
+ # 你可以去掉这个if,允许选原图,但通常 User Study 不需要选 Reference
164
+ # 这里我们返回原样,不更新选中状态
165
+ return current_indices, "⚠️ 您点击的是参考原图 (Reference),请选择后面的选项。"
166
+
167
+ # 切换选中状态
168
  if clicked_idx in current_indices:
169
  current_indices.remove(clicked_idx)
170
  else:
171
  current_indices.append(clicked_idx)
 
172
  current_indices.sort()
173
 
174
+ # 生成状态文本
175
  if not current_indices:
176
  status_text = "当前未选择任何图片"
177
  else:
178
+ # 计算显示的 Label
179
+ labels = []
180
+ for idx in current_indices:
181
+ # 如果有原图,index 1 才是 Option A
182
+ if has_origin:
183
+ option_char = chr(65 + (idx - 1)) # idx=1 -> A, idx=2 -> B
184
+ else:
185
+ option_char = chr(65 + idx)
186
+ labels.append(f"Option {option_char}")
187
+
188
  status_text = "已选中: " + ", ".join(labels)
189
 
190
  return current_indices, status_text
191
 
192
+ def submit_vote(user_state, current_file_paths, current_indices, is_none=False, has_origin=False):
193
  user_id = user_state["user_id"]
194
  current_idx = user_state["index"]
195
 
 
198
 
199
  group_id = ALL_GROUP_IDS[current_idx]
200
 
 
201
  if is_none:
202
  save_user_vote(user_id, group_id, "Rejected All", "None_Satisfied")
203
  user_state["index"] += 1
204
  return get_current_question_ui(user_state)
205
 
 
206
  if not current_indices:
207
  return (
208
  gr.update(), gr.update(),
209
+ gr.update(value="❌ 请至少选择一张图片"),
210
  gr.update(), gr.update(),
211
+ user_state, current_file_paths, current_indices, has_origin
212
  )
213
 
214
  selected_labels = []
215
  selected_methods = []
216
 
217
  for idx in current_indices:
218
+ # 获取真实路径
219
+ real_path = current_file_paths[idx]
220
+
221
+ # 计算 Label (Option A/B...)
222
+ if has_origin:
223
+ label = f"Option {chr(65 + (idx - 1))}"
224
+ else:
225
+ label = f"Option {chr(65 + idx)}"
226
  selected_labels.append(label)
227
 
228
+ # 提取方法名
229
  filename = os.path.basename(real_path)
230
  name_no_ext = os.path.splitext(filename)[0]
231
+
232
+ # 如果是原图被选中了(假设你去掉了保护机制),方法名就是 origin
233
+ if "_origin" in name_no_ext:
234
+ method = "reference_origin"
235
+ else:
236
+ parts = name_no_ext.split('_', 1)
237
+ method = parts[1] if len(parts) > 1 else name_no_ext
238
+
239
  selected_methods.append(method)
240
 
241
  str_labels = "; ".join(selected_labels)
 
246
  user_state["index"] += 1
247
  return get_current_question_ui(user_state)
248
 
249
+ # --- 6. 界面构建 ---
250
+ with gr.Blocks(title="User Study") as demo:
251
 
252
  state_user = gr.State(lambda: {"user_id": str(uuid.uuid4())[:8], "index": 0})
253
  state_files = gr.State([])
254
+ state_indices = gr.State([])
255
+ state_has_origin = gr.State(False) # 记录当前页面是否有原图
256
 
257
  with gr.Column():
258
  instruction_md = gr.Markdown("Loading...")
259
 
260
+ # 图片显示
261
  gallery = gr.Gallery(
262
+ label="请参考第一张原图,选择后面最好的结果(可多选)",
263
  allow_preview=True,
264
  object_fit="contain",
265
  height="auto",
 
269
  status_box = gr.Textbox(value="当前未选择任何图片", label="当前选中状态", interactive=False)
270
 
271
  with gr.Row():
272
+ btn_submit = gr.Button("✅ 提交选择", variant="primary", scale=2)
273
+ btn_none = gr.Button("🚫 都不满意", variant="stop", scale=1)
274
 
275
  end_msg = gr.Markdown(visible=False)
276
 
277
+ # 加载第一题
278
  demo.load(
279
  fn=get_current_question_ui,
280
  inputs=[state_user],
281
+ outputs=[instruction_md, gallery, status_box, btn_submit, end_msg, state_user, state_files, state_indices, state_has_origin]
282
  )
283
 
284
+ # 点击图片
285
  gallery.select(
286
  fn=toggle_selection,
287
+ inputs=[state_indices, state_has_origin],
288
  outputs=[state_indices, status_box]
289
  )
290
 
291
+ # 提交按钮
292
  btn_submit.click(
293
+ fn=lambda s, f, i, h: submit_vote(s, f, i, is_none=False, has_origin=h),
294
+ inputs=[state_user, state_files, state_indices, state_has_origin],
295
+ outputs=[instruction_md, gallery, status_box, btn_submit, end_msg, state_user, state_files, state_indices, state_has_origin]
296
  )
297
 
298
+ # 都不满意按钮
299
  btn_none.click(
300
+ fn=lambda s, f, i, h: submit_vote(s, f, i, is_none=True, has_origin=h),
301
+ inputs=[state_user, state_files, state_indices, state_has_origin],
302
+ outputs=[instruction_md, gallery, status_box, btn_submit, end_msg, state_user, state_files, state_indices, state_has_origin]
303
  )
304
 
305
  if __name__ == "__main__":