harshraj21 commited on
Commit
174ad3f
·
verified ·
1 Parent(s): ec18e86

Update backend/app.py

Browse files
Files changed (1) hide show
  1. backend/app.py +50 -23
backend/app.py CHANGED
@@ -15,6 +15,8 @@ from pydantic import BaseModel
15
  from transformers import AutoModelForSequenceClassification, AutoTokenizer
16
  from torchvision import models, transforms
17
 
 
 
18
 
19
  ROOT_DIR = Path(__file__).resolve().parents[1]
20
  MODEL_PATH = Path(os.getenv("TRUTHSHIELD_MODEL_PATH", ROOT_DIR / "ML" / "truthshield_aigc_efficientnet_b0_final.pth"))
@@ -108,12 +110,18 @@ def load_text_model() -> tuple[Any, Any]:
108
  text_tokenizer, text_model = load_text_model()
109
 
110
 
111
- def result_payload(fake_probability: float) -> dict[str, Any]:
112
  real_probability = 1.0 - fake_probability
113
  predicted_label = "fake" if fake_probability >= real_probability else "real"
114
  confidence = max(fake_probability, real_probability)
 
115
 
116
- if confidence < CONFIDENCE_THRESHOLD:
 
 
 
 
 
117
  verdict = "uncertain"
118
  impact = "medium"
119
  summary = "The local AIGC image model found mixed visual evidence, so this image should be reviewed manually."
@@ -126,30 +134,47 @@ def result_payload(fake_probability: float) -> dict[str, Any]:
126
  impact = "low"
127
  summary = "The local AIGC image model classified this image as likely authentic."
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  return {
130
  "verdict": verdict,
131
  "confidence": round(float(confidence), 4),
132
  "summary": summary,
133
- "signals": [
134
- {
135
- "label": "AIGC EfficientNet-B0 prediction",
136
- "impact": impact,
137
- "note": (
138
- f"Class probabilities using order {CLASS_ORDER}: "
139
- f"real={real_probability:.2%}, fake={fake_probability:.2%}."
140
- ),
141
- },
142
- {
143
- "label": "Local AIGC model inference",
144
- "impact": "low",
145
- "note": f"Model loaded from {MODEL_PATH.name} on {device.type.upper()} with {IMAGE_SIZE}x{IMAGE_SIZE} preprocessing.",
146
- },
147
- ],
148
- "recommended_next_steps": [
149
- "Use this model output as a screening signal, not final proof.",
150
- "Check source, metadata, and reverse-image search results before escalation.",
151
- "Treat screenshots, heavy compression, crops, and out-of-distribution images cautiously.",
152
- ],
153
  "metadata": {
154
  "model": "efficientnet_b0",
155
  "model_path": str(MODEL_PATH),
@@ -160,6 +185,7 @@ def result_payload(fake_probability: float) -> dict[str, Any]:
160
  "fake": round(float(fake_probability), 6),
161
  },
162
  "threshold": CONFIDENCE_THRESHOLD,
 
163
  },
164
  }
165
 
@@ -350,6 +376,7 @@ async def predict_image(file: UploadFile = File(...)) -> dict[str, Any]:
350
  except UnidentifiedImageError as exc:
351
  raise HTTPException(status_code=400, detail="Could not read image file.") from exc
352
 
 
353
  tensor = preprocess(image).unsqueeze(0).to(device)
354
 
355
  with torch.inference_mode():
@@ -357,7 +384,7 @@ async def predict_image(file: UploadFile = File(...)) -> dict[str, Any]:
357
  probabilities = torch.softmax(logits, dim=1).squeeze(0).detach().cpu().tolist()
358
 
359
  probability_by_class = {CLASS_ORDER[index]: float(probabilities[index]) for index in range(2)}
360
- return result_payload(fake_probability=probability_by_class["fake"])
361
 
362
 
363
  @app.post("/predict-text")
 
15
  from transformers import AutoModelForSequenceClassification, AutoTokenizer
16
  from torchvision import models, transforms
17
 
18
+ from backend.image_evidence import image_watermark_analysis
19
+
20
 
21
  ROOT_DIR = Path(__file__).resolve().parents[1]
