abinazebinoy commited on
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 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
- Pillow==11.2.1 \
 
21
  imagehash==4.3.1 \
22
- numpy==1.26.3 \
23
  scipy==1.11.4 \
24
- scikit-learn==1.4.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==41.0.7 \
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.1 \
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
- """Startup and shutdown lifecycle events."""
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("🛑 VeriFile-X API shutting down...")
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
- else:
69
- return {
70
- "name": settings.API_TITLE,
71
- "version": settings.API_VERSION,
72
- "status": "operational",
73
- "docs": "/docs"
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 25 detection signals
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 25 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,12 +145,12 @@ class AdvancedEnsembleDetector(StatisticalDetector):
145
 
146
  suspicious_count = sum(1 for s in all_signals if s["score"] > 0.5)
147
 
148
- # Boost if multiple independent methods agree
149
- if suspicious_count >= 12: # More than half
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"
 
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