Spaces:
Sleeping
Sleeping
File size: 3,142 Bytes
bb5ec1b 5ccd8c3 bb5ec1b 5ccd8c3 bb5ec1b 5ccd8c3 bb5ec1b | 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 | import io
from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from PIL import Image, UnidentifiedImageError
from pydantic import BaseModel
from .config import get_settings
from .detector import NoFaceDetectedError, Prediction, get_detector
settings = get_settings()
app = FastAPI(
title="DeepVerify detection service",
description=(
"Image deepfake detection using the pre-trained yermandy/deepfake-detection "
"checkpoint (CLIP ViT-L/14 + LN-tuning, MIT). No model is trained here."
),
version="1.0.0",
)
app.add_middleware(
CORSMiddleware,
allow_origins=[o.strip() for o in settings.allowed_origins.split(",")],
allow_methods=["GET", "POST"],
allow_headers=["*"],
)
def run_prediction(image: Image.Image) -> Prediction:
"""Detect and score, in-process.
A seam, not indirection: on ZeroGPU the forward pass has to happen inside a
GPU-scoped function, so the Space replaces this with its own implementation
and the route below stays identical in both deployments.
"""
return get_detector().predict(image)
class Health(BaseModel):
status: str
model_id: str
weights_sha256: str
device: str
threshold: float
class DetectionResponse(BaseModel):
is_deepfake: bool
label: str
fake_probability: float
real_probability: float
threshold: float
face_box: tuple[int, int, int, int]
face_confidence: float
model_id: str
@app.get("/health", response_model=Health)
def health() -> Health:
detector = get_detector()
return Health(
status="ok",
model_id=detector.model_id,
weights_sha256=detector.weights_sha256,
device=str(detector.device),
threshold=detector.threshold,
)
@app.post("/v1/detect", response_model=DetectionResponse)
async def detect(file: UploadFile = File(...)) -> DetectionResponse:
contents = await file.read()
if len(contents) > settings.max_upload_bytes:
raise HTTPException(
status_code=413,
detail=f"file exceeds {settings.max_upload_bytes // (1024 * 1024)}MB limit",
)
try:
image = Image.open(io.BytesIO(contents))
image.load()
except (UnidentifiedImageError, OSError):
raise HTTPException(status_code=400, detail="file is not a readable image")
detector = get_detector()
try:
prediction = run_prediction(image)
except NoFaceDetectedError:
raise HTTPException(
status_code=422,
detail=(
"no face detected. This detector is trained on face crops, so a score "
"on a face-free image would be meaningless."
),
)
return DetectionResponse(
is_deepfake=prediction.is_deepfake,
label=prediction.label,
fake_probability=prediction.fake_probability,
real_probability=prediction.real_probability,
threshold=prediction.threshold,
face_box=prediction.face_box,
face_confidence=prediction.face_confidence,
model_id=detector.model_id,
)
|