Update app.py
Browse files
app.py
CHANGED
|
@@ -9,7 +9,7 @@ import io
|
|
| 9 |
from huggingface_hub import HfApi
|
| 10 |
from huggingface_hub.hf_api import HfHubHTTPError
|
| 11 |
import traceback
|
| 12 |
-
from itertools import combinations
|
| 13 |
|
| 14 |
# ==== 全局配置 ====
|
| 15 |
# ---- 测试模式开关 ----
|
|
@@ -93,12 +93,6 @@ if not os.path.exists(full_subdir_path):
|
|
| 93 |
else:
|
| 94 |
print(f"信息:持久化子目录 '{full_subdir_path}' 已存在。")
|
| 95 |
|
| 96 |
-
# (调试代码可以按需保留或移除)
|
| 97 |
-
# print(f"调试:检查 '{PERSISTENT_STORAGE_BASE}' 的内容:")
|
| 98 |
-
# try:
|
| 99 |
-
# for item in os.listdir(PERSISTENT_STORAGE_BASE): print(f" - Base Storage Item: {item}")
|
| 100 |
-
# except Exception as e: print(f" 错误:列出 '{PERSISTENT_STORAGE_BASE}' 内容失败: {e}")
|
| 101 |
-
|
| 102 |
GLOBAL_HISTORY_FILE = os.path.join(full_subdir_path, "global_experiment_shown_pairs.json")
|
| 103 |
if not (os.path.isdir(full_subdir_path) and os.access(full_subdir_path, os.W_OK)):
|
| 104 |
print(f"严重警告:持久化子目录 '{full_subdir_path}' 无效或不可写。")
|
|
@@ -106,13 +100,11 @@ print(f"全局历史文件将被加载/保存到: {GLOBAL_HISTORY_FILE}")
|
|
| 106 |
|
| 107 |
global_shown_pairs_cache = {}
|
| 108 |
global_history_has_unsaved_changes = False
|
| 109 |
-
exhausted_target_images = set()
|
| 110 |
|
| 111 |
def load_global_shown_pairs():
|
| 112 |
global global_shown_pairs_cache, global_history_has_unsaved_changes, exhausted_target_images
|
| 113 |
-
|
| 114 |
-
# 如果需要持久化它,也需要加入到JSON的读写逻辑中。
|
| 115 |
-
exhausted_target_images = set() # 确保每次加载时重置(如果它不是持久化的)
|
| 116 |
|
| 117 |
if not GLOBAL_HISTORY_FILE or not os.path.exists(GLOBAL_HISTORY_FILE):
|
| 118 |
print(f"信息:全局历史文件 '{GLOBAL_HISTORY_FILE}' 未找到或路径无效。将创建新的空历史记录。")
|
|
@@ -146,9 +138,7 @@ def save_global_shown_pairs():
|
|
| 146 |
if not GLOBAL_HISTORY_FILE:
|
| 147 |
print("错误:GLOBAL_HISTORY_FILE 未定义。无法保存历史。")
|
| 148 |
return False
|
| 149 |
-
# print(f"--- save_global_shown_pairs --- 当前工作目录: {os.getcwd()}") # 可按需保留
|
| 150 |
final_save_path = os.path.abspath(GLOBAL_HISTORY_FILE)
|
| 151 |
-
# print(f"--- save_global_shown_pairs --- 尝试保存到文件: {final_save_path}") # 可按需保留
|
| 152 |
try:
|
| 153 |
parent_dir = os.path.dirname(final_save_path)
|
| 154 |
if not os.path.exists(parent_dir):
|
|
@@ -162,30 +152,16 @@ def save_global_shown_pairs():
|
|
| 162 |
target_img: [sorted(list(pair_fset)) for pair_fset in pairs_set]
|
| 163 |
for target_img, pairs_set in global_shown_pairs_cache.items()
|
| 164 |
}
|
| 165 |
-
|
| 166 |
-
|
| 167 |
temp_file_path = final_save_path + ".tmp"
|
| 168 |
with open(temp_file_path, 'w', encoding='utf-8') as f:
|
| 169 |
json.dump(data_to_save, f, ensure_ascii=False, indent=2)
|
| 170 |
os.replace(temp_file_path, final_save_path)
|
| 171 |
print(f"已成功将全局已展示图片对历史保存到 '{final_save_path}'。")
|
| 172 |
global_history_has_unsaved_changes = False
|
| 173 |
-
|
| 174 |
-
# (保存后立即读取的调试代码可以按需保留或移除)
|
| 175 |
-
# print(f"调试:尝试立即读取已保存的文件 '{final_save_path}'...")
|
| 176 |
-
# try:
|
| 177 |
-
# with open(final_save_path, 'r', encoding='utf-8') as f_read: content_read = f_read.read()
|
| 178 |
-
# print(f"调试:成功读取文件内容(最多显示前500字符):\n--BEGIN FILE CONTENT--\n{content_read[:500]}\n--END FILE CONTENT--")
|
| 179 |
-
# if not content_read.strip(): print("调试:警告 - 读取到的文件内容为空或只有空白符。")
|
| 180 |
-
# try:
|
| 181 |
-
# json.loads(content_read)
|
| 182 |
-
# print("调试:读取到的文件内容是有效的JSON。")
|
| 183 |
-
# except json.JSONDecodeError as jde: print(f"调试:错误 - 读取到的文件内容不是有效的JSON: {jde}")
|
| 184 |
-
# except FileNotFoundError: print(f"调试:严重错误 - 刚刚保存的文件 '{final_save_path}' 在尝试立即读取时未找到!")
|
| 185 |
-
# except Exception as e_read: print(f"调试:读取文件 '{final_save_path}' 时发生其他错误: {e_read}")
|
| 186 |
return True
|
| 187 |
except Exception as e:
|
| 188 |
-
print(f"
|
| 189 |
return False
|
| 190 |
|
| 191 |
load_global_shown_pairs()
|
|
@@ -209,21 +185,16 @@ if REPEAT_SINGLE_TARGET_FOR_TESTING:
|
|
| 209 |
if not master_image_list:
|
| 210 |
print(f"测试模式错误:master_image_list 为空,无法进行重复单一目标图测试。")
|
| 211 |
else:
|
| 212 |
-
# test_target_image = "399162.jpg" # 您可以指定一个特定的测试图片
|
| 213 |
-
# if test_target_image in master_image_list:
|
| 214 |
-
# master_image_list = [test_target_image]
|
| 215 |
-
# print(f"测试模式:master_image_list 已成功设置为: {master_image_list}")
|
| 216 |
-
# # load_global_shown_pairs_test() # 如果需要,在这里调用
|
| 217 |
-
# else:
|
| 218 |
original_first_image = master_image_list[0]
|
| 219 |
master_image_list = [original_first_image]
|
| 220 |
-
# print(f"测试模式警告:指定测试图片 '{test_target_image}' 不在原始列表中。")
|
| 221 |
print(f"测试模式:master_image_list 已被缩减为原列表的第一个图像: {master_image_list}")
|
| 222 |
-
if not master_image_list:
|
| 223 |
print(f"关键错误:无目标图片可用 (master_image_list为空)。实验无法进行。")
|
| 224 |
-
# exit() # 考虑是否退出
|
| 225 |
|
| 226 |
# ==== 辅助函数 ====
|
|
|
|
|
|
|
|
|
|
| 227 |
def get_next_trial_info(current_trial_idx_in_run, current_run_image_list_for_trial, num_trials_in_this_run_for_trial):
|
| 228 |
global TARGET_DIR, METHOD_ROOTS, SUBJECTS, SENTINEL_TRIAL_INTERVAL
|
| 229 |
global global_shown_pairs_cache, global_history_has_unsaved_changes, exhausted_target_images
|
|
@@ -235,31 +206,34 @@ def get_next_trial_info(current_trial_idx_in_run, current_run_image_list_for_tri
|
|
| 235 |
target_full_path = os.path.join(TARGET_DIR, img_filename_original)
|
| 236 |
trial_number_for_display = current_trial_idx_in_run + 1
|
| 237 |
|
| 238 |
-
|
| 239 |
-
|
|
|
|
| 240 |
|
| 241 |
for m_root_path in METHOD_ROOTS:
|
| 242 |
method_name = os.path.basename(m_root_path)
|
| 243 |
-
|
| 244 |
-
|
| 245 |
subjects_for_method = SUBJECTS
|
| 246 |
if method_name.lower() == "takagi":
|
| 247 |
if "subj01" in SUBJECTS:
|
| 248 |
subjects_for_method = ["subj01"]
|
| 249 |
else:
|
| 250 |
continue
|
| 251 |
-
|
| 252 |
for s_id in subjects_for_method:
|
| 253 |
base, ext = os.path.splitext(img_filename_original)
|
| 254 |
reconstructed_filename = f"{base}_0{ext}"
|
| 255 |
candidate_path = os.path.join(m_root_path, s_id, reconstructed_filename)
|
| 256 |
if os.path.exists(candidate_path):
|
| 257 |
internal_label = f"{method_name}/{s_id}/{reconstructed_filename}"
|
| 258 |
-
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
| 260 |
else:
|
| 261 |
-
|
| 262 |
-
|
| 263 |
|
| 264 |
trial_info = {"image_id": img_filename_original, "target_path": target_full_path, "cur_no": trial_number_for_display, "is_sentinel": False,
|
| 265 |
"left_display_label": "N/A", "left_internal_label": "N/A", "left_path": None,
|
|
@@ -268,13 +242,15 @@ def get_next_trial_info(current_trial_idx_in_run, current_run_image_list_for_tri
|
|
| 268 |
is_potential_sentinel_trial = (trial_number_for_display > 0 and trial_number_for_display % SENTINEL_TRIAL_INTERVAL == 0)
|
| 269 |
|
| 270 |
if is_potential_sentinel_trial:
|
| 271 |
-
|
|
|
|
|
|
|
|
|
|
| 272 |
else:
|
| 273 |
-
# ... (哨兵试验逻辑不变) ...
|
| 274 |
print(f"生成哨兵试验 for '{img_filename_original}' (trial {trial_number_for_display})")
|
| 275 |
trial_info["is_sentinel"] = True
|
| 276 |
sentinel_candidate_target_tuple = ("目标图像", target_full_path)
|
| 277 |
-
random_reconstruction_candidate_tuple = random.choice(
|
| 278 |
candidates_for_sentinel = [
|
| 279 |
(("目标图像", target_full_path), sentinel_candidate_target_tuple[0]),
|
| 280 |
(("重建图", random_reconstruction_candidate_tuple[1]), random_reconstruction_candidate_tuple[0])
|
|
@@ -285,20 +261,22 @@ def get_next_trial_info(current_trial_idx_in_run, current_run_image_list_for_tri
|
|
| 285 |
"right_display_label": candidates_for_sentinel[1][0][0], "right_path": candidates_for_sentinel[1][0][1], "right_internal_label": candidates_for_sentinel[1][1],
|
| 286 |
})
|
| 287 |
else: # 常规试验
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
|
|
|
| 293 |
return None, current_trial_idx_in_run
|
| 294 |
|
| 295 |
target_global_history_set = global_shown_pairs_cache.setdefault(img_filename_original, set())
|
|
|
|
|
|
|
| 296 |
all_possible_pairs_in_pool = []
|
| 297 |
-
for
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
|
| 303 |
unseen_globally_pairs_with_data = [
|
| 304 |
item for item in all_possible_pairs_in_pool if item[1] not in target_global_history_set
|
|
@@ -311,14 +289,12 @@ def get_next_trial_info(current_trial_idx_in_run, current_run_image_list_for_tri
|
|
| 311 |
chosen_pair_frozenset = chosen_pair_data_and_labels[1]
|
| 312 |
target_global_history_set.add(chosen_pair_frozenset)
|
| 313 |
global_history_has_unsaved_changes = True
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
if all_possible_pairs_in_pool: # 确保池中至少能形成一对
|
| 318 |
print(f"目标图 '{img_filename_original}' 将被标记为已耗尽,未来轮次中将被跳过。")
|
| 319 |
exhausted_target_images.add(img_filename_original)
|
| 320 |
-
|
| 321 |
-
return None, current_trial_idx_in_run # 返回 None,表示此图片本次无法生成新试验
|
| 322 |
|
| 323 |
display_order_candidates = list(selected_candidates_tuples)
|
| 324 |
if random.random() > 0.5:
|
|
@@ -334,7 +310,7 @@ def save_single_log_to_hf_dataset(log_entry, user_identifier_str):
|
|
| 334 |
global DATASET_REPO_ID, INDIVIDUAL_LOGS_FOLDER
|
| 335 |
if not isinstance(log_entry, dict):
|
| 336 |
print(f"错误:单个日志条目不是字典格式,无法保存:{log_entry}")
|
| 337 |
-
return False
|
| 338 |
current_user_id = user_identifier_str if user_identifier_str else "unknown_user_session"
|
| 339 |
identifier_safe = str(current_user_id).replace('.', '_').replace(':', '_').replace('/', '_')
|
| 340 |
print(f"用户 {identifier_safe} - 准备保存单条日志 for image {log_entry.get('image_id', 'Unknown')}...")
|
|
@@ -342,12 +318,11 @@ def save_single_log_to_hf_dataset(log_entry, user_identifier_str):
|
|
| 342 |
token = os.getenv("HF_TOKEN")
|
| 343 |
if not token:
|
| 344 |
print("错误:环境变量 HF_TOKEN 未设置。无法保存单条日志到Dataset。")
|
| 345 |
-
return False
|
| 346 |
if not DATASET_REPO_ID:
|
| 347 |
print("错误:DATASET_REPO_ID 未配置。无法保存单条日志到Dataset。")
|
| 348 |
-
return False
|
| 349 |
api = HfApi(token=token)
|
| 350 |
-
# ... (文件名和路径生成逻辑不变) ...
|
| 351 |
image_id_safe_for_filename = os.path.splitext(log_entry.get("image_id", "unknown_img"))[0].replace('.', '_').replace(':', '_').replace('/', '_')
|
| 352 |
file_creation_timestamp_str = datetime.now().strftime('%Y%m%d_%H%M%S_%f')
|
| 353 |
unique_filename = (f"run{log_entry.get('run_no', 'X')}_trial{log_entry.get('trial_sequence_in_run', 'Y')}_img{image_id_safe_for_filename}_{file_creation_timestamp_str}.json")
|
|
@@ -357,10 +332,8 @@ def save_single_log_to_hf_dataset(log_entry, user_identifier_str):
|
|
| 357 |
json_content = json.dumps(log_entry, ensure_ascii=False, indent=2)
|
| 358 |
except Exception as json_err:
|
| 359 |
print(f"错误:序列化单条日志时出错: {log_entry}. 错误: {json_err}")
|
| 360 |
-
# 即使序列化失败,也尝试记录一个最小化的错误日志
|
| 361 |
error_log_content = {"error": "serialization_failed_single", "original_data_keys": list(log_entry.keys()) if isinstance(log_entry, dict) else None, "timestamp": datetime.now().isoformat()}
|
| 362 |
json_content = json.dumps(error_log_content, ensure_ascii=False, indent=2)
|
| 363 |
-
# return False # 序列化失败也认为是一种保存失败
|
| 364 |
|
| 365 |
log_bytes = json_content.encode('utf-8')
|
| 366 |
file_like_object = io.BytesIO(log_bytes)
|
|
@@ -373,22 +346,22 @@ def save_single_log_to_hf_dataset(log_entry, user_identifier_str):
|
|
| 373 |
commit_message=(f"Log choice: img {log_entry.get('image_id', 'N/A')}, run {log_entry.get('run_no', 'N/A')}, trial {log_entry.get('trial_sequence_in_run', 'N/A')} by {identifier_safe}")
|
| 374 |
)
|
| 375 |
print(f"单条日志已成功保存到 HF Dataset: {DATASET_REPO_ID}/{path_in_repo}")
|
| 376 |
-
return True
|
| 377 |
except HfHubHTTPError as hf_http_error:
|
| 378 |
print(f"保存单条日志到 Hugging Face Dataset 时发生 HTTP 错误 (可能被限流或权限问题): {hf_http_error}")
|
| 379 |
traceback.print_exc()
|
| 380 |
-
return False
|
| 381 |
except Exception as e:
|
| 382 |
print(f"保存单条日志 (image {log_entry.get('image_id', 'Unknown')}, user {identifier_safe}) 到 Hugging Face Dataset 时发生严重错误: {e}")
|
| 383 |
traceback.print_exc()
|
| 384 |
-
return False
|
| 385 |
|
| 386 |
# ==== 批量保存用户选择日志函数 (确保返回 True/False) ====
|
| 387 |
def save_collected_logs_batch(list_of_log_entries, user_identifier_str, batch_identifier):
|
| 388 |
global DATASET_REPO_ID, BATCH_LOG_FOLDER
|
| 389 |
if not list_of_log_entries:
|
| 390 |
print("批量保存用户日志:没有累积的日志。")
|
| 391 |
-
return True
|
| 392 |
identifier_safe = str(user_identifier_str if user_identifier_str else "unknown_user_session").replace('.', '_').replace(':', '_').replace('/', '_').replace(' ', '_')
|
| 393 |
print(f"用户 {identifier_safe} - 准备批量保存 {len(list_of_log_entries)} 条选择日志 (批次标识: {batch_identifier})...")
|
| 394 |
try:
|
|
@@ -400,7 +373,6 @@ def save_collected_logs_batch(list_of_log_entries, user_identifier_str, batch_id
|
|
| 400 |
print("错误:DATASET_REPO_ID 未配置。无法批量保存选择日志。")
|
| 401 |
return False
|
| 402 |
api = HfApi(token=token)
|
| 403 |
-
# ... (文件名和内容生成逻辑不变) ...
|
| 404 |
timestamp_str = datetime.now().strftime('%Y%m%d_%H%M%S_%f')
|
| 405 |
batch_filename = f"batch_user-{identifier_safe}_id-{batch_identifier}_{timestamp_str}_logs-{len(list_of_log_entries)}.jsonl"
|
| 406 |
path_in_repo = f"{BATCH_LOG_FOLDER}/{identifier_safe}/{batch_filename}"
|
|
@@ -415,7 +387,7 @@ def save_collected_logs_batch(list_of_log_entries, user_identifier_str, batch_id
|
|
| 415 |
|
| 416 |
if not jsonl_content.strip():
|
| 417 |
print(f"用户 {identifier_safe} (批次 {batch_identifier}) 无可序列化选择日志。")
|
| 418 |
-
return True
|
| 419 |
|
| 420 |
log_bytes = jsonl_content.encode('utf-8')
|
| 421 |
file_like_object = io.BytesIO(log_bytes)
|
|
@@ -456,12 +428,10 @@ def process_experiment_step(
|
|
| 456 |
user_ip_fallback = request.client.host if request else "unknown_ip"
|
| 457 |
user_identifier_for_logging = output_s_user_session_id if output_s_user_session_id else user_ip_fallback
|
| 458 |
|
| 459 |
-
# outputs_ui_components_definition 长度现在是 11
|
| 460 |
-
# (target_img, left_img, right_img, left_lbl, right_lbl, status_text, progress_text, btn_start, btn_left, btn_right, file_out_placeholder)
|
| 461 |
len_ui_outputs = len(outputs_ui_components_definition)
|
| 462 |
|
| 463 |
def create_ui_error_tuple(message, progress_msg_text, stop_experiment=False):
|
| 464 |
-
btn_start_interactive = not stop_experiment
|
| 465 |
btn_choices_interactive = not stop_experiment
|
| 466 |
return (gr.update(visible=False),) * 3 + \
|
| 467 |
("", "") + \
|
|
@@ -474,7 +444,6 @@ def process_experiment_step(
|
|
| 474 |
|
| 475 |
if action_type == "record_choice":
|
| 476 |
if output_s_current_trial_data.get("data") and output_s_current_trial_data["data"].get("left_internal_label"):
|
| 477 |
-
# ... (log_entry 创建逻辑不变) ...
|
| 478 |
chosen_internal_label = (output_s_current_trial_data["data"]["left_internal_label"] if choice_value == "left" else output_s_current_trial_data["data"]["right_internal_label"])
|
| 479 |
parsed_chosen_method, parsed_chosen_subject, parsed_chosen_filename = "N/A", "N/A", "N/A"
|
| 480 |
if chosen_internal_label == "目标图像": parsed_chosen_method, parsed_chosen_subject, parsed_chosen_filename = "TARGET", "GT", output_s_current_trial_data["data"]["image_id"]
|
|
@@ -496,7 +465,6 @@ def process_experiment_step(
|
|
| 496 |
output_s_user_logs.append(log_entry)
|
| 497 |
print(f"用户 {user_identifier_for_logging} 记录选择 (img: {log_entry['image_id']})。当前批次日志数: {len(output_s_user_logs)}")
|
| 498 |
|
| 499 |
-
# 尝试批量保存日志
|
| 500 |
if len(output_s_user_logs) >= LOG_BATCH_SIZE:
|
| 501 |
print(f"累积用户选择日志达到 {LOG_BATCH_SIZE} 条,准备批量保存...")
|
| 502 |
batch_id_for_filename = f"run{output_s_run_no}_trialidx{output_s_trial_idx}_logcount{len(output_s_user_logs)}"
|
|
@@ -506,14 +474,12 @@ def process_experiment_step(
|
|
| 506 |
output_s_user_logs = []
|
| 507 |
else:
|
| 508 |
print("严重错误:批量用户选择日志保存失败。实验无法继续。")
|
| 509 |
-
# 更新UI以显示错误并停止实验
|
| 510 |
error_message_ui = "错误:日志保存失败,可能是网络问题或API限流。实验已停止,请联系管理员。"
|
| 511 |
progress_message_ui = f"用户ID: {user_id_display_text} | 实验因错误停止在第 {output_s_run_no} 轮,试验 {output_s_trial_idx+1}"
|
| 512 |
error_ui_updates = create_ui_error_tuple(error_message_ui, progress_message_ui, stop_experiment=True)
|
| 513 |
return output_s_trial_idx, output_s_run_no, output_s_user_logs, output_s_current_trial_data, \
|
| 514 |
output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *error_ui_updates
|
| 515 |
|
| 516 |
-
# 尝试保存全局历史
|
| 517 |
if global_history_has_unsaved_changes:
|
| 518 |
print("检测到全局图片对历史自上次保存后有更新,将一并保存...")
|
| 519 |
if not save_global_shown_pairs():
|
|
@@ -525,18 +491,15 @@ def process_experiment_step(
|
|
| 525 |
output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *error_ui_updates
|
| 526 |
else:
|
| 527 |
print(f"用户 {user_identifier_for_logging} 错误:记录选择时当前试验数据为空或缺少internal_label!")
|
| 528 |
-
|
| 529 |
-
error_ui_updates = create_ui_error_tuple("记录选择时内部错误。", f"用户ID: {user_id_display_text} | 进度:{output_s_trial_idx}/{output_s_num_trials_this_run}", stop_experiment=False) # 这种错误不一定停止实验
|
| 530 |
return output_s_trial_idx, output_s_run_no, output_s_user_logs, output_s_current_trial_data, output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *error_ui_updates
|
| 531 |
|
| 532 |
-
# ... (start_experiment 和后续的试验获取逻辑基本保持不变) ...
|
| 533 |
-
# --- 在 start_experiment 开始新轮次前,或在轮次结束时,也需要检查并尝试保存剩余日志和全局历史 ---
|
| 534 |
if action_type == "start_experiment":
|
| 535 |
is_first = (output_s_num_trials_this_run == 0 and output_s_trial_idx == 0 and output_s_run_no == 1)
|
| 536 |
is_completed_for_restart = (output_s_num_trials_this_run > 0 and output_s_trial_idx >= output_s_num_trials_this_run)
|
| 537 |
|
| 538 |
-
if is_completed_for_restart:
|
| 539 |
-
if output_s_user_logs:
|
| 540 |
print(f"轮次 {output_s_run_no-1} 结束,尝试保存剩余的 {len(output_s_user_logs)} 条用户选择日志...")
|
| 541 |
batch_id_for_filename = f"run{output_s_run_no-1}_final_logcount{len(output_s_user_logs)}"
|
| 542 |
if not save_collected_logs_batch(list(output_s_user_logs), user_identifier_for_logging, batch_id_for_filename):
|
|
@@ -546,7 +509,7 @@ def process_experiment_step(
|
|
| 546 |
error_ui_updates = create_ui_error_tuple(error_message_ui, progress_message_ui, stop_experiment=True)
|
| 547 |
return output_s_trial_idx, output_s_run_no-1, output_s_user_logs, output_s_current_trial_data, \
|
| 548 |
output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *error_ui_updates
|
| 549 |
-
output_s_user_logs = []
|
| 550 |
|
| 551 |
if global_history_has_unsaved_changes:
|
| 552 |
print("轮次结束,尝试保存全局图片对历史...")
|
|
@@ -558,29 +521,25 @@ def process_experiment_step(
|
|
| 558 |
return output_s_trial_idx, output_s_run_no-1, output_s_user_logs, output_s_current_trial_data, \
|
| 559 |
output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *error_ui_updates
|
| 560 |
|
| 561 |
-
# ... (后续的 start_experiment 逻辑不变,如选择图片,重置状态等) ...
|
| 562 |
if is_first or is_completed_for_restart:
|
| 563 |
if is_completed_for_restart: output_s_run_no += 1
|
| 564 |
available_master_images = [img for img in master_image_list if img not in exhausted_target_images]
|
| 565 |
print(f"开始轮次 {output_s_run_no}: 从 {len(master_image_list)}个总目标图片中筛选,可用图片 {len(available_master_images)}个 (已排除 {len(exhausted_target_images)}个已耗尽图片).")
|
| 566 |
if not available_master_images:
|
| 567 |
-
# ... (所有图片耗尽的逻辑不变) ...
|
| 568 |
msg = "所有目标图片的所有唯一图片对均已展示完毕!感谢您的参与。"
|
| 569 |
prog_text = f"用户ID: {user_id_display_text} | 实验完成!"
|
| 570 |
-
# 在这里,如果还有未保存的日志,也应该尝试保存
|
| 571 |
if output_s_user_logs:
|
| 572 |
print(f"最终轮次结束,尝试保存剩余的 {len(output_s_user_logs)} 条用户选择日志...")
|
| 573 |
-
batch_id_for_filename = f"run{output_s_run_no-1}_final_logcount{len(output_s_user_logs)}"
|
| 574 |
-
save_collected_logs_batch(list(output_s_user_logs), user_identifier_for_logging, batch_id_for_filename)
|
| 575 |
output_s_user_logs = []
|
| 576 |
if global_history_has_unsaved_changes:
|
| 577 |
print("实验最终结束,尝试保存全局图片对历史...")
|
| 578 |
-
save_global_shown_pairs()
|
| 579 |
|
| 580 |
-
ui_updates = list(create_ui_error_tuple(msg, prog_text, stop_experiment=True))
|
| 581 |
return 0, output_s_run_no, [], {}, output_s_user_session_id, [], 0, *tuple(ui_updates)
|
| 582 |
|
| 583 |
-
# ... (后续 start_experiment 逻辑:选择图片,重置 trial_idx, user_logs, current_trial_data 等)
|
| 584 |
if REPEAT_SINGLE_TARGET_FOR_TESTING and available_master_images:
|
| 585 |
print(f"测试模式 (重复单一目标图) 已激活。")
|
| 586 |
single_image_to_repeat = available_master_images[0]
|
|
@@ -592,44 +551,31 @@ def process_experiment_step(
|
|
| 592 |
current_run_max_trials = NUM_TRIALS_PER_RUN
|
| 593 |
run_size = min(num_really_avail, current_run_max_trials)
|
| 594 |
if run_size == 0:
|
| 595 |
-
error_ui = create_ui_error_tuple("错误: 可用图片采样数为0!", f"用户ID: {user_id_display_text} | 进度: 0/0", stop_experiment=False)
|
| 596 |
return 0, output_s_run_no, output_s_user_logs, {}, output_s_user_session_id, [], 0, *error_ui
|
| 597 |
output_s_current_run_image_list = random.sample(available_master_images, run_size)
|
| 598 |
output_s_num_trials_this_run = run_size
|
| 599 |
|
| 600 |
output_s_trial_idx = 0
|
| 601 |
-
output_s_current_trial_data = {}
|
| 602 |
-
|
| 603 |
-
# 如果是 is_first,它本身就是空的
|
| 604 |
-
if is_first: # 只有第一次启动时分配新用户ID
|
| 605 |
timestamp_str = datetime.now().strftime('%Y%m%d%H%M%S%f'); random_val = random.randint(10000, 99999)
|
| 606 |
-
if not output_s_user_session_id:
|
| 607 |
output_s_user_session_id = f"user_{timestamp_str}_{random_val}"; user_identifier_for_logging = output_s_user_session_id
|
| 608 |
else:
|
| 609 |
-
user_identifier_for_logging = output_s_user_session_id
|
| 610 |
print(f"用户会话ID: {output_s_user_session_id}")
|
| 611 |
print(f"开始/继续轮次 {output_s_run_no} (用户ID: {output_s_user_session_id}). 本轮共 {output_s_num_trials_this_run} 个试验。")
|
| 612 |
-
else:
|
| 613 |
print(f"用户 {user_identifier_for_logging} 在第 {output_s_run_no} 轮,试验 {output_s_trial_idx} 点击开始,但轮次未完成。忽略。")
|
| 614 |
no_change_ui = create_no_change_tuple()
|
| 615 |
return output_s_trial_idx, output_s_run_no, output_s_user_logs, output_s_current_trial_data, output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *no_change_ui
|
| 616 |
|
| 617 |
-
|
| 618 |
-
# --- 获取下一个试验并更新UI的逻辑 ---
|
| 619 |
-
# ... (这部分逻辑与您上一版包含while循环尝试获取有效试验的代码基本相同) ...
|
| 620 |
-
# ... (确保所有 yield 和 return 都返回7个状态变量 + 11个UI更新) ...
|
| 621 |
-
# ... (并且在循环结束仍未找到 trial_info 时,也尝试保存剩余日志和全局历史) ...
|
| 622 |
current_actual_trial_index_for_get_next = output_s_trial_idx
|
| 623 |
-
|
| 624 |
-
|
| 625 |
if current_actual_trial_index_for_get_next >= output_s_num_trials_this_run and output_s_num_trials_this_run > 0:
|
| 626 |
-
# (这个条件实际上在 record_choice 后,或 start_experiment 的 is_completed_for_restart 后,output_s_trial_idx 可能已经等于或超过)
|
| 627 |
-
# (这里的逻辑主要是为了在 yield 返回前,如果恰好是轮次结束,则正确显示结束信息)
|
| 628 |
-
# (大部分轮次结束逻辑已移到 action_type == "record_choice" 和 "start_experiment" 的 is_completed_for_restart 部分)
|
| 629 |
-
print(f"调试: 在获取下一个试验前,检测到轮次 {output_s_run_no} 可能已结束 (idx: {current_actual_trial_index_for_get_next} >= num_trials: {output_s_num_trials_this_run})")
|
| 630 |
-
# (如果确实结束,应该已经在前面返回了,这里是一个保险或冗余检查)
|
| 631 |
prog_text = f"用户ID: {output_s_user_session_id} | 进度:{output_s_num_trials_this_run}/{output_s_num_trials_this_run} | 第 {output_s_run_no} 轮 🎉"
|
| 632 |
-
ui_updates = list(create_ui_error_tuple(f"🎉 第 {output_s_run_no} 轮完成!请点击“开始试验 / 下一轮”继续。", prog_text, stop_experiment=False))
|
| 633 |
ui_updates[7]=gr.update(interactive=True); ui_updates[8]=gr.update(interactive=False); ui_updates[9]=gr.update(interactive=False)
|
| 634 |
ui_updates[0]=gr.update(value=None,visible=False); ui_updates[1]=gr.update(value=None,visible=False); ui_updates[2]=gr.update(value=None,visible=False)
|
| 635 |
yield output_s_trial_idx, output_s_run_no, output_s_user_logs, {"data": None}, output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *ui_updates; return
|
|
@@ -654,24 +600,20 @@ def process_experiment_step(
|
|
| 654 |
|
| 655 |
if _trial_info_candidate is not None:
|
| 656 |
trial_info = _trial_info_candidate
|
| 657 |
-
output_s_trial_idx = _returned_next_idx
|
| 658 |
break
|
| 659 |
else:
|
| 660 |
print(f"信息:目标图 '{current_target_image_for_trial}' 无法生成有效试验。尝试列表中的下一个。")
|
| 661 |
next_s_trial_idx_for_state_loop +=1
|
| 662 |
output_s_trial_idx = next_s_trial_idx_for_state_loop
|
| 663 |
-
# exhausted_target_images 应该在 get_next_trial_info 内部被更新
|
| 664 |
|
| 665 |
if trial_info is None:
|
| 666 |
print(f"轮次 {output_s_run_no} 中没有更多可用的有效试验了。结束本轮。")
|
| 667 |
-
# 尝试保存剩余日志
|
| 668 |
if output_s_user_logs:
|
| 669 |
print(f"轮次 {output_s_run_no} 无更多有效试验,尝试保存剩余 {len(output_s_user_logs)} 条日志...")
|
| 670 |
batch_id_for_filename = f"run{output_s_run_no}_no_more_trials_logcount{len(output_s_user_logs)}"
|
| 671 |
if not save_collected_logs_batch(list(output_s_user_logs), user_identifier_for_logging, batch_id_for_filename):
|
| 672 |
-
|
| 673 |
-
print("严重错误:保存剩余日志失败。���验可能需要停止。")
|
| 674 |
-
# 可以选择在这里返回一个停止实验的UI更新
|
| 675 |
output_s_user_logs = []
|
| 676 |
if global_history_has_unsaved_changes:
|
| 677 |
print("轮次无更多有效试验,尝试保存全局图片对历史...")
|
|
@@ -697,17 +639,13 @@ def process_experiment_step(
|
|
| 697 |
|
| 698 |
ui_show_candidates_updates = list(create_no_change_tuple())
|
| 699 |
ui_show_candidates_updates[0]=gr.update(value=None,visible=False); ui_show_candidates_updates[1]=gr.update(value=trial_info["left_path"],visible=True); ui_show_candidates_updates[2]=gr.update(value=trial_info["right_path"],visible=True)
|
| 700 |
-
ui_show_candidates_updates[3]=gr.update(value=trial_info["left_display_label"], visible=True); ui_show_candidates_updates[4]=gr.update(value=trial_info["right_display_label"], visible=True)
|
| 701 |
ui_show_candidates_updates[5]="请选择更像原图的一张"; ui_show_candidates_updates[6]=prog_text
|
| 702 |
ui_show_candidates_updates[7]=gr.update(interactive=False); ui_show_candidates_updates[8]=gr.update(interactive=True); ui_show_candidates_updates[9]=gr.update(interactive=True)
|
| 703 |
yield output_s_trial_idx, output_s_run_no, output_s_user_logs, output_s_current_trial_data, output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *ui_show_candidates_updates
|
| 704 |
|
| 705 |
-
# ==== Gradio UI 定义 和 程序入口
|
| 706 |
-
|
| 707 |
-
# ... (with gr.Blocks(...) as demo: ...) ...
|
| 708 |
-
# ... (if __name__ == "__main__": ...) ...
|
| 709 |
-
# (此处省略这部分,因为它们与您包含下载按钮的上一版代码相同)
|
| 710 |
-
def handle_download_history_file(): # 确保这个函数在UI定义之前
|
| 711 |
global GLOBAL_HISTORY_FILE
|
| 712 |
if os.path.exists(GLOBAL_HISTORY_FILE):
|
| 713 |
try:
|
|
@@ -785,21 +723,21 @@ with gr.Blocks(css=CSS, title="图像重建主观评估") as demo:
|
|
| 785 |
with experiment_container:
|
| 786 |
gr.Markdown("## 🧠 图像重建主观评估实验"); gr.Markdown(f"每轮实验大约有 {NUM_TRIALS_PER_RUN} 次比较。")
|
| 787 |
with gr.Row():
|
| 788 |
-
with gr.Column(scale=1, min_width=300): left_img = gr.Image(label="左候选图", visible=False, height=400, interactive=False); left_lbl = gr.Textbox(label="左图信息", value="", visible=True, interactive=False, max_lines=1); btn_left = gr.Button("选择左图 (更相似)", interactive=False, elem_classes="compact_button")
|
| 789 |
-
with gr.Column(scale=1, min_width=300): right_img = gr.Image(label="右候选图", visible=False, height=400, interactive=False); right_lbl = gr.Textbox(label="右图信息",value="", visible=True, interactive=False, max_lines=1); btn_right = gr.Button("选择右图 (更相似)", interactive=False, elem_classes="compact_button")
|
| 790 |
with gr.Row(): target_img = gr.Image(label="目标图像 (观察3秒后消失)", visible=False, height=400, interactive=False)
|
| 791 |
with gr.Row(): status_text = gr.Markdown(value="请点击“开始试验 / 下一轮”按钮。")
|
| 792 |
-
with gr.Row(): progress_text = gr.Markdown()
|
| 793 |
with gr.Row():
|
| 794 |
btn_start = gr.Button("开始试验 / 下一轮")
|
| 795 |
btn_download_json = gr.Button("下载JSON历史记录")
|
| 796 |
json_download_output = gr.File(label="下载的文件会在此处提供", interactive=False)
|
| 797 |
-
file_out_placeholder = gr.File(label=" ", visible=False, interactive=False)
|
| 798 |
|
| 799 |
outputs_ui_components_definition = [
|
| 800 |
target_img, left_img, right_img, left_lbl, right_lbl, status_text, progress_text,
|
| 801 |
btn_start, btn_left, btn_right, file_out_placeholder
|
| 802 |
-
]
|
| 803 |
click_inputs_base = [
|
| 804 |
s_trial_index, s_run_no, s_user_logs, s_current_trial_data, s_user_session_id,
|
| 805 |
s_current_run_image_list, s_num_trials_this_run
|
|
@@ -815,33 +753,13 @@ with gr.Blocks(css=CSS, title="图像重建主观评估") as demo:
|
|
| 815 |
btn_right.click(fn=partial(process_experiment_step, action_type="record_choice", choice_value="right"), inputs=click_inputs_base, outputs=event_outputs, queue=True)
|
| 816 |
btn_download_json.click(fn=handle_download_history_file, inputs=None, outputs=[json_download_output, status_text])
|
| 817 |
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
if __name__ == "__main__":
|
| 838 |
-
# ... (您的 __main__ 部分代码保持不变,包含所有路径检查和 Gradio 启动) ...
|
| 839 |
if not master_image_list: print("\n关键错误:程序无法启动,因无目标图片。"); exit()
|
| 840 |
else:
|
| 841 |
print(f"从 '{TARGET_DIR}' 加载 {len(master_image_list)} 张目标���片。")
|
| 842 |
if not METHOD_ROOTS: print(f"警告: '{BASE_IMAGE_DIR}' 无候选方法子目录。")
|
| 843 |
if not SUBJECTS: print("警告: SUBJECTS 列表为空。")
|
| 844 |
-
print(f"用户选择日志保存到 Dataset: '{DATASET_REPO_ID}' 的 '{BATCH_LOG_FOLDER}/ 文件夹")
|
| 845 |
if not os.getenv("HF_TOKEN"): print("警告: HF_TOKEN 未设置。日志无法保存到Hugging Face Dataset。\n 请在 Space Secrets 中设置 HF_TOKEN。")
|
| 846 |
else: print("HF_TOKEN 已找到。")
|
| 847 |
print(f"全局图片对历史将从 '{GLOBAL_HISTORY_FILE}' 加载/保存到此文件。")
|
|
@@ -865,9 +783,9 @@ if __name__ == "__main__":
|
|
| 865 |
except Exception as e_mkdir_main:
|
| 866 |
print(f"错误:在 main 中创建目录 '{PERSISTENT_STORAGE_BASE}' 失败: {e_mkdir_main}")
|
| 867 |
|
| 868 |
-
final_allowed_paths = list(set(allowed_paths_list))
|
| 869 |
if final_allowed_paths:
|
| 870 |
-
print(f"Gradio
|
| 871 |
else:
|
| 872 |
print("警告:没有有效的 allowed_paths 被配置。Gradio文件访问可能受限。")
|
| 873 |
|
|
|
|
| 9 |
from huggingface_hub import HfApi
|
| 10 |
from huggingface_hub.hf_api import HfHubHTTPError
|
| 11 |
import traceback
|
| 12 |
+
from itertools import combinations, product
|
| 13 |
|
| 14 |
# ==== 全局配置 ====
|
| 15 |
# ---- 测试模式开关 ----
|
|
|
|
| 93 |
else:
|
| 94 |
print(f"信息:持久化子目录 '{full_subdir_path}' 已存在。")
|
| 95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
GLOBAL_HISTORY_FILE = os.path.join(full_subdir_path, "global_experiment_shown_pairs.json")
|
| 97 |
if not (os.path.isdir(full_subdir_path) and os.access(full_subdir_path, os.W_OK)):
|
| 98 |
print(f"严重警告:持久化子目录 '{full_subdir_path}' 无效或不可写。")
|
|
|
|
| 100 |
|
| 101 |
global_shown_pairs_cache = {}
|
| 102 |
global_history_has_unsaved_changes = False
|
| 103 |
+
exhausted_target_images = set()
|
| 104 |
|
| 105 |
def load_global_shown_pairs():
|
| 106 |
global global_shown_pairs_cache, global_history_has_unsaved_changes, exhausted_target_images
|
| 107 |
+
exhausted_target_images = set()
|
|
|
|
|
|
|
| 108 |
|
| 109 |
if not GLOBAL_HISTORY_FILE or not os.path.exists(GLOBAL_HISTORY_FILE):
|
| 110 |
print(f"信息:全局历史文件 '{GLOBAL_HISTORY_FILE}' 未找到或路径无效。将创建新的空历史记录。")
|
|
|
|
| 138 |
if not GLOBAL_HISTORY_FILE:
|
| 139 |
print("错误:GLOBAL_HISTORY_FILE 未定义。无法保存历史。")
|
| 140 |
return False
|
|
|
|
| 141 |
final_save_path = os.path.abspath(GLOBAL_HISTORY_FILE)
|
|
|
|
| 142 |
try:
|
| 143 |
parent_dir = os.path.dirname(final_save_path)
|
| 144 |
if not os.path.exists(parent_dir):
|
|
|
|
| 152 |
target_img: [sorted(list(pair_fset)) for pair_fset in pairs_set]
|
| 153 |
for target_img, pairs_set in global_shown_pairs_cache.items()
|
| 154 |
}
|
| 155 |
+
|
|
|
|
| 156 |
temp_file_path = final_save_path + ".tmp"
|
| 157 |
with open(temp_file_path, 'w', encoding='utf-8') as f:
|
| 158 |
json.dump(data_to_save, f, ensure_ascii=False, indent=2)
|
| 159 |
os.replace(temp_file_path, final_save_path)
|
| 160 |
print(f"已成功将全局已展示图片对历史保存到 '{final_save_path}'。")
|
| 161 |
global_history_has_unsaved_changes = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
return True
|
| 163 |
except Exception as e:
|
| 164 |
+
print(f"错误:保存全局历史文件 '{final_save_path}' 失败: {e}")
|
| 165 |
return False
|
| 166 |
|
| 167 |
load_global_shown_pairs()
|
|
|
|
| 185 |
if not master_image_list:
|
| 186 |
print(f"测试模式错误:master_image_list 为空,无法进行重复单一目标图测试。")
|
| 187 |
else:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
original_first_image = master_image_list[0]
|
| 189 |
master_image_list = [original_first_image]
|
|
|
|
| 190 |
print(f"测试模式:master_image_list 已被缩减为原列表的第一个图像: {master_image_list}")
|
| 191 |
+
if not master_image_list:
|
| 192 |
print(f"关键错误:无目标图片可用 (master_image_list为空)。实验无法进行。")
|
|
|
|
| 193 |
|
| 194 |
# ==== 辅助函数 ====
|
| 195 |
+
# #############################################################################
|
| 196 |
+
# ############# 函数修改点:get_next_trial_info ################################
|
| 197 |
+
# #############################################################################
|
| 198 |
def get_next_trial_info(current_trial_idx_in_run, current_run_image_list_for_trial, num_trials_in_this_run_for_trial):
|
| 199 |
global TARGET_DIR, METHOD_ROOTS, SUBJECTS, SENTINEL_TRIAL_INTERVAL
|
| 200 |
global global_shown_pairs_cache, global_history_has_unsaved_changes, exhausted_target_images
|
|
|
|
| 206 |
target_full_path = os.path.join(TARGET_DIR, img_filename_original)
|
| 207 |
trial_number_for_display = current_trial_idx_in_run + 1
|
| 208 |
|
| 209 |
+
# ---- MODIFICATION START: Create two separate pools for candidates ----
|
| 210 |
+
pool_generated_color = []
|
| 211 |
+
pool_other_methods = []
|
| 212 |
|
| 213 |
for m_root_path in METHOD_ROOTS:
|
| 214 |
method_name = os.path.basename(m_root_path)
|
| 215 |
+
|
|
|
|
| 216 |
subjects_for_method = SUBJECTS
|
| 217 |
if method_name.lower() == "takagi":
|
| 218 |
if "subj01" in SUBJECTS:
|
| 219 |
subjects_for_method = ["subj01"]
|
| 220 |
else:
|
| 221 |
continue
|
| 222 |
+
|
| 223 |
for s_id in subjects_for_method:
|
| 224 |
base, ext = os.path.splitext(img_filename_original)
|
| 225 |
reconstructed_filename = f"{base}_0{ext}"
|
| 226 |
candidate_path = os.path.join(m_root_path, s_id, reconstructed_filename)
|
| 227 |
if os.path.exists(candidate_path):
|
| 228 |
internal_label = f"{method_name}/{s_id}/{reconstructed_filename}"
|
| 229 |
+
candidate_tuple = (internal_label, candidate_path)
|
| 230 |
+
|
| 231 |
+
# Segregate candidates into the two pools
|
| 232 |
+
if method_name == "generated_images_color":
|
| 233 |
+
pool_generated_color.append(candidate_tuple)
|
| 234 |
else:
|
| 235 |
+
pool_other_methods.append(candidate_tuple)
|
| 236 |
+
# ---- MODIFICATION END: Candidate pools are now populated ----
|
| 237 |
|
| 238 |
trial_info = {"image_id": img_filename_original, "target_path": target_full_path, "cur_no": trial_number_for_display, "is_sentinel": False,
|
| 239 |
"left_display_label": "N/A", "left_internal_label": "N/A", "left_path": None,
|
|
|
|
| 242 |
is_potential_sentinel_trial = (trial_number_for_display > 0 and trial_number_for_display % SENTINEL_TRIAL_INTERVAL == 0)
|
| 243 |
|
| 244 |
if is_potential_sentinel_trial:
|
| 245 |
+
# For sentinel trials, we just need one random reconstruction. Combine pools to pick one.
|
| 246 |
+
combined_pool = pool_generated_color + pool_other_methods
|
| 247 |
+
if not combined_pool:
|
| 248 |
+
print(f"警告:哨兵图 '{img_filename_original}' (trial {trial_number_for_display}) 无任何候选图。")
|
| 249 |
else:
|
|
|
|
| 250 |
print(f"生成哨兵试验 for '{img_filename_original}' (trial {trial_number_for_display})")
|
| 251 |
trial_info["is_sentinel"] = True
|
| 252 |
sentinel_candidate_target_tuple = ("目标图像", target_full_path)
|
| 253 |
+
random_reconstruction_candidate_tuple = random.choice(combined_pool)
|
| 254 |
candidates_for_sentinel = [
|
| 255 |
(("目标图像", target_full_path), sentinel_candidate_target_tuple[0]),
|
| 256 |
(("重建图", random_reconstruction_candidate_tuple[1]), random_reconstruction_candidate_tuple[0])
|
|
|
|
| 261 |
"right_display_label": candidates_for_sentinel[1][0][0], "right_path": candidates_for_sentinel[1][0][1], "right_internal_label": candidates_for_sentinel[1][1],
|
| 262 |
})
|
| 263 |
else: # 常规试验
|
| 264 |
+
# ---- MODIFICATION START: New check and pairing logic ----
|
| 265 |
+
# Check if both pools have at least one candidate
|
| 266 |
+
if not pool_generated_color or not pool_other_methods:
|
| 267 |
+
print(f"警告:常规图 '{img_filename_original}' (trial {trial_number_for_display}) 候选不足以形成指定对。 "
|
| 268 |
+
f"('generated_images_color' 找到 {len(pool_generated_color)} 个, "
|
| 269 |
+
f"其他方法找到 {len(pool_other_methods)} 个)。此试验无法进行。")
|
| 270 |
return None, current_trial_idx_in_run
|
| 271 |
|
| 272 |
target_global_history_set = global_shown_pairs_cache.setdefault(img_filename_original, set())
|
| 273 |
+
|
| 274 |
+
# Generate all pairs by picking one from each pool
|
| 275 |
all_possible_pairs_in_pool = []
|
| 276 |
+
for c_color, c_other in product(pool_generated_color, pool_other_methods):
|
| 277 |
+
pair_labels_fset = frozenset({c_color[0], c_other[0]})
|
| 278 |
+
all_possible_pairs_in_pool.append( ((c_color, c_other), pair_labels_fset) )
|
| 279 |
+
# ---- MODIFICATION END: New pairing logic is complete ----
|
|
|
|
| 280 |
|
| 281 |
unseen_globally_pairs_with_data = [
|
| 282 |
item for item in all_possible_pairs_in_pool if item[1] not in target_global_history_set
|
|
|
|
| 289 |
chosen_pair_frozenset = chosen_pair_data_and_labels[1]
|
| 290 |
target_global_history_set.add(chosen_pair_frozenset)
|
| 291 |
global_history_has_unsaved_changes = True
|
| 292 |
+
else:
|
| 293 |
+
print(f"警告:目标图 '{img_filename_original}' (trial {trial_number_for_display}): 所有 ({len(all_possible_pairs_in_pool)}) 个 'generated_color' vs 'other' 对均已在全局展示过。")
|
| 294 |
+
if all_possible_pairs_in_pool:
|
|
|
|
| 295 |
print(f"目标图 '{img_filename_original}' 将被标记为已耗尽,未来轮次中将被跳过。")
|
| 296 |
exhausted_target_images.add(img_filename_original)
|
| 297 |
+
return None, current_trial_idx_in_run
|
|
|
|
| 298 |
|
| 299 |
display_order_candidates = list(selected_candidates_tuples)
|
| 300 |
if random.random() > 0.5:
|
|
|
|
| 310 |
global DATASET_REPO_ID, INDIVIDUAL_LOGS_FOLDER
|
| 311 |
if not isinstance(log_entry, dict):
|
| 312 |
print(f"错误:单个日志条目不是字典格式,无法保存:{log_entry}")
|
| 313 |
+
return False
|
| 314 |
current_user_id = user_identifier_str if user_identifier_str else "unknown_user_session"
|
| 315 |
identifier_safe = str(current_user_id).replace('.', '_').replace(':', '_').replace('/', '_')
|
| 316 |
print(f"用户 {identifier_safe} - 准备保存单条日志 for image {log_entry.get('image_id', 'Unknown')}...")
|
|
|
|
| 318 |
token = os.getenv("HF_TOKEN")
|
| 319 |
if not token:
|
| 320 |
print("错误:环境变量 HF_TOKEN 未设置。无法保存单条日志到Dataset。")
|
| 321 |
+
return False
|
| 322 |
if not DATASET_REPO_ID:
|
| 323 |
print("错误:DATASET_REPO_ID 未配置。无法保存单条日志到Dataset。")
|
| 324 |
+
return False
|
| 325 |
api = HfApi(token=token)
|
|
|
|
| 326 |
image_id_safe_for_filename = os.path.splitext(log_entry.get("image_id", "unknown_img"))[0].replace('.', '_').replace(':', '_').replace('/', '_')
|
| 327 |
file_creation_timestamp_str = datetime.now().strftime('%Y%m%d_%H%M%S_%f')
|
| 328 |
unique_filename = (f"run{log_entry.get('run_no', 'X')}_trial{log_entry.get('trial_sequence_in_run', 'Y')}_img{image_id_safe_for_filename}_{file_creation_timestamp_str}.json")
|
|
|
|
| 332 |
json_content = json.dumps(log_entry, ensure_ascii=False, indent=2)
|
| 333 |
except Exception as json_err:
|
| 334 |
print(f"错误:序列化单条日志时出错: {log_entry}. 错误: {json_err}")
|
|
|
|
| 335 |
error_log_content = {"error": "serialization_failed_single", "original_data_keys": list(log_entry.keys()) if isinstance(log_entry, dict) else None, "timestamp": datetime.now().isoformat()}
|
| 336 |
json_content = json.dumps(error_log_content, ensure_ascii=False, indent=2)
|
|
|
|
| 337 |
|
| 338 |
log_bytes = json_content.encode('utf-8')
|
| 339 |
file_like_object = io.BytesIO(log_bytes)
|
|
|
|
| 346 |
commit_message=(f"Log choice: img {log_entry.get('image_id', 'N/A')}, run {log_entry.get('run_no', 'N/A')}, trial {log_entry.get('trial_sequence_in_run', 'N/A')} by {identifier_safe}")
|
| 347 |
)
|
| 348 |
print(f"单条日志已成功保存到 HF Dataset: {DATASET_REPO_ID}/{path_in_repo}")
|
| 349 |
+
return True
|
| 350 |
except HfHubHTTPError as hf_http_error:
|
| 351 |
print(f"保存单条日志到 Hugging Face Dataset 时发生 HTTP 错误 (可能被限流或权限问题): {hf_http_error}")
|
| 352 |
traceback.print_exc()
|
| 353 |
+
return False
|
| 354 |
except Exception as e:
|
| 355 |
print(f"保存单条日志 (image {log_entry.get('image_id', 'Unknown')}, user {identifier_safe}) 到 Hugging Face Dataset 时发生严重错误: {e}")
|
| 356 |
traceback.print_exc()
|
| 357 |
+
return False
|
| 358 |
|
| 359 |
# ==== 批量保存用户选择日志函数 (确保返回 True/False) ====
|
| 360 |
def save_collected_logs_batch(list_of_log_entries, user_identifier_str, batch_identifier):
|
| 361 |
global DATASET_REPO_ID, BATCH_LOG_FOLDER
|
| 362 |
if not list_of_log_entries:
|
| 363 |
print("批量保存用户日志:没有累积的日志。")
|
| 364 |
+
return True
|
| 365 |
identifier_safe = str(user_identifier_str if user_identifier_str else "unknown_user_session").replace('.', '_').replace(':', '_').replace('/', '_').replace(' ', '_')
|
| 366 |
print(f"用户 {identifier_safe} - 准备批量保存 {len(list_of_log_entries)} 条选择日志 (批次标识: {batch_identifier})...")
|
| 367 |
try:
|
|
|
|
| 373 |
print("错误:DATASET_REPO_ID 未配置。无法批量保存选择日志。")
|
| 374 |
return False
|
| 375 |
api = HfApi(token=token)
|
|
|
|
| 376 |
timestamp_str = datetime.now().strftime('%Y%m%d_%H%M%S_%f')
|
| 377 |
batch_filename = f"batch_user-{identifier_safe}_id-{batch_identifier}_{timestamp_str}_logs-{len(list_of_log_entries)}.jsonl"
|
| 378 |
path_in_repo = f"{BATCH_LOG_FOLDER}/{identifier_safe}/{batch_filename}"
|
|
|
|
| 387 |
|
| 388 |
if not jsonl_content.strip():
|
| 389 |
print(f"用户 {identifier_safe} (批次 {batch_identifier}) 无可序列化选择日志。")
|
| 390 |
+
return True
|
| 391 |
|
| 392 |
log_bytes = jsonl_content.encode('utf-8')
|
| 393 |
file_like_object = io.BytesIO(log_bytes)
|
|
|
|
| 428 |
user_ip_fallback = request.client.host if request else "unknown_ip"
|
| 429 |
user_identifier_for_logging = output_s_user_session_id if output_s_user_session_id else user_ip_fallback
|
| 430 |
|
|
|
|
|
|
|
| 431 |
len_ui_outputs = len(outputs_ui_components_definition)
|
| 432 |
|
| 433 |
def create_ui_error_tuple(message, progress_msg_text, stop_experiment=False):
|
| 434 |
+
btn_start_interactive = not stop_experiment
|
| 435 |
btn_choices_interactive = not stop_experiment
|
| 436 |
return (gr.update(visible=False),) * 3 + \
|
| 437 |
("", "") + \
|
|
|
|
| 444 |
|
| 445 |
if action_type == "record_choice":
|
| 446 |
if output_s_current_trial_data.get("data") and output_s_current_trial_data["data"].get("left_internal_label"):
|
|
|
|
| 447 |
chosen_internal_label = (output_s_current_trial_data["data"]["left_internal_label"] if choice_value == "left" else output_s_current_trial_data["data"]["right_internal_label"])
|
| 448 |
parsed_chosen_method, parsed_chosen_subject, parsed_chosen_filename = "N/A", "N/A", "N/A"
|
| 449 |
if chosen_internal_label == "目标图像": parsed_chosen_method, parsed_chosen_subject, parsed_chosen_filename = "TARGET", "GT", output_s_current_trial_data["data"]["image_id"]
|
|
|
|
| 465 |
output_s_user_logs.append(log_entry)
|
| 466 |
print(f"用户 {user_identifier_for_logging} 记录选择 (img: {log_entry['image_id']})。当前批次日志数: {len(output_s_user_logs)}")
|
| 467 |
|
|
|
|
| 468 |
if len(output_s_user_logs) >= LOG_BATCH_SIZE:
|
| 469 |
print(f"累积用户选择日志达到 {LOG_BATCH_SIZE} 条,准备批量保存...")
|
| 470 |
batch_id_for_filename = f"run{output_s_run_no}_trialidx{output_s_trial_idx}_logcount{len(output_s_user_logs)}"
|
|
|
|
| 474 |
output_s_user_logs = []
|
| 475 |
else:
|
| 476 |
print("严重错误:批量用户选择日志保存失败。实验无法继续。")
|
|
|
|
| 477 |
error_message_ui = "错误:日志保存失败,可能是网络问题或API限流。实验已停止,请联系管理员。"
|
| 478 |
progress_message_ui = f"用户ID: {user_id_display_text} | 实验因错误停止在第 {output_s_run_no} 轮,试验 {output_s_trial_idx+1}"
|
| 479 |
error_ui_updates = create_ui_error_tuple(error_message_ui, progress_message_ui, stop_experiment=True)
|
| 480 |
return output_s_trial_idx, output_s_run_no, output_s_user_logs, output_s_current_trial_data, \
|
| 481 |
output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *error_ui_updates
|
| 482 |
|
|
|
|
| 483 |
if global_history_has_unsaved_changes:
|
| 484 |
print("检测到全局图片对历史自上次保存后有更新,将一并保存...")
|
| 485 |
if not save_global_shown_pairs():
|
|
|
|
| 491 |
output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *error_ui_updates
|
| 492 |
else:
|
| 493 |
print(f"用户 {user_identifier_for_logging} 错误:记录选择时当前试验数据为空或缺少internal_label!")
|
| 494 |
+
error_ui_updates = create_ui_error_tuple("记录选择时内部错误。", f"用户ID: {user_id_display_text} | 进度:{output_s_trial_idx}/{output_s_num_trials_this_run}", stop_experiment=False)
|
|
|
|
| 495 |
return output_s_trial_idx, output_s_run_no, output_s_user_logs, output_s_current_trial_data, output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *error_ui_updates
|
| 496 |
|
|
|
|
|
|
|
| 497 |
if action_type == "start_experiment":
|
| 498 |
is_first = (output_s_num_trials_this_run == 0 and output_s_trial_idx == 0 and output_s_run_no == 1)
|
| 499 |
is_completed_for_restart = (output_s_num_trials_this_run > 0 and output_s_trial_idx >= output_s_num_trials_this_run)
|
| 500 |
|
| 501 |
+
if is_completed_for_restart:
|
| 502 |
+
if output_s_user_logs:
|
| 503 |
print(f"轮次 {output_s_run_no-1} 结束,尝试保存剩余的 {len(output_s_user_logs)} 条用户选择日志...")
|
| 504 |
batch_id_for_filename = f"run{output_s_run_no-1}_final_logcount{len(output_s_user_logs)}"
|
| 505 |
if not save_collected_logs_batch(list(output_s_user_logs), user_identifier_for_logging, batch_id_for_filename):
|
|
|
|
| 509 |
error_ui_updates = create_ui_error_tuple(error_message_ui, progress_message_ui, stop_experiment=True)
|
| 510 |
return output_s_trial_idx, output_s_run_no-1, output_s_user_logs, output_s_current_trial_data, \
|
| 511 |
output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *error_ui_updates
|
| 512 |
+
output_s_user_logs = []
|
| 513 |
|
| 514 |
if global_history_has_unsaved_changes:
|
| 515 |
print("轮次结束,尝试保存全局图片对历史...")
|
|
|
|
| 521 |
return output_s_trial_idx, output_s_run_no-1, output_s_user_logs, output_s_current_trial_data, \
|
| 522 |
output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *error_ui_updates
|
| 523 |
|
|
|
|
| 524 |
if is_first or is_completed_for_restart:
|
| 525 |
if is_completed_for_restart: output_s_run_no += 1
|
| 526 |
available_master_images = [img for img in master_image_list if img not in exhausted_target_images]
|
| 527 |
print(f"开始轮次 {output_s_run_no}: 从 {len(master_image_list)}个总目标图片中筛选,可用图片 {len(available_master_images)}个 (已排除 {len(exhausted_target_images)}个已耗尽图片).")
|
| 528 |
if not available_master_images:
|
|
|
|
| 529 |
msg = "所有目标图片的所有唯一图片对均已展示完毕!感谢您的参与。"
|
| 530 |
prog_text = f"用户ID: {user_id_display_text} | 实验完成!"
|
|
|
|
| 531 |
if output_s_user_logs:
|
| 532 |
print(f"最终轮次结束,尝试保存剩余的 {len(output_s_user_logs)} 条用户选择日志...")
|
| 533 |
+
batch_id_for_filename = f"run{output_s_run_no-1}_final_logcount{len(output_s_user_logs)}"
|
| 534 |
+
save_collected_logs_batch(list(output_s_user_logs), user_identifier_for_logging, batch_id_for_filename)
|
| 535 |
output_s_user_logs = []
|
| 536 |
if global_history_has_unsaved_changes:
|
| 537 |
print("实验最终结束,尝试保存全局图片对历史...")
|
| 538 |
+
save_global_shown_pairs()
|
| 539 |
|
| 540 |
+
ui_updates = list(create_ui_error_tuple(msg, prog_text, stop_experiment=True))
|
| 541 |
return 0, output_s_run_no, [], {}, output_s_user_session_id, [], 0, *tuple(ui_updates)
|
| 542 |
|
|
|
|
| 543 |
if REPEAT_SINGLE_TARGET_FOR_TESTING and available_master_images:
|
| 544 |
print(f"测试模式 (重复单一目标图) 已激活。")
|
| 545 |
single_image_to_repeat = available_master_images[0]
|
|
|
|
| 551 |
current_run_max_trials = NUM_TRIALS_PER_RUN
|
| 552 |
run_size = min(num_really_avail, current_run_max_trials)
|
| 553 |
if run_size == 0:
|
| 554 |
+
error_ui = create_ui_error_tuple("错误: 可用图片采样数为0!", f"用户ID: {user_id_display_text} | 进度: 0/0", stop_experiment=False)
|
| 555 |
return 0, output_s_run_no, output_s_user_logs, {}, output_s_user_session_id, [], 0, *error_ui
|
| 556 |
output_s_current_run_image_list = random.sample(available_master_images, run_size)
|
| 557 |
output_s_num_trials_this_run = run_size
|
| 558 |
|
| 559 |
output_s_trial_idx = 0
|
| 560 |
+
output_s_current_trial_data = {}
|
| 561 |
+
if is_first:
|
|
|
|
|
|
|
| 562 |
timestamp_str = datetime.now().strftime('%Y%m%d%H%M%S%f'); random_val = random.randint(10000, 99999)
|
| 563 |
+
if not output_s_user_session_id:
|
| 564 |
output_s_user_session_id = f"user_{timestamp_str}_{random_val}"; user_identifier_for_logging = output_s_user_session_id
|
| 565 |
else:
|
| 566 |
+
user_identifier_for_logging = output_s_user_session_id
|
| 567 |
print(f"用户会话ID: {output_s_user_session_id}")
|
| 568 |
print(f"开始/继续轮次 {output_s_run_no} (用户ID: {output_s_user_session_id}). 本轮共 {output_s_num_trials_this_run} 个试验。")
|
| 569 |
+
else:
|
| 570 |
print(f"用户 {user_identifier_for_logging} 在第 {output_s_run_no} 轮,试验 {output_s_trial_idx} 点击开始,但轮次未完成。忽略。")
|
| 571 |
no_change_ui = create_no_change_tuple()
|
| 572 |
return output_s_trial_idx, output_s_run_no, output_s_user_logs, output_s_current_trial_data, output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *no_change_ui
|
| 573 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 574 |
current_actual_trial_index_for_get_next = output_s_trial_idx
|
| 575 |
+
|
|
|
|
| 576 |
if current_actual_trial_index_for_get_next >= output_s_num_trials_this_run and output_s_num_trials_this_run > 0:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 577 |
prog_text = f"用户ID: {output_s_user_session_id} | 进度:{output_s_num_trials_this_run}/{output_s_num_trials_this_run} | 第 {output_s_run_no} 轮 🎉"
|
| 578 |
+
ui_updates = list(create_ui_error_tuple(f"🎉 第 {output_s_run_no} 轮完成!请点击“开始试验 / 下一轮”继续。", prog_text, stop_experiment=False))
|
| 579 |
ui_updates[7]=gr.update(interactive=True); ui_updates[8]=gr.update(interactive=False); ui_updates[9]=gr.update(interactive=False)
|
| 580 |
ui_updates[0]=gr.update(value=None,visible=False); ui_updates[1]=gr.update(value=None,visible=False); ui_updates[2]=gr.update(value=None,visible=False)
|
| 581 |
yield output_s_trial_idx, output_s_run_no, output_s_user_logs, {"data": None}, output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *ui_updates; return
|
|
|
|
| 600 |
|
| 601 |
if _trial_info_candidate is not None:
|
| 602 |
trial_info = _trial_info_candidate
|
| 603 |
+
output_s_trial_idx = _returned_next_idx
|
| 604 |
break
|
| 605 |
else:
|
| 606 |
print(f"信息:目标图 '{current_target_image_for_trial}' 无法生成有效试验。尝试列表中的下一个。")
|
| 607 |
next_s_trial_idx_for_state_loop +=1
|
| 608 |
output_s_trial_idx = next_s_trial_idx_for_state_loop
|
|
|
|
| 609 |
|
| 610 |
if trial_info is None:
|
| 611 |
print(f"轮次 {output_s_run_no} 中没有更多可用的有效试验了。结束本轮。")
|
|
|
|
| 612 |
if output_s_user_logs:
|
| 613 |
print(f"轮次 {output_s_run_no} 无更多有效试验,尝试保存剩余 {len(output_s_user_logs)} 条日志...")
|
| 614 |
batch_id_for_filename = f"run{output_s_run_no}_no_more_trials_logcount{len(output_s_user_logs)}"
|
| 615 |
if not save_collected_logs_batch(list(output_s_user_logs), user_identifier_for_logging, batch_id_for_filename):
|
| 616 |
+
print("严重错误:保存剩余日志失败。实验可能需要停止。")
|
|
|
|
|
|
|
| 617 |
output_s_user_logs = []
|
| 618 |
if global_history_has_unsaved_changes:
|
| 619 |
print("轮次无更多有效试验,尝试保存全局图片对历史...")
|
|
|
|
| 639 |
|
| 640 |
ui_show_candidates_updates = list(create_no_change_tuple())
|
| 641 |
ui_show_candidates_updates[0]=gr.update(value=None,visible=False); ui_show_candidates_updates[1]=gr.update(value=trial_info["left_path"],visible=True); ui_show_candidates_updates[2]=gr.update(value=trial_info["right_path"],visible=True)
|
| 642 |
+
ui_show_candidates_updates[3]=gr.update(value=trial_info["left_display_label"], visible=True); ui_show_candidates_updates[4]=gr.update(value=trial_info["right_display_label"], visible=True)
|
| 643 |
ui_show_candidates_updates[5]="请选择更像原图的一张"; ui_show_candidates_updates[6]=prog_text
|
| 644 |
ui_show_candidates_updates[7]=gr.update(interactive=False); ui_show_candidates_updates[8]=gr.update(interactive=True); ui_show_candidates_updates[9]=gr.update(interactive=True)
|
| 645 |
yield output_s_trial_idx, output_s_run_no, output_s_user_logs, output_s_current_trial_data, output_s_user_session_id, output_s_current_run_image_list, output_s_num_trials_this_run, *ui_show_candidates_updates
|
| 646 |
|
| 647 |
+
# ==== Gradio UI 定义 和 程序入口 ====
|
| 648 |
+
def handle_download_history_file():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 649 |
global GLOBAL_HISTORY_FILE
|
| 650 |
if os.path.exists(GLOBAL_HISTORY_FILE):
|
| 651 |
try:
|
|
|
|
| 723 |
with experiment_container:
|
| 724 |
gr.Markdown("## 🧠 图像重建主观评估实验"); gr.Markdown(f"每轮实验大约有 {NUM_TRIALS_PER_RUN} 次比较。")
|
| 725 |
with gr.Row():
|
| 726 |
+
with gr.Column(scale=1, min_width=300): left_img = gr.Image(label="左候选图", visible=False, height=400, interactive=False); left_lbl = gr.Textbox(label="左图信息", value="", visible=True, interactive=False, max_lines=1); btn_left = gr.Button("选择左图 (更相似)", interactive=False, elem_classes="compact_button")
|
| 727 |
+
with gr.Column(scale=1, min_width=300): right_img = gr.Image(label="右候选图", visible=False, height=400, interactive=False); right_lbl = gr.Textbox(label="右图信息",value="", visible=True, interactive=False, max_lines=1); btn_right = gr.Button("选择右图 (更相似)", interactive=False, elem_classes="compact_button")
|
| 728 |
with gr.Row(): target_img = gr.Image(label="目标图像 (观察3秒后消失)", visible=False, height=400, interactive=False)
|
| 729 |
with gr.Row(): status_text = gr.Markdown(value="请点击“开始试验 / 下一轮”按钮。")
|
| 730 |
+
with gr.Row(): progress_text = gr.Markdown()
|
| 731 |
with gr.Row():
|
| 732 |
btn_start = gr.Button("开始试验 / 下一轮")
|
| 733 |
btn_download_json = gr.Button("下载JSON历史记录")
|
| 734 |
json_download_output = gr.File(label="下载的文件会在此处提供", interactive=False)
|
| 735 |
+
file_out_placeholder = gr.File(label=" ", visible=False, interactive=False)
|
| 736 |
|
| 737 |
outputs_ui_components_definition = [
|
| 738 |
target_img, left_img, right_img, left_lbl, right_lbl, status_text, progress_text,
|
| 739 |
btn_start, btn_left, btn_right, file_out_placeholder
|
| 740 |
+
]
|
| 741 |
click_inputs_base = [
|
| 742 |
s_trial_index, s_run_no, s_user_logs, s_current_trial_data, s_user_session_id,
|
| 743 |
s_current_run_image_list, s_num_trials_this_run
|
|
|
|
| 753 |
btn_right.click(fn=partial(process_experiment_step, action_type="record_choice", choice_value="right"), inputs=click_inputs_base, outputs=event_outputs, queue=True)
|
| 754 |
btn_download_json.click(fn=handle_download_history_file, inputs=None, outputs=[json_download_output, status_text])
|
| 755 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 756 |
if __name__ == "__main__":
|
|
|
|
| 757 |
if not master_image_list: print("\n关键错误:程序无法启动,因无目标图片。"); exit()
|
| 758 |
else:
|
| 759 |
print(f"从 '{TARGET_DIR}' 加载 {len(master_image_list)} 张目标���片。")
|
| 760 |
if not METHOD_ROOTS: print(f"警告: '{BASE_IMAGE_DIR}' 无候选方法子目录。")
|
| 761 |
if not SUBJECTS: print("警告: SUBJECTS 列表为空。")
|
| 762 |
+
print(f"用户选择日志保存到 Dataset: '{DATASET_REPO_ID}' 的 '{BATCH_LOG_FOLDER}/ 文件夹")
|
| 763 |
if not os.getenv("HF_TOKEN"): print("警告: HF_TOKEN 未设置。日志无法保存到Hugging Face Dataset。\n 请在 Space Secrets 中设置 HF_TOKEN。")
|
| 764 |
else: print("HF_TOKEN 已找到。")
|
| 765 |
print(f"全局图片对历史将从 '{GLOBAL_HISTORY_FILE}' 加载/保存到此文件。")
|
|
|
|
| 783 |
except Exception as e_mkdir_main:
|
| 784 |
print(f"错误:在 main 中创建目录 '{PERSISTENT_STORAGE_BASE}' 失败: {e_mkdir_main}")
|
| 785 |
|
| 786 |
+
final_allowed_paths = list(set(allowed_paths_list))
|
| 787 |
if final_allowed_paths:
|
| 788 |
+
print(f"Gradio demo.launch() 配置最终 allowed_paths: {final_allowed_paths}")
|
| 789 |
else:
|
| 790 |
print("警告:没有有效的 allowed_paths 被配置。Gradio文件访问可能受限。")
|
| 791 |
|