Spaces:
Running on Zero
Running on Zero
Smaug branding; public-address check for links; upload size cap; queue cap; 10 s GPU reservation
Browse files
README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title: AI Image Detector
|
| 3 |
emoji: ๐
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: indigo
|
|
@@ -11,10 +11,11 @@ pinned: false
|
|
| 11 |
license: apache-2.0
|
| 12 |
models:
|
| 13 |
- abacusai/Smaug-AI-Image-Detector
|
| 14 |
-
short_description: Is this image AI-generated?
|
| 15 |
---
|
| 16 |
|
| 17 |
-
|
|
|
|
| 18 |
[abacusai/Smaug-AI-Image-Detector](https://huggingface.co/abacusai/Smaug-AI-Image-Detector).
|
| 19 |
|
| 20 |
Example images: a public-domain Flickr photo (CC0, via Megalith-10m) and an image generated with Z-Image-Turbo (Apache 2.0).
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Smaug AI Image Detector
|
| 3 |
emoji: ๐
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: indigo
|
|
|
|
| 11 |
license: apache-2.0
|
| 12 |
models:
|
| 13 |
- abacusai/Smaug-AI-Image-Detector
|
| 14 |
+
short_description: Is this image AI-generated? Free, open-source detector
|
| 15 |
---
|
| 16 |
|
| 17 |
+
Smaug AI Image Detector by Abacus.AI: upload an image or paste a link to check whether it was made by an AI image
|
| 18 |
+
generator. Free and open source, running
|
| 19 |
[abacusai/Smaug-AI-Image-Detector](https://huggingface.co/abacusai/Smaug-AI-Image-Detector).
|
| 20 |
|
| 21 |
Example images: a public-domain Flickr photo (CC0, via Megalith-10m) and an image generated with Z-Image-Turbo (Apache 2.0).
|
app.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
| 1 |
-
"""
|
| 2 |
import io
|
| 3 |
-
import
|
|
|
|
| 4 |
import sys
|
|
|
|
| 5 |
|
| 6 |
import gradio as gr
|
| 7 |
import requests
|
|
@@ -12,26 +14,46 @@ from PIL import Image
|
|
| 12 |
|
| 13 |
MODEL = 'abacusai/Smaug-AI-Image-Detector'
|
| 14 |
MAX_BYTES = 20 * 1024 * 1024
|
|
|
|
| 15 |
|
| 16 |
-
path = snapshot_download(MODEL
|
| 17 |
sys.path.insert(0, path)
|
| 18 |
from smaug_image_detect import SmaugImageDetect # noqa: E402 - ships with the model repo
|
| 19 |
|
| 20 |
det = SmaugImageDetect.from_pretrained(path, device="cuda") # ZeroGPU: place on cuda at startup, real GPU inside @spaces.GPU
|
| 21 |
|
| 22 |
|
| 23 |
-
@spaces.GPU(duration=
|
| 24 |
def score(img):
|
| 25 |
return det(img)
|
| 26 |
|
| 27 |
|
| 28 |
-
def
|
| 29 |
-
|
|
|
|
|
|
|
| 30 |
raise gr.Error('The link must start with http:// or https://')
|
| 31 |
try:
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
except requests.RequestException as e:
|
| 36 |
raise gr.Error(f'Could not download the image: {e}')
|
| 37 |
if len(data) > MAX_BYTES:
|
|
@@ -54,9 +76,9 @@ def predict(image, url):
|
|
| 54 |
return {'AI-generated': p, 'Real': 1 - p}, f'### {verdict}\nProbability AI-generated: **{p:.1%}** (threshold 50%)'
|
| 55 |
|
| 56 |
|
| 57 |
-
with gr.Blocks(title='AI Image Detector') as demo:
|
| 58 |
-
gr.Markdown('# AI Image Detector\nIs this image AI-generated? Upload one or paste a link. '
|
| 59 |
-
f'
|
| 60 |
with gr.Row():
|
| 61 |
with gr.Column():
|
| 62 |
image = gr.Image(type='pil', label='Image', sources=['upload', 'clipboard'])
|
|
@@ -70,4 +92,4 @@ with gr.Blocks(title='AI Image Detector') as demo:
|
|
| 70 |
'with a small AI-edited region as real. Do not use it as the only basis for decisions about people or their work.')
|
| 71 |
btn.click(predict, [image, url], [label, text])
|
| 72 |
|
| 73 |
-
demo.launch()
|
|
|
|
| 1 |
+
"""Smaug AI Image Detector demo: upload an image or paste a link, get the probability it is AI-generated."""
|
| 2 |
import io
|
| 3 |
+
import ipaddress
|
| 4 |
+
import socket
|
| 5 |
import sys
|
| 6 |
+
from urllib.parse import urljoin, urlparse
|
| 7 |
|
| 8 |
import gradio as gr
|
| 9 |
import requests
|
|
|
|
| 14 |
|
| 15 |
MODEL = 'abacusai/Smaug-AI-Image-Detector'
|
| 16 |
MAX_BYTES = 20 * 1024 * 1024
|
| 17 |
+
UA = 'AbacusAI-SmaugAIImageDetector/1.0 (https://huggingface.co/spaces/abacusai/Smaug-AI-Image-Detector)'
|
| 18 |
|
| 19 |
+
path = snapshot_download(MODEL)
|
| 20 |
sys.path.insert(0, path)
|
| 21 |
from smaug_image_detect import SmaugImageDetect # noqa: E402 - ships with the model repo
|
| 22 |
|
| 23 |
det = SmaugImageDetect.from_pretrained(path, device="cuda") # ZeroGPU: place on cuda at startup, real GPU inside @spaces.GPU
|
| 24 |
|
| 25 |
|
| 26 |
+
@spaces.GPU(duration=10) # scoring takes ~1-2 s; a short reservation leaves more of each visitor's GPU quota
|
| 27 |
def score(img):
|
| 28 |
return det(img)
|
| 29 |
|
| 30 |
|
| 31 |
+
def check_public(url):
|
| 32 |
+
"""Only fetch from public internet addresses: no localhost, private networks or cloud metadata endpoints."""
|
| 33 |
+
u = urlparse(url)
|
| 34 |
+
if u.scheme not in ('http', 'https') or not u.hostname:
|
| 35 |
raise gr.Error('The link must start with http:// or https://')
|
| 36 |
try:
|
| 37 |
+
addrs = {a[4][0] for a in socket.getaddrinfo(u.hostname, u.port or (443 if u.scheme == 'https' else 80))}
|
| 38 |
+
except socket.gaierror:
|
| 39 |
+
raise gr.Error('Could not find that website.')
|
| 40 |
+
if not all(ipaddress.ip_address(a.split('%')[0]).is_global for a in addrs):
|
| 41 |
+
raise gr.Error('That link does not point to a public website.')
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def fetch(url):
|
| 45 |
+
try:
|
| 46 |
+
for _ in range(4): # the link plus up to 3 redirects, each checked before it is followed
|
| 47 |
+
check_public(url)
|
| 48 |
+
r = requests.get(url, timeout=15, stream=True, allow_redirects=False, headers={'User-Agent': UA})
|
| 49 |
+
if r.is_redirect:
|
| 50 |
+
url = urljoin(url, r.headers['Location'])
|
| 51 |
+
continue
|
| 52 |
+
r.raise_for_status()
|
| 53 |
+
data = r.raw.read(MAX_BYTES + 1, decode_content=True)
|
| 54 |
+
break
|
| 55 |
+
else:
|
| 56 |
+
raise gr.Error('That link redirects too many times.')
|
| 57 |
except requests.RequestException as e:
|
| 58 |
raise gr.Error(f'Could not download the image: {e}')
|
| 59 |
if len(data) > MAX_BYTES:
|
|
|
|
| 76 |
return {'AI-generated': p, 'Real': 1 - p}, f'### {verdict}\nProbability AI-generated: **{p:.1%}** (threshold 50%)'
|
| 77 |
|
| 78 |
|
| 79 |
+
with gr.Blocks(title='Smaug AI Image Detector: is this image AI-generated?') as demo:
|
| 80 |
+
gr.Markdown('# Smaug AI Image Detector\nIs this image AI-generated? Upload one or paste a link. Free and open source: '
|
| 81 |
+
f'[{MODEL}](https://huggingface.co/{MODEL}) by [Abacus.AI](https://abacus.ai).')
|
| 82 |
with gr.Row():
|
| 83 |
with gr.Column():
|
| 84 |
image = gr.Image(type='pil', label='Image', sources=['upload', 'clipboard'])
|
|
|
|
| 92 |
'with a small AI-edited region as real. Do not use it as the only basis for decisions about people or their work.')
|
| 93 |
btn.click(predict, [image, url], [label, text])
|
| 94 |
|
| 95 |
+
demo.queue(max_size=40).launch(max_file_size='20mb')
|