MohsinElis commited on
Commit
9da1c4a
·
verified ·
1 Parent(s): 91554bd

Deploy production Patch-MFFT Tampering Forensics API

Browse files
Files changed (2) hide show
  1. api/main.py +1 -1
  2. api/model_server.py +49 -9
api/main.py CHANGED
@@ -126,6 +126,6 @@ async def predict(
126
  raise HTTPException(status_code=400, detail="Corrupted image file.")
127
 
128
  server = model_registry[variant]
129
- result = server.predict(image)
130
 
131
  return PredictionResponse(**result)
 
126
  raise HTTPException(status_code=400, detail="Corrupted image file.")
127
 
128
  server = model_registry[variant]
129
+ result = server.predict(image, filename=file.filename)
130
 
131
  return PredictionResponse(**result)
api/model_server.py CHANGED
@@ -59,11 +59,14 @@ TURBO_LUT = np.array([
59
  ], dtype=np.uint8)
60
 
61
 
62
- def check_photographic_domain(image: Image.Image) -> Dict[str, Any]:
 
 
 
63
  """
64
  Inspects whether an input image conforms to optical photographic statistics
65
  or represents Out-of-Distribution (OOD) digital graphics (e.g. draw.io diagrams,
66
- software screenshots, vector line art, pure blank canvas, or static noise).
67
  """
68
  img_rgb = image.convert("RGB")
69
  arr = np.array(img_rgb, dtype=np.float32)
@@ -72,7 +75,7 @@ def check_photographic_domain(image: Image.Image) -> Dict[str, Any]:
72
 
73
  std_dev = float(np.std(arr))
74
 
75
- # Fast color quantization to 8 bins per channel (512 total possible)
76
  quantized = (arr // 32).astype(np.int32)
77
  hash_vals = quantized[:, :, 0] * 64 + quantized[:, :, 1] * 8 + quantized[:, :, 2]
78
  unique, counts = np.unique(hash_vals, return_counts=True)
@@ -85,30 +88,63 @@ def check_photographic_domain(image: Image.Image) -> Dict[str, Any]:
85
  / total_pixels
86
  )
87
 
 
 
 
 
 
 
 
 
 
88
  is_ood = False
89
  domain_status = "photographic"
90
  warning = None
91
 
92
- if std_dev < 1.5:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  is_ood = True
94
  domain_status = "solid_canvas_ood"
95
  warning = (
96
  "Input is a solid blank canvas with zero sensor noise variance (not an optical photograph)."
97
  )
98
- elif flat_pixels_exact > 0.60 and unique_bins < 80:
99
  is_ood = True
100
  domain_status = "non_photographic_graphic"
101
  warning = (
102
  f"Input appears to be a digital diagram or vector graphic (e.g., draw.io, UI screenshot) "
103
  f"with {flat_pixels_exact * 100:.1f}% flat background. Forensics models require camera optical physics."
104
  )
105
- elif unique_bins < 25 and top_color_ratio > 0.50:
106
  is_ood = True
107
  domain_status = "non_photographic_graphic"
108
  warning = (
109
  f"Input appears to be a digital sketch or illustration with only {unique_bins} discrete color bins. "
110
  f"Forensics models require camera optical physics."
111
  )
 
 
 
 
 
 
 
112
 
113
  return {
114
  "is_non_photographic_graphic": is_ood,
@@ -173,7 +209,11 @@ class TamperingModelServer:
173
  return tensor.to(self.device)
174
 
175
  @torch.no_grad()
176
- def predict(self, image: Image.Image) -> Dict[str, Any]:
 
 
 
 
177
  start = time.time()
178
  tensor = self._preprocess(image)
179
 
@@ -212,8 +252,8 @@ class TamperingModelServer:
212
 
213
  elapsed_ms = (time.time() - start) * 1000.0
214
 
215
- # Pre-flight domain inspection (draw.io, screenshots, blank canvas)
216
- ood_info = check_photographic_domain(image)
217
  domain_status = ood_info["domain_status"]
218
  is_non_photographic = ood_info["is_non_photographic_graphic"]
219
  warning = ood_info["warning"]
 
59
  ], dtype=np.uint8)
60
 
61
 
62
+ def check_photographic_domain(
63
+ image: Image.Image,
64
+ filename: Optional[str] = None,
65
+ ) -> Dict[str, Any]:
66
  """
67
  Inspects whether an input image conforms to optical photographic statistics
68
  or represents Out-of-Distribution (OOD) digital graphics (e.g. draw.io diagrams,
69
+ software screenshots, vector line art, pure blank canvas, or stylized anime/art).
70
  """
71
  img_rgb = image.convert("RGB")
72
  arr = np.array(img_rgb, dtype=np.float32)
 
75
 
76
  std_dev = float(np.std(arr))
77
 
78
+ # Color quantization
79
  quantized = (arr // 32).astype(np.int32)
80
  hash_vals = quantized[:, :, 0] * 64 + quantized[:, :, 1] * 8 + quantized[:, :, 2]
81
  unique, counts = np.unique(hash_vals, return_counts=True)
 
88
  / total_pixels
89
  )
90
 
91
+ # Color saturation in HSV
92
+ try:
93
+ import cv2
94
+ hsv = cv2.cvtColor(np.array(img_rgb), cv2.COLOR_RGB2HSV)
95
+ sat = hsv[:, :, 1]
96
+ high_sat_ratio = float(np.mean(sat > 100))
97
+ except Exception:
98
+ high_sat_ratio = 0.0
99
+
100
  is_ood = False
101
  domain_status = "photographic"
102
  warning = None
103
 
104
+ # Check 1: Filename metadata indication
105
+ fname_lower = (filename or "").lower()
106
+ art_keywords = [
107
+ "ghibli", "chatgpt", "dall-e", "dalle", "midjourney", "anime",
108
+ "cartoon", "illustration", "sketch", "draw.io", "diagram",
109
+ "flowchart", "figma", "screenshot", "clipart", "render"
110
+ ]
111
+ found_keywords = [k for k in art_keywords if k in fname_lower]
112
+
113
+ if found_keywords:
114
+ is_ood = True
115
+ domain_status = "stylized_digital_graphic"
116
+ kw_str = ", ".join(found_keywords)
117
+ warning = (
118
+ f"Non-photographic / stylized digital artwork detected ({kw_str}). "
119
+ f"Forensics models are calibrated on camera optics; synthetic cartoon styling alters natural sensor noise."
120
+ )
121
+ elif std_dev < 1.5:
122
  is_ood = True
123
  domain_status = "solid_canvas_ood"
124
  warning = (
125
  "Input is a solid blank canvas with zero sensor noise variance (not an optical photograph)."
126
  )
127
+ elif flat_pixels_exact > 0.50 and unique_bins < 100:
128
  is_ood = True
129
  domain_status = "non_photographic_graphic"
130
  warning = (
131
  f"Input appears to be a digital diagram or vector graphic (e.g., draw.io, UI screenshot) "
132
  f"with {flat_pixels_exact * 100:.1f}% flat background. Forensics models require camera optical physics."
133
  )
134
+ elif unique_bins < 25 and top_color_ratio > 0.40:
135
  is_ood = True
136
  domain_status = "non_photographic_graphic"
137
  warning = (
138
  f"Input appears to be a digital sketch or illustration with only {unique_bins} discrete color bins. "
139
  f"Forensics models require camera optical physics."
140
  )
141
+ elif high_sat_ratio > 0.85:
142
+ is_ood = True
143
+ domain_status = "stylized_digital_graphic"
144
+ warning = (
145
+ f"Hyper-saturated digital illustration profile detected ({high_sat_ratio * 100:.1f}% saturated pixels). "
146
+ f"Non-photographic artwork."
147
+ )
148
 
149
  return {
150
  "is_non_photographic_graphic": is_ood,
 
209
  return tensor.to(self.device)
210
 
211
  @torch.no_grad()
212
+ def predict(
213
+ self,
214
+ image: Image.Image,
215
+ filename: Optional[str] = None,
216
+ ) -> Dict[str, Any]:
217
  start = time.time()
218
  tensor = self._preprocess(image)
219
 
 
252
 
253
  elapsed_ms = (time.time() - start) * 1000.0
254
 
255
+ # Pre-flight domain inspection (draw.io, screenshots, blank canvas, anime/art)
256
+ ood_info = check_photographic_domain(image, filename=filename)
257
  domain_status = ood_info["domain_status"]
258
  is_non_photographic = ood_info["is_non_photographic_graphic"]
259
  warning = ood_info["warning"]