Spaces:
Running
Running
Commit ·
aff4477
1
Parent(s): ecf44ac
fix(api,deploy): correct AttributeError, missing lifespan, Dockerfile drift, XGBoost double-boost
Browse files- main.py: fix settings.API_TITLE/API_VERSION -> PROJECT_NAME/VERSION (AttributeError)
- main.py: pass lifespan= to FastAPI constructor so startup/shutdown events fire
- main.py: remove emoji from logger strings
- advanced_ensemble_detector.py: guard suspicious-count boost behind xgb_model is None
to prevent double-multiplying an already-calibrated XGBoost probability
- advanced_ensemble_detector.py: fix stale comment (25 -> 26 signals)
- Dockerfile: sync all package versions with requirements.txt; add starlette>=0.40.0,
xgboost>=2.0.0, shap>=0.45.0 which were missing from Docker build
- render.yaml: replace wildcard CORS_ORIGINS with explicit allowed origins
- Dockerfile +9 -7
- backend/main.py +11 -23
- backend/services/advanced_ensemble_detector.py +8 -8
- render.yaml +1 -1
Dockerfile
CHANGED
|
@@ -7,7 +7,6 @@ RUN apt-get update && apt-get install -y \
|
|
| 7 |
WORKDIR /app
|
| 8 |
COPY . .
|
| 9 |
|
| 10 |
-
# Install lightweight packages first, then heavy ones
|
| 11 |
RUN pip install --no-cache-dir --upgrade pip && \
|
| 12 |
pip install --no-cache-dir \
|
| 13 |
fastapi==0.115.0 \
|
|
@@ -17,17 +16,20 @@ RUN pip install --no-cache-dir --upgrade pip && \
|
|
| 17 |
python-dotenv==1.0.0 \
|
| 18 |
python-magic==0.4.27 \
|
| 19 |
python-multipart==0.0.22 \
|
| 20 |
-
|
|
|
|
| 21 |
imagehash==4.3.1 \
|
| 22 |
-
numpy=
|
| 23 |
scipy==1.11.4 \
|
| 24 |
-
scikit-learn==1.
|
| 25 |
opencv-python==4.9.0.80 \
|
| 26 |
slowapi==0.1.9 \
|
| 27 |
PyWavelets==1.4.1 \
|
| 28 |
scikit-image==0.22.0 \
|
| 29 |
-
cryptography==
|
| 30 |
-
psutil==5.9.8
|
|
|
|
|
|
|
| 31 |
pip install --no-cache-dir \
|
| 32 |
torch==2.6.0 \
|
| 33 |
torchvision==0.21.0 \
|
|
@@ -38,7 +40,7 @@ RUN pip install --no-cache-dir --upgrade pip && \
|
|
| 38 |
safetensors==0.4.5 \
|
| 39 |
ftfy==6.1.1 \
|
| 40 |
regex==2023.12.25 \
|
| 41 |
-
tqdm==4.66.
|
| 42 |
"clip @ git+https://github.com/openai/CLIP.git"
|
| 43 |
|
| 44 |
ENV PYTHONPATH=/app
|
|
|
|
| 7 |
WORKDIR /app
|
| 8 |
COPY . .
|
| 9 |
|
|
|
|
| 10 |
RUN pip install --no-cache-dir --upgrade pip && \
|
| 11 |
pip install --no-cache-dir \
|
| 12 |
fastapi==0.115.0 \
|
|
|
|
| 16 |
python-dotenv==1.0.0 \
|
| 17 |
python-magic==0.4.27 \
|
| 18 |
python-multipart==0.0.22 \
|
| 19 |
+
"starlette>=0.40.0" \
|
| 20 |
+
Pillow==12.1.1 \
|
| 21 |
imagehash==4.3.1 \
|
| 22 |
+
"numpy>=1.24.0,<2.0.0" \
|
| 23 |
scipy==1.11.4 \
|
| 24 |
+
scikit-learn==1.5.0 \
|
| 25 |
opencv-python==4.9.0.80 \
|
| 26 |
slowapi==0.1.9 \
|
| 27 |
PyWavelets==1.4.1 \
|
| 28 |
scikit-image==0.22.0 \
|
| 29 |
+
cryptography==46.0.6 \
|
| 30 |
+
psutil==5.9.8 \
|
| 31 |
+
xgboost>=2.0.0 \
|
| 32 |
+
shap>=0.45.0 && \
|
| 33 |
pip install --no-cache-dir \
|
| 34 |
torch==2.6.0 \
|
| 35 |
torchvision==0.21.0 \
|
|
|
|
| 40 |
safetensors==0.4.5 \
|
| 41 |
ftfy==6.1.1 \
|
| 42 |
regex==2023.12.25 \
|
| 43 |
+
tqdm==4.66.3 \
|
| 44 |
"clip @ git+https://github.com/openai/CLIP.git"
|
| 45 |
|
| 46 |
ENV PYTHONPATH=/app
|
backend/main.py
CHANGED
|
@@ -1,6 +1,3 @@
|
|
| 1 |
-
"""
|
| 2 |
-
VeriFile-X API - Privacy-preserving digital forensics platform.
|
| 3 |
-
"""
|
| 4 |
from fastapi import FastAPI, Request
|
| 5 |
from fastapi.middleware.cors import CORSMiddleware
|
| 6 |
from fastapi.responses import FileResponse
|
|
@@ -23,25 +20,24 @@ limiter = Limiter(key_func=get_remote_address)
|
|
| 23 |
|
| 24 |
@asynccontextmanager
|
| 25 |
async def lifespan(app: FastAPI):
|
| 26 |
-
"
|
| 27 |
-
logger.info("🚀 VeriFile-X API starting up...")
|
| 28 |
logger.info(f"Debug mode: {settings.DEBUG}")
|
| 29 |
logger.info(f"Max file size: {settings.MAX_FILE_SIZE_MB}MB")
|
| 30 |
yield
|
| 31 |
-
logger.info("
|
| 32 |
|
| 33 |
|
| 34 |
app = FastAPI(
|
| 35 |
title=settings.PROJECT_NAME,
|
| 36 |
version=settings.VERSION,
|
| 37 |
-
debug=settings.DEBUG
|
|
|
|
| 38 |
)
|
| 39 |
|
| 40 |
app.state.limiter = limiter
|
| 41 |
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
| 42 |
app.add_middleware(SlowAPIMiddleware)
|
| 43 |
|
| 44 |
-
# CORS Configuration
|
| 45 |
app.add_middleware(
|
| 46 |
CORSMiddleware,
|
| 47 |
allow_origins=settings.cors_origins_list,
|
|
@@ -50,36 +46,28 @@ app.add_middleware(
|
|
| 50 |
allow_headers=["*"],
|
| 51 |
)
|
| 52 |
|
| 53 |
-
|
| 54 |
-
# Register API routers
|
| 55 |
app.include_router(upload.router)
|
| 56 |
app.include_router(analyze.router)
|
| 57 |
|
| 58 |
|
| 59 |
@app.get("/")
|
| 60 |
async def root():
|
| 61 |
-
"""
|
| 62 |
-
Serve frontend HTML.
|
| 63 |
-
In production, this serves the web UI.
|
| 64 |
-
"""
|
| 65 |
frontend_path = os.path.join(os.path.dirname(__file__), "..", "frontend", "index.html")
|
| 66 |
if os.path.exists(frontend_path):
|
| 67 |
return FileResponse(frontend_path)
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
}
|
| 75 |
|
| 76 |
|
| 77 |
@app.get("/health")
|
| 78 |
@limiter.limit("60/minute")
|
| 79 |
async def health_check(request: Request):
|
| 80 |
-
"""Health check endpoint."""
|
| 81 |
return {
|
| 82 |
"status": "healthy",
|
| 83 |
"debug_mode": settings.DEBUG,
|
| 84 |
-
"timestamp": datetime.now().isoformat()
|
| 85 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from fastapi import FastAPI, Request
|
| 2 |
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
from fastapi.responses import FileResponse
|
|
|
|
| 20 |
|
| 21 |
@asynccontextmanager
|
| 22 |
async def lifespan(app: FastAPI):
|
| 23 |
+
logger.info("VeriFile-X API starting up")
|
|
|
|
| 24 |
logger.info(f"Debug mode: {settings.DEBUG}")
|
| 25 |
logger.info(f"Max file size: {settings.MAX_FILE_SIZE_MB}MB")
|
| 26 |
yield
|
| 27 |
+
logger.info("VeriFile-X API shutting down")
|
| 28 |
|
| 29 |
|
| 30 |
app = FastAPI(
|
| 31 |
title=settings.PROJECT_NAME,
|
| 32 |
version=settings.VERSION,
|
| 33 |
+
debug=settings.DEBUG,
|
| 34 |
+
lifespan=lifespan,
|
| 35 |
)
|
| 36 |
|
| 37 |
app.state.limiter = limiter
|
| 38 |
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
| 39 |
app.add_middleware(SlowAPIMiddleware)
|
| 40 |
|
|
|
|
| 41 |
app.add_middleware(
|
| 42 |
CORSMiddleware,
|
| 43 |
allow_origins=settings.cors_origins_list,
|
|
|
|
| 46 |
allow_headers=["*"],
|
| 47 |
)
|
| 48 |
|
|
|
|
|
|
|
| 49 |
app.include_router(upload.router)
|
| 50 |
app.include_router(analyze.router)
|
| 51 |
|
| 52 |
|
| 53 |
@app.get("/")
|
| 54 |
async def root():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
frontend_path = os.path.join(os.path.dirname(__file__), "..", "frontend", "index.html")
|
| 56 |
if os.path.exists(frontend_path):
|
| 57 |
return FileResponse(frontend_path)
|
| 58 |
+
return {
|
| 59 |
+
"name": settings.PROJECT_NAME,
|
| 60 |
+
"version": settings.VERSION,
|
| 61 |
+
"status": "operational",
|
| 62 |
+
"docs": "/docs",
|
| 63 |
+
}
|
|
|
|
| 64 |
|
| 65 |
|
| 66 |
@app.get("/health")
|
| 67 |
@limiter.limit("60/minute")
|
| 68 |
async def health_check(request: Request):
|
|
|
|
| 69 |
return {
|
| 70 |
"status": "healthy",
|
| 71 |
"debug_mode": settings.DEBUG,
|
| 72 |
+
"timestamp": datetime.now().isoformat(),
|
| 73 |
}
|
backend/services/advanced_ensemble_detector.py
CHANGED
|
@@ -60,7 +60,7 @@ class AdvancedEnsembleDetector(StatisticalDetector):
|
|
| 60 |
Run complete advanced detection with all methods.
|
| 61 |
|
| 62 |
Returns:
|
| 63 |
-
Complete report with
|
| 64 |
"""
|
| 65 |
logger.info(f"Starting advanced ensemble detection for {self.filename}")
|
| 66 |
|
|
@@ -86,7 +86,7 @@ class AdvancedEnsembleDetector(StatisticalDetector):
|
|
| 86 |
# Add DCT frequency signal
|
| 87 |
dct_result = detect_dct_artifacts(self.image_bytes, self.filename)
|
| 88 |
|
| 89 |
-
# Combine all signals (now
|
| 90 |
all_signals = base_report["all_signals"] + [
|
| 91 |
dire_result, clip_result, own_result, prnu_result,
|
| 92 |
ela_result, metadata_result, dct_result,
|
|
@@ -145,12 +145,12 @@ class AdvancedEnsembleDetector(StatisticalDetector):
|
|
| 145 |
|
| 146 |
suspicious_count = sum(1 for s in all_signals if s["score"] > 0.5)
|
| 147 |
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
# Classification
|
| 155 |
if weighted_score > 0.80:
|
| 156 |
classification = "likely_ai_generated"
|
|
|
|
| 60 |
Run complete advanced detection with all methods.
|
| 61 |
|
| 62 |
Returns:
|
| 63 |
+
Complete report with 26 detection signals
|
| 64 |
"""
|
| 65 |
logger.info(f"Starting advanced ensemble detection for {self.filename}")
|
| 66 |
|
|
|
|
| 86 |
# Add DCT frequency signal
|
| 87 |
dct_result = detect_dct_artifacts(self.image_bytes, self.filename)
|
| 88 |
|
| 89 |
+
# Combine all signals (now 26 total)
|
| 90 |
all_signals = base_report["all_signals"] + [
|
| 91 |
dire_result, clip_result, own_result, prnu_result,
|
| 92 |
ela_result, metadata_result, dct_result,
|
|
|
|
| 145 |
|
| 146 |
suspicious_count = sum(1 for s in all_signals if s["score"] > 0.5)
|
| 147 |
|
| 148 |
+
if xgb_model is None:
|
| 149 |
+
if suspicious_count >= 12:
|
| 150 |
+
weighted_score = min(1.0, weighted_score * 1.3)
|
| 151 |
+
elif suspicious_count >= 10:
|
| 152 |
+
weighted_score = min(1.0, weighted_score * 1.2)
|
| 153 |
+
|
| 154 |
# Classification
|
| 155 |
if weighted_score > 0.80:
|
| 156 |
classification = "likely_ai_generated"
|
render.yaml
CHANGED
|
@@ -11,7 +11,7 @@ services:
|
|
| 11 |
- key: DEBUG
|
| 12 |
value: false
|
| 13 |
- key: CORS_ORIGINS
|
| 14 |
-
value: "
|
| 15 |
- key: MAX_FILE_SIZE_MB
|
| 16 |
value: 50
|
| 17 |
- key: MAX_ANALYSIS_SIZE_MB
|
|
|
|
| 11 |
- key: DEBUG
|
| 12 |
value: false
|
| 13 |
- key: CORS_ORIGINS
|
| 14 |
+
value: "https://abinaze.github.io,https://abinazebinoy-verifile-x-api.hf.space"
|
| 15 |
- key: MAX_FILE_SIZE_MB
|
| 16 |
value: 50
|
| 17 |
- key: MAX_ANALYSIS_SIZE_MB
|