Spaces:
Sleeping
Sleeping
fix DataParallel loading, add limitation note to UI
Browse files
app.py
CHANGED
|
@@ -9,35 +9,60 @@ from src.services.gradcam import generate_gradcam
|
|
| 9 |
import tempfile
|
| 10 |
import os
|
| 11 |
|
| 12 |
-
# Load
|
|
|
|
|
|
|
| 13 |
model = load_model()
|
| 14 |
generator_model = load_generator_model()
|
| 15 |
|
|
|
|
| 16 |
def analyze_image(image):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
if image is None:
|
| 18 |
return "Please upload an image.", None
|
| 19 |
|
| 20 |
-
# Save PIL image to temp file
|
| 21 |
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
|
| 22 |
image.save(tmp.name)
|
| 23 |
tmp_path = tmp.name
|
| 24 |
|
| 25 |
try:
|
|
|
|
| 26 |
prediction = predict(tmp_path, model=model)
|
| 27 |
metadata = get_metadata(tmp_path)
|
| 28 |
cam_array = generate_gradcam(tmp_path, model=model)
|
| 29 |
-
cam_image = Image.fromarray(cam_array)
|
| 30 |
generator_result = predict_generator(tmp_path, model=generator_model)
|
| 31 |
finally:
|
|
|
|
| 32 |
os.remove(tmp_path)
|
| 33 |
|
| 34 |
label = prediction["label"]
|
| 35 |
confidence = prediction["confidence"]
|
| 36 |
|
|
|
|
| 37 |
result_text = f"**{label}** β Confidence: {confidence}%\n\n"
|
| 38 |
result_text += f"β οΈ This is a model-based estimate, not definitive proof.\n\n"
|
|
|
|
|
|
|
| 39 |
|
| 40 |
-
# Generator type
|
| 41 |
gen_type = generator_result["generator_type"]
|
| 42 |
gen_conf = generator_result["confidence"]
|
| 43 |
result_text += f"**Generator Type:** {gen_type} ({gen_conf}%)\n\n"
|
|
@@ -45,18 +70,27 @@ def analyze_image(image):
|
|
| 45 |
for cls, prob in generator_result["class_probabilities"].items():
|
| 46 |
result_text += f"- {cls}: {prob}%\n"
|
| 47 |
|
|
|
|
| 48 |
result_text += f"\n**Metadata:**\n"
|
| 49 |
result_text += f"- Format: {metadata['format']}\n"
|
| 50 |
result_text += f"- Dimensions: {metadata['dimensions']}\n"
|
| 51 |
result_text += f"- File Size: {metadata['file_size_kb']} KB\n"
|
| 52 |
result_text += f"- EXIF Data: {'Yes' if metadata['has_exif'] else 'No'}\n"
|
| 53 |
|
|
|
|
| 54 |
if not metadata["has_exif"]:
|
| 55 |
result_text += f"\n_{metadata['exif_note']}_"
|
| 56 |
|
|
|
|
|
|
|
|
|
|
| 57 |
return result_text, cam_image
|
| 58 |
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
demo = gr.Interface(
|
| 61 |
fn=analyze_image,
|
| 62 |
inputs=gr.Image(type="pil", label="Upload Image"),
|
|
|
|
| 9 |
import tempfile
|
| 10 |
import os
|
| 11 |
|
| 12 |
+
# Load both models once at startup β singleton pattern
|
| 13 |
+
# Models stay in memory for the lifetime of the app
|
| 14 |
+
# Avoids 2-3 second reload penalty on every user request
|
| 15 |
model = load_model()
|
| 16 |
generator_model = load_generator_model()
|
| 17 |
|
| 18 |
+
|
| 19 |
def analyze_image(image):
|
| 20 |
+
"""
|
| 21 |
+
Main function called by Gradio when user submits an image.
|
| 22 |
+
|
| 23 |
+
Flow:
|
| 24 |
+
1. Save PIL image to a temp file (inference functions require file paths)
|
| 25 |
+
2. Run binary prediction (real vs fake)
|
| 26 |
+
3. Run generator type prediction (Real/GAN/Diffusion/Other)
|
| 27 |
+
4. Extract image metadata and EXIF
|
| 28 |
+
5. Generate Grad-CAM heatmap
|
| 29 |
+
6. Build formatted markdown result string
|
| 30 |
+
7. Delete temp file
|
| 31 |
+
8. Return (result_text, cam_image) β matches Gradio output components
|
| 32 |
+
|
| 33 |
+
Why temp file:
|
| 34 |
+
Gradio passes images as PIL objects but our inference functions
|
| 35 |
+
expect file paths. We save temporarily and clean up after.
|
| 36 |
+
"""
|
| 37 |
if image is None:
|
| 38 |
return "Please upload an image.", None
|
| 39 |
|
| 40 |
+
# Save PIL image to temp file with unique name
|
| 41 |
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
|
| 42 |
image.save(tmp.name)
|
| 43 |
tmp_path = tmp.name
|
| 44 |
|
| 45 |
try:
|
| 46 |
+
# Run all analysis pipelines
|
| 47 |
prediction = predict(tmp_path, model=model)
|
| 48 |
metadata = get_metadata(tmp_path)
|
| 49 |
cam_array = generate_gradcam(tmp_path, model=model)
|
| 50 |
+
cam_image = Image.fromarray(cam_array) # convert numpy array to PIL for Gradio
|
| 51 |
generator_result = predict_generator(tmp_path, model=generator_model)
|
| 52 |
finally:
|
| 53 |
+
# Always clean up temp file regardless of success or failure
|
| 54 |
os.remove(tmp_path)
|
| 55 |
|
| 56 |
label = prediction["label"]
|
| 57 |
confidence = prediction["confidence"]
|
| 58 |
|
| 59 |
+
# Build markdown-formatted result string for Gradio Markdown component
|
| 60 |
result_text = f"**{label}** β Confidence: {confidence}%\n\n"
|
| 61 |
result_text += f"β οΈ This is a model-based estimate, not definitive proof.\n\n"
|
| 62 |
+
result_text += f"π **Note:** Model performs best on Stable Diffusion, StyleGAN, and DDPM images. "
|
| 63 |
+
result_text += f"Performance drops on unseen generators (DALL-E, MidJourney).\n\n"
|
| 64 |
|
| 65 |
+
# Generator type section β shows architecture family
|
| 66 |
gen_type = generator_result["generator_type"]
|
| 67 |
gen_conf = generator_result["confidence"]
|
| 68 |
result_text += f"**Generator Type:** {gen_type} ({gen_conf}%)\n\n"
|
|
|
|
| 70 |
for cls, prob in generator_result["class_probabilities"].items():
|
| 71 |
result_text += f"- {cls}: {prob}%\n"
|
| 72 |
|
| 73 |
+
# Metadata section
|
| 74 |
result_text += f"\n**Metadata:**\n"
|
| 75 |
result_text += f"- Format: {metadata['format']}\n"
|
| 76 |
result_text += f"- Dimensions: {metadata['dimensions']}\n"
|
| 77 |
result_text += f"- File Size: {metadata['file_size_kb']} KB\n"
|
| 78 |
result_text += f"- EXIF Data: {'Yes' if metadata['has_exif'] else 'No'}\n"
|
| 79 |
|
| 80 |
+
# Show EXIF note in italics β honest disclaimer about what missing EXIF means
|
| 81 |
if not metadata["has_exif"]:
|
| 82 |
result_text += f"\n_{metadata['exif_note']}_"
|
| 83 |
|
| 84 |
+
# Gradio expects return values matching the outputs list order:
|
| 85 |
+
# outputs[0] = Markdown β result_text
|
| 86 |
+
# outputs[1] = Image β cam_image
|
| 87 |
return result_text, cam_image
|
| 88 |
|
| 89 |
|
| 90 |
+
# Gradio Interface β simplest Gradio pattern
|
| 91 |
+
# fn: function to call on submit
|
| 92 |
+
# inputs: single image upload component
|
| 93 |
+
# outputs: markdown text + image side by side
|
| 94 |
demo = gr.Interface(
|
| 95 |
fn=analyze_image,
|
| 96 |
inputs=gr.Image(type="pil", label="Upload Image"),
|