import gradio as gr import tensorflow as tf import numpy as np import os import random from PIL import Image import keras # --- Config --- # Get the absolute path of the directory containing this script BASE_DIR = os.path.dirname(os.path.abspath(__file__)) MODEL_PATH = os.path.join(BASE_DIR, "best_model.keras") IMAGE_FOLDER = os.path.join(BASE_DIR, "test_images") IMG_SIZE = (128, 128) # --- Global State Helpers --- loading_error = None # MONKEY PATCH: Handle 'quantization_config' in Dense layer # This patch ensures that if 'quantization_config' is passed to Dense layer (from saved model), # it is safely ignored instead of raising an error. try: _original_dense_init = keras.layers.Dense.__init__ def _patched_dense_init(self, *args, quantization_config=None, **kwargs): _original_dense_init(self, *args, **kwargs) keras.layers.Dense.__init__ = _patched_dense_init print("✅ Applied monkey patch to keras.layers.Dense for quantization_config compatibility") except Exception as e: print(f"⚠️ Failed to patch Dense layer: {e}") def load_game_resources(): global loading_error """Loads model and image list.""" print(f"DEBUG: Keras Version: {keras.__version__}") print(f"DEBUG: TensorFlow Version: {tf.__version__}") # Load Model try: # compile=False avoids errors related to optimizers/losses that aren't needed for inference model = keras.models.load_model(MODEL_PATH, compile=False) print("✅ Model loaded successfully.") except Exception as e: print(f"❌ Error loading model: {e}") loading_error = str(e) model = None # Load Images images = [] if os.path.exists(IMAGE_FOLDER): for fname in os.listdir(IMAGE_FOLDER): if fname.lower().endswith(('.png', '.jpg', '.jpeg')): path = os.path.join(IMAGE_FOLDER, fname) # Determine label from filename prefix created in prepare_assets.py label = "Real" if "real_" in fname.lower() else "AI" images.append((path, label)) random.shuffle(images) print(f"✅ Loaded {len(images)} images.") return model, images # Load resources once on startup global_model, global_images = load_game_resources() # --- Game Logic --- def start_new_game(): """Reset scores and reshuffle images.""" random.shuffle(global_images) # State: [human_score, model_score, round_count, current_image_index, image_list] return 0, 0, 0, 0, global_images, gr.update(visible=True), gr.update(visible=False) def get_current_image(index, image_list): if not image_list or index >= len(image_list): return None, "Game Over! Restart to play again.", None path, label = image_list[index] return path, f"Round {index + 1}", label def predict_and_score(user_guess, index, image_list, human_score, model_score, rounds): if not image_list or index >= len(image_list): return human_score, model_score, rounds, index, "Game Over", None, gr.update(visible=False), gr.update(visible=True) path, true_label = image_list[index] # 1. Model Prediction model_guess_label = "Unsure" confidence = 0.0 if global_model: try: img = Image.open(path).convert('RGB') img = img.resize(IMG_SIZE) img_array = np.array(img) # Model has internal Rescaling layer, so keep [0, 255] img_array = np.expand_dims(img_array, axis=0) # Prediction (Sigmoid: <0.5 = Class 0, >0.5 = Class 1) # We need to know which class is which. # Usually: 0=AI, 1=Real OR 0=Real, 1=AI. # In the notebook: train_generator.class_indices would tell us. # Assumption based on directory names: sorting usually puts AI first. # So 0=AI, 1=Real. Let's verify this output if possible, but assume 0=AI for now. prediction = global_model.predict(img_array, verbose=0)[0][0] # Notebook typically uses 'training_AI' and 'training_real'. # Alphabetical: AI=0, real=1. is_real = prediction > 0.5 model_guess_label = "Real" if is_real else "AI" confidence = prediction if is_real else 1 - prediction except Exception as e: print(f"Prediction Error: {e}") model_guess_label = "Error" else: model_guess_label = f"Load Error: {loading_error}" # 2. Update Scores human_correct = (user_guess == true_label) model_correct = (model_guess_label == true_label) if human_correct: human_score += 1 if model_correct: model_score += 1 rounds += 1 next_idx = index + 1 # ... (Previous code) # 3. Message & Visuals if human_correct: # Green success text result_text = f"## Correct! It was {true_label}. 🎉\n\n" # Trigger confetti JS js_cmd = """ """ else: # Red failure text result_text = f"## Wrong! It was {true_label}. 😢\n\n" js_cmd = "" result_text += f"**You guessed:** {user_guess}\n" model_result_color = "green" if model_correct else "red" result_text += f"**Model guessed:** {model_guess_label} ({confidence:.2f})" # Prepare next round view # Hide guess buttons, Show Next button return human_score, model_score, rounds, next_idx, result_text, gr.update(visible=False), gr.update(visible=True), js_cmd # Helper for Game Over HTML def get_game_over_html(human_score, model_score): winner = "It's a Tie!" color = "#6b7280" # Gray if human_score > model_score: winner = "🎉 YOU WON! 🎉" color = "#10b981" # Green elif model_score > human_score: winner = "🤖 AI WON! 🤖" color = "#ef4444" # Red return f"""

