File size: 4,284 Bytes
d581b00
 
dda3973
 
d581b00
dda3973
d581b00
 
 
 
 
ff9e377
 
 
d581b00
dda3973
d581b00
ff9e377
d581b00
ff9e377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dda3973
 
 
ff9e377
d581b00
 
 
 
 
ff9e377
d581b00
 
 
ff9e377
dda3973
d581b00
ff9e377
d581b00
 
 
 
 
ff9e377
d581b00
 
ff9e377
 
dda3973
e7ddfc7
 
 
 
 
 
 
 
 
 
dda3973
ff9e377
e7ddfc7
d581b00
 
 
 
 
ff9e377
d581b00
 
 
ff9e377
 
 
d581b00
 
 
ff9e377
 
 
 
d581b00
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import gradio as gr
import torch
import base64
import io
from PIL import Image
from src.models.inference import load_model, predict, predict_generator, load_generator_model
from src.services.metadata_checker import get_metadata
from src.services.gradcam import generate_gradcam
import tempfile
import os

# Load both models once at startup β€” singleton pattern
# Models stay in memory for the lifetime of the app
# Avoids 2-3 second reload penalty on every user request
model = load_model()
generator_model = load_generator_model()


def analyze_image(image):
    """
    Main function called by Gradio when user submits an image.

    Flow:
    1. Save PIL image to a temp file (inference functions require file paths)
    2. Run binary prediction (real vs fake)
    3. Run generator type prediction (Real/GAN/Diffusion/Other)
    4. Extract image metadata and EXIF
    5. Generate Grad-CAM heatmap
    6. Build formatted markdown result string
    7. Delete temp file
    8. Return (result_text, cam_image) β€” matches Gradio output components

    Why temp file:
    Gradio passes images as PIL objects but our inference functions
    expect file paths. We save temporarily and clean up after.
    """
    if image is None:
        return "Please upload an image.", None

    # Save PIL image to temp file with unique name
    with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
        image.save(tmp.name)
        tmp_path = tmp.name

    try:
        # Run all analysis pipelines
        prediction = predict(tmp_path, model=model)
        metadata = get_metadata(tmp_path)
        cam_array = generate_gradcam(tmp_path, model=model)
        cam_image = Image.fromarray(cam_array)  # convert numpy array to PIL for Gradio
        generator_result = predict_generator(tmp_path, model=generator_model)
    finally:
        # Always clean up temp file regardless of success or failure
        os.remove(tmp_path)

    label = prediction["label"]
    confidence = prediction["confidence"]

    # Build markdown-formatted result string for Gradio Markdown component
    result_text = f"**{label}** β€” Confidence: {confidence}%\n\n"
    result_text += f"⚠️ This is a model-based estimate, not definitive proof.\n\n"
    result_text += f"πŸ“Œ **Note:** Model performs best on Stable Diffusion, StyleGAN, and DDPM images. "
    result_text += f"Performance drops on unseen generators (DALL-E, MidJourney).\n\n"

    # Generator type section β€” only shown when image is predicted AI-Generated
    # No point showing generator type if image is classified as real
    if label == "AI-Generated":
        gen_type = generator_result["generator_type"]
        gen_conf = generator_result["confidence"]
        result_text += f"**Generator Type:** {gen_type} ({gen_conf}%)\n\n"
        result_text += "**Class Probabilities:**\n"
        for cls, prob in generator_result["class_probabilities"].items():
            result_text += f"- {cls}: {prob}%\n"
        result_text += "\n"

    # Metadata section
    result_text += f"**Metadata:**\n"
    result_text += f"- Format: {metadata['format']}\n"
    result_text += f"- Dimensions: {metadata['dimensions']}\n"
    result_text += f"- File Size: {metadata['file_size_kb']} KB\n"
    result_text += f"- EXIF Data: {'Yes' if metadata['has_exif'] else 'No'}\n"

    # Show EXIF note in italics β€” honest disclaimer about what missing EXIF means
    if not metadata["has_exif"]:
        result_text += f"\n_{metadata['exif_note']}_"

    # Gradio expects return values matching the outputs list order:
    # outputs[0] = Markdown β†’ result_text
    # outputs[1] = Image β†’ cam_image
    return result_text, cam_image


# Gradio Interface β€” simplest Gradio pattern
# fn: function to call on submit
# inputs: single image upload component
# outputs: markdown text + image side by side
demo = gr.Interface(
    fn=analyze_image,
    inputs=gr.Image(type="pil", label="Upload Image"),
    outputs=[
        gr.Markdown(label="Result"),
        gr.Image(type="pil", label="Grad-CAM Explanation")
    ],
    title="πŸ” ImageTrust-AI",
    description="Upload an image to check if it's real or AI-generated. Powered by ResNet18 trained on ArtiFact dataset.",
    examples=[],
)

if __name__ == "__main__":
    demo.launch()