22
  MODEL_PATH = Path(os.getenv("TRUTHSHIELD_MODEL_PATH", ROOT_DIR / "ML" / "truthshield_aigc_efficientnet_b0_final.pth"))
 
110
  text_tokenizer, text_model = load_text_model()
111
 
112
 
113
+ def result_payload(fake_probability: float, watermark_analysis: dict[str, Any] | None = None) -> dict[str, Any]:
114
  real_probability = 1.0 - fake_probability
115
  predicted_label = "fake" if fake_probability >= real_probability else "real"
116
  confidence = max(fake_probability, real_probability)
117
+ watermark_detected = bool(watermark_analysis and watermark_analysis.get("detected"))
118
 
119
+ if watermark_detected:
120
+ verdict = "likely_manipulated"
121
+ impact = "high"
122
+ confidence = max(confidence, 0.92)
123
+ summary = "A visible AI-generator watermark was detected, so this image should be treated as AI-generated even if the classifier score is mixed."
124
+ elif confidence < CONFIDENCE_THRESHOLD:
125
  verdict = "uncertain"
126
  impact = "medium"
127
  summary = "The local AIGC image model found mixed visual evidence, so this image should be reviewed manually."
 
134
  impact = "low"
135
  summary = "The local AIGC image model classified this image as likely authentic."
136
 
137
+ signals = [
138
+ {
139
+ "label": "AIGC EfficientNet-B0 prediction",
140
+ "impact": impact,
141
+ "note": (
142
+ f"Class probabilities using order {CLASS_ORDER}: "
143
+ f"real={real_probability:.2%}, fake={fake_probability:.2%}."
144
+ ),
145
+ },
146
+ {
147
+ "label": "Local AIGC model inference",
148
+ "impact": "low",
149
+ "note": f"Model loaded from {MODEL_PATH.name} on {device.type.upper()} with {IMAGE_SIZE}x{IMAGE_SIZE} preprocessing.",
150
+ },
151
+ ]
152
+
153
+ if watermark_detected:
154
+ signals.insert(
155
+ 0,
156
+ {
157
+ "label": "Visible generator watermark",
158
+ "impact": "high",
159
+ "note": "A Gemini-style sparkle watermark was found in the lower-right corner, which is direct evidence that the image was generated or exported by an AI tool.",
160
+ },
161
+ )
162
+
163
+ recommended_next_steps = [
164
+ "Use this model output as a screening signal, not final proof.",
165
+ "Check source, metadata, and reverse-image search results before escalation.",
166
+ "Treat screenshots, heavy compression, crops, and out-of-distribution images cautiously.",
167
+ ]
168
+
169
+ if watermark_detected:
170
+ recommended_next_steps.insert(0, "Preserve the original file because the visible generator watermark is the strongest evidence.")
171
+
172
  return {
173
  "verdict": verdict,
174
  "confidence": round(float(confidence), 4),
175
  "summary": summary,
176
+ "signals": signals,
177
+ "recommended_next_steps": recommended_next_steps,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  "metadata": {
179
  "model": "efficientnet_b0",
180
  "model_path": str(MODEL_PATH),
 
185
  "fake": round(float(fake_probability), 6),
186
  },
187
  "threshold": CONFIDENCE_THRESHOLD,
188
+ "watermark_analysis": watermark_analysis or {"detected": False, "matches": []},
189
  },
190
  }
191
 
 
376
  except UnidentifiedImageError as exc:
377
  raise HTTPException(status_code=400, detail="Could not read image file.") from exc
378
 
379
+ watermark_analysis = image_watermark_analysis(image)
380
  tensor = preprocess(image).unsqueeze(0).to(device)
381
 
382
  with torch.inference_mode():
 
384
  probabilities = torch.softmax(logits, dim=1).squeeze(0).detach().cpu().tolist()
385
 
386
  probability_by_class = {CLASS_ORDER[index]: float(probabilities[index]) for index in range(2)}
387
+ return result_payload(fake_probability=probability_by_class["fake"], watermark_analysis=watermark_analysis)
388
 
389
 
390
  @app.post("/predict-text")