GAME OVER

{winner}

Human: {human_score}  |  AI: {model_score}

Click 'Restart Game' to play again

""" def next_round_view(index, image_list, human_score, model_score): path, round_text, _ = get_current_image(index, image_list) if path is None: # End game - Show big visual summary final_html = get_game_over_html(human_score, model_score) return None, final_html, gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), "" return path, round_text, gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), "" # --- UI Construction --- with gr.Blocks() as demo: # Load Confetti Library gr.HTML(""" """) # Game State # Stores: [human_score, model_score, rounds, index, image_list] state_human = gr.State(0) state_model = gr.State(0) state_rounds = gr.State(0) state_index = gr.State(0) state_images = gr.State(global_images) gr.Markdown("# 🤖 Human vs. AI: Face Detection Showdown") gr.Markdown("Can you beat the deep learning model at spotting AI-generated faces?") with gr.Row(): # Left Column: Stats with gr.Column(scale=1): human_score_disp = gr.Number(label="Your Score", value=0) model_score_disp = gr.Number(label="Model Score", value=0) round_disp = gr.Number(label="Rounds Played", value=0) restart_btn = gr.Button("🔄 Restart Game", variant="secondary") # Right Column: Game Area with gr.Column(scale=2): round_label = gr.Markdown("### Round 1") image_display = gr.Image(label="Is this face Real or AI?", type="filepath", height=300) # Result Display result_box = gr.Markdown("") # Visual Effects Trigger (Hidden HTML component to run JS) js_trigger = gr.HTML(visible=False) with gr.Row() as guess_row: btn_real = gr.Button("Real 👤", variant="primary", size="lg") btn_ai = gr.Button("AI 🤖", variant="primary", size="lg") next_btn = gr.Button("Next Image ➡️", variant="primary", visible=False) # Instructions & Details with gr.Accordion("ℹ️ How to Play & Model Details", open=False): gr.Markdown(""" **How to Play:** 1. Look at the image and decide if it's a **Real** person or **AI-Generated**. 2. Click the corresponding button. 3. See if you can beat the AI! **Understanding the Score:** - The number in brackets, e.g., `(0.95)`, represents the **Model's Confidence Percentage** in its guess (95% confident). **Model Details:** - This game uses our **Best Fine-Tuned MobileNetV2 Model**, which achieved the highest accuracy during training. - It has been trained to detect subtle artifacts in AI-generated faces. """) # --- Event Handlers --- # Restart restart_btn.click( start_new_game, outputs=[state_human, state_model, state_rounds, state_index, state_images, guess_row, next_btn] ).then( next_round_view, inputs=[state_index, state_images, state_human, state_model], outputs=[image_display, round_label, guess_row, next_btn, restart_btn, js_trigger] ).then( lambda: (0,0,0, ""), outputs=[human_score_disp, model_score_disp, round_disp, result_box] ) # Guess Real btn_real.click( fn=predict_and_score, inputs=[gr.State("Real"), state_index, state_images, state_human, state_model, state_rounds], outputs=[state_human, state_model, state_rounds, state_index, result_box, guess_row, next_btn, js_trigger] ).then( lambda h, m, r: (h, m, r), inputs=[state_human, state_model, state_rounds], outputs=[human_score_disp, model_score_disp, round_disp] ) # Guess AI btn_ai.click( fn=predict_and_score, inputs=[gr.State("AI"), state_index, state_images, state_human, state_model, state_rounds], outputs=[state_human, state_model, state_rounds, state_index, result_box, guess_row, next_btn, js_trigger] ).then( lambda h, m, r: (h, m, r), inputs=[state_human, state_model, state_rounds], outputs=[human_score_disp, model_score_disp, round_disp] ) # Next Image next_btn.click( next_round_view, inputs=[state_index, state_images, state_human, state_model], outputs=[image_display, round_label, guess_row, next_btn, restart_btn, js_trigger] ).then( lambda: "", outputs=[result_box] ) # Initial Load demo.load( start_new_game, outputs=[state_human, state_model, state_rounds, state_index, state_images, guess_row, next_btn] ).then( next_round_view, inputs=[state_index, state_images, state_human, state_model], outputs=[image_display, round_label, guess_row, next_btn, restart_btn, js_trigger] ) if __name__ == "__main__": demo.launch(theme=gr.themes.Soft(), server_name="0.0.0.0", server_port=7860)