import os, random, json from datetime import datetime from PIL import Image, ImageOps import numpy as np import gradio as gr from gspread import service_account_from_dict from huggingface_hub import HfApi, hf_hub_download # -------- Configuration -------- HF_TOKEN = os.environ["HF_TOKEN"] api = HfApi(token=HF_TOKEN) REPO_ID = "HotshotGoku/Images_Test_AI_or_Real" # List files in the dataset all_files = api.list_repo_files( repo_id=REPO_ID, repo_type="dataset" ) # Get the AI vs Real folders ai_remote = [f for f in all_files if f.startswith("AI/")] real_remote = [f for f in all_files if f.startswith("Real/")] ai_files = [hf_hub_download(repo_id=REPO_ID, repo_type="dataset",filename=path, token=HF_TOKEN) for path in ai_remote] real_files = [hf_hub_download(repo_id=REPO_ID, repo_type="dataset",filename=path, token=HF_TOKEN) for path in real_remote] # Load a sample real image for the demo real_demo_path= hf_hub_download(repo_id=REPO_ID, repo_type="dataset",filename="Experiment_grid_3x3.png", token=HF_TOKEN) real_demo = Image.open(real_demo_path).convert("RGB") real_demo.thumbnail((500, 500), resample=Image.LANCZOS) # now ≤500×500 # 2) Turn it into a numpy array real_demo_small = np.array(real_demo) history_file = "history_state.json" # -------------------------------- # ——— Google Sheets setup ——— # load service‐account JSON from HF secret info = json.loads(os.environ["JSON_ACCESS"]) # info = json.loads('service_account.json') # for local testing only gc = service_account_from_dict(info) # open by key for stability ws = gc.open_by_key("1lfhomrFnhmRIxQvdJZr-7gASA3sONBT_mblHNreMKvc").sheet1 def record_run(name, session_start, attempt_num, elapsed_sec, familiarity, correct_count, wrong_list): """ Append one row with exactly: Name | StartTime | #Attempt | ElapsedSec | Familiarity | AttemptsList | WrongsList """ ws.append_row([ name, session_start, # when that grid/timer started attempt_num, # 1, 2, 3, … elapsed_sec, # seconds for this attempt familiarity, # slider value json.dumps([correct_count]), # e.g. [9] json.dumps(wrong_list) # e.g. ["217_1.png","243_2.png"] ]) def get_new_grid(state=None): """ Generate a fresh 4×4 grid: - Preserve attempt_count - **Reset** timer_start to "now" whenever the user hits Retry (i.e. state is not None) - Leave timer_start unset on initial load (so they must click Start or first Fake?) """ attempt_count = (state or {}).get("attempt_count", 0) t = random.randint(4, 12) sel_ai = random.sample(ai_files, t) sel_real = random.sample(real_files, 16 - t) combo = [(p, False) for p in sel_ai] + [(p, True) for p in sel_real] random.shuffle(combo) paths, truth = zip(*combo) # 3) Build thumbnails + reset flags thumbs = [np.array(Image.open(p).convert("RGB")) for p in paths] resets = [False] * 16 new_state = { "paths": paths, "truth": truth, "attempt_count": attempt_count , # increment attempt count, "timer_start": datetime.now().isoformat() } return (*thumbs, *resets, new_state) def evaluate( sel0, sel1, sel2, sel3, sel4, sel5, sel6, sel7, sel8, sel9, sel10, sel11, sel12, sel13, sel14, sel15, state, user_name, familiarity ): # 1) Increment the simple count attempt_count = state.get("attempt_count", 0) + 1 state["attempt_count"] = attempt_count # 2) Border logic as before... sels = [sel0, sel1, sel2, sel3, sel4, sel5, sel6, sel7, sel8, sel9, sel10, sel11, sel12, sel13, sel14, sel15] bordered, wrong_list = [], [] correct_count = 0 for i, chosen in enumerate(sels): img = Image.open(state["paths"][i]).convert("RGB") is_real = state["truth"][i] correct = chosen == (not is_real) color = "green" if correct else "red" if correct: correct_count += 1 else: wrong_list.append(os.path.basename(state["paths"][i])) bordered.append(np.array(ImageOps.expand(img, border=5, fill=color))) # 3) Timer logic timer_iso = state.get("timer_start") elapsed = (datetime.now() - datetime.fromisoformat(timer_iso)).total_seconds() if timer_iso else 0.0 # incremented earlier in evaluate: attempt_num = state["attempt_count"] record_run( user_name, # your textbox input state["timer_start"], # ISO start time for this grid attempt_num, # 1, 2, 3, ... elapsed, # per‐attempt elapsed familiarity, # slider input correct_count, # how many correct this try wrong_list # which files were wrong ) # 5) Summary text pct = correct_count / 16 * 100 summary = f"Attempt {attempt_count}: {correct_count}/16 correct ({pct:.1f}%), time {elapsed:.1f}s. \n Retry to improve your score! (The grid of images will refresh )" return (*bordered, state, summary) with gr.Blocks(fill_width=True) as demo: # Terms & Conditions gate terms_cb = gr.Checkbox( label="I agree that my game data can be used for downstream analysis purposes.", value=False ) # Main UI hidden until terms are accepted with gr.Column(visible=False) as main_ui: gr.Markdown("# Identify the Fake \nEnter your name, then play:") user_name = gr.Textbox(label="Your name", placeholder="e.g. Can be anonymous ex: 'Trisolaris123'") gr.Markdown("# Instructions: Click the images you think are AI-generated. \nWhen you are done, click 'Submit' to record your results.\nClick 'Retry' to get a new set of images and improve your score (infinite retries). ") # Familiarity slider (1=first time, 10=expert) familiarity = gr.Slider( minimum=1, maximum=10, step=1, value=1, label="How familiar are you with P. aeruginosa patterns? 1: First time seeing these 10: Have worked with these patterns before", interactive=True ) state = gr.State(None) example_ui = gr.Column(visible=True) with example_ui: gr.Markdown("### Before you begin: This are what *Real* Images look like. Click start to begin the game. ") gr.Image(value=real_demo_small, show_label=False) #.style( # width=300, # px # height=300 # px # ) # set desired display height) gr.Markdown("---") # Add a Start button that WILL load images and start the timer start_btn = gr.Button("Start") # Now wrap the grid + controls in a hidden Column with gr.Column(visible=False) as grid_ui: image_components = [] checkbox_components = [] for row in range(4): with gr.Row(): for col in range(4): idx = row * 4 + col with gr.Column(min_width=200, scale=1): img = gr.Image(type="numpy", interactive=False, show_label=False) cb = gr.Checkbox(label="Fake?", elem_id=f"cb_{idx}") image_components.append(img) checkbox_components.append(cb) submit_btn = gr.Button("Submit") retry_btn = gr.Button("Retry") score_out = gr.Textbox(label="Score & Time", interactive=False) retry_btn.click( fn=get_new_grid, inputs=[state], outputs=[*image_components, *checkbox_components, state] ) submit_btn.click( fn=evaluate, inputs=[*checkbox_components, state,user_name, familiarity], outputs=[*image_components, state,score_out] ) # Wire start_btn to both load the grid _and_ reveal it start_btn.click( fn=get_new_grid, inputs=[state], outputs=[*image_components, *checkbox_components, state] ) start_btn.click( fn=lambda _: gr.update(visible=True), inputs=[], outputs=[grid_ui] ) start_btn.click( fn=lambda _: gr.update(visible=False), inputs=[], outputs=[example_ui] ) # Reveal main UI only after agreeing to terms terms_cb.change( fn=lambda agreed: gr.update(visible=agreed), inputs=[terms_cb], outputs=[main_ui] ) demo.launch()