Spaces:
Paused
Paused
feat: add 8x8 DCT blockiness and social media compression heuristics
Browse files- api/model_server.py +41 -2
api/model_server.py
CHANGED
|
@@ -146,10 +146,45 @@ def check_photographic_domain(
|
|
| 146 |
f"Non-photographic artwork."
|
| 147 |
)
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
return {
|
| 150 |
"is_non_photographic_graphic": is_ood,
|
| 151 |
"domain_status": domain_status,
|
| 152 |
-
"
|
|
|
|
|
|
|
| 153 |
}
|
| 154 |
|
| 155 |
|
|
@@ -252,10 +287,12 @@ class TamperingModelServer:
|
|
| 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"]
|
| 260 |
|
| 261 |
conf = round(probs[pred_idx].item(), 4)
|
|
@@ -274,5 +311,7 @@ class TamperingModelServer:
|
|
| 274 |
"model_variant": self.variant,
|
| 275 |
"is_non_photographic_graphic": is_non_photographic,
|
| 276 |
"domain_status": domain_status,
|
|
|
|
|
|
|
| 277 |
"warning": warning,
|
| 278 |
}
|
|
|
|
| 146 |
f"Non-photographic artwork."
|
| 147 |
)
|
| 148 |
|
| 149 |
+
# 8x8 DCT JPEG Blockiness calculation
|
| 150 |
+
blockiness = 1.0
|
| 151 |
+
try:
|
| 152 |
+
import cv2
|
| 153 |
+
gray = cv2.cvtColor(np.array(img_rgb), cv2.COLOR_RGB2GRAY).astype(np.float32)
|
| 154 |
+
h_blocks = h // 8
|
| 155 |
+
if h_blocks > 2 and w > 16:
|
| 156 |
+
boundary_diff = np.mean([np.mean(np.abs(gray[i * 8, :] - gray[i * 8 - 1, :])) for i in range(1, h_blocks)])
|
| 157 |
+
interior_diff = np.mean([np.mean(np.abs(gray[i * 8 - 1, :] - gray[i * 8 - 2, :])) for i in range(1, h_blocks)])
|
| 158 |
+
blockiness = float(boundary_diff / (interior_diff + 1e-6))
|
| 159 |
+
except Exception:
|
| 160 |
+
blockiness = 1.0
|
| 161 |
+
|
| 162 |
+
# Social media compression indicators
|
| 163 |
+
social_media_keywords = [
|
| 164 |
+
"whatsapp", "telegram", "messenger", "instagram", "discord",
|
| 165 |
+
"facebook", "fb_", "signal", "wechat", "viber"
|
| 166 |
+
]
|
| 167 |
+
has_sm_keyword = any(k in fname_lower for k in social_media_keywords)
|
| 168 |
+
is_social_media_compressed = has_sm_keyword or (blockiness > 1.25)
|
| 169 |
+
|
| 170 |
+
compression_warning = None
|
| 171 |
+
if is_social_media_compressed:
|
| 172 |
+
compression_warning = (
|
| 173 |
+
f"Social media lossy compression detected (8x8 DCT blockiness {blockiness:.2f}). "
|
| 174 |
+
f"Messaging platforms (WhatsApp/Telegram) discard high-frequency Fourier harmonics (see Manuscript Table 7). "
|
| 175 |
+
f"Treat 'Authentic' classifications with caution."
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
full_warning = warning
|
| 179 |
+
if compression_warning:
|
| 180 |
+
full_warning = f"{full_warning} | {compression_warning}" if full_warning else compression_warning
|
| 181 |
+
|
| 182 |
return {
|
| 183 |
"is_non_photographic_graphic": is_ood,
|
| 184 |
"domain_status": domain_status,
|
| 185 |
+
"is_social_media_compressed": is_social_media_compressed,
|
| 186 |
+
"compression_blockiness": round(blockiness, 3),
|
| 187 |
+
"warning": full_warning,
|
| 188 |
}
|
| 189 |
|
| 190 |
|
|
|
|
| 287 |
|
| 288 |
elapsed_ms = (time.time() - start) * 1000.0
|
| 289 |
|
| 290 |
+
# Pre-flight domain inspection (draw.io, screenshots, blank canvas, anime/art, social media compression)
|
| 291 |
ood_info = check_photographic_domain(image, filename=filename)
|
| 292 |
domain_status = ood_info["domain_status"]
|
| 293 |
is_non_photographic = ood_info["is_non_photographic_graphic"]
|
| 294 |
+
is_sm_compressed = ood_info.get("is_social_media_compressed", False)
|
| 295 |
+
blockiness = ood_info.get("compression_blockiness", 1.0)
|
| 296 |
warning = ood_info["warning"]
|
| 297 |
|
| 298 |
conf = round(probs[pred_idx].item(), 4)
|
|
|
|
| 311 |
"model_variant": self.variant,
|
| 312 |
"is_non_photographic_graphic": is_non_photographic,
|
| 313 |
"domain_status": domain_status,
|
| 314 |
+
"is_social_media_compressed": is_sm_compressed,
|
| 315 |
+
"compression_blockiness": blockiness,
|
| 316 |
"warning": warning,
|
| 317 |
}
|