Spaces:
Sleeping
Sleeping
| import cv2 | |
| import numpy as np | |
| from core.config import FUSION_WEIGHTS, TAMPER_THRESHOLD | |
| from core.types import Detection, Verdict | |
| LABELS = { | |
| 'ela': 'ELA anomaly (pixel error inconsistency)', | |
| 'noise': 'Noise fingerprint mismatch', | |
| 'copy_move': 'Cloned/duplicated region detected', | |
| 'double_jpeg': 'Double compression artifact', | |
| 'font': 'Font or text baseline inconsistency', | |
| 'metadata': 'Suspicious metadata', | |
| 'ai_generated': 'AI-generated image signature', | |
| 'model': 'CNN tampering localization', | |
| } | |
| def build_evidence(detections: list[Detection]) -> list[str]: | |
| messages = [] | |
| for det in detections: | |
| if det.score > 0.3: | |
| base = LABELS.get(det.detector_name, det.detector_name) | |
| flags = det.details.get('flags', []) | |
| if flags: | |
| messages.append(f'{base}: {"; ".join(flags)}') | |
| else: | |
| messages.append(f'{base} (score={det.score:.0%})') | |
| return messages or ['No significant tampering signals detected'] | |
| def fuse(detections: list[Detection], model_confidence: float = 0.0, | |
| model_heatmap: np.ndarray | None = None, | |
| weights: dict | None = None) -> Verdict: | |
| w = weights or FUSION_WEIGHTS | |
| scores = {d.detector_name: d.score for d in detections} | |
| scores['model'] = model_confidence | |
| # ββ Base: weighted average of every signal ββ | |
| weighted = sum(w.get(name, 0.0) * s for name, s in scores.items()) | |
| # ββ Strong-signal boost: a single confident specialist shouldn't be diluted | |
| # to nothing by the calmer detectors. Only the RELIABLE detectors get to | |
| # drive this (copy_move / double_jpeg are too noisy on documents). | |
| strongest = max(scores.get('ai_generated', 0.0), | |
| scores.get('model', 0.0), | |
| scores.get('ela', 0.0)) | |
| fused_score = float(np.clip(max(weighted, 0.55 * strongest + 0.45 * weighted), 0, 1)) | |
| # ββ Decide a human-readable label ββ | |
| ai = scores.get('ai_generated', 0.0) | |
| is_flagged = fused_score >= TAMPER_THRESHOLD | |
| if is_flagged and ai >= 0.6 and ai >= strongest - 1e-6: | |
| label = 'AI-GENERATED' | |
| elif is_flagged: | |
| label = 'TAMPERED' | |
| else: | |
| label = 'AUTHENTIC' | |
| # ββ Merge heatmaps (weighted) ββ | |
| heatmaps = [(d.heatmap, w.get(d.detector_name, 0.0)) | |
| for d in detections if d.heatmap is not None] | |
| if model_heatmap is not None: | |
| heatmaps.append((model_heatmap, w.get('model', 0.2))) | |
| if heatmaps: | |
| ref_h, ref_w = heatmaps[0][0].shape | |
| merged = np.zeros((ref_h, ref_w), dtype=np.float32) | |
| total_w = 0.0 | |
| for hmap, hw in heatmaps: | |
| resized = cv2.resize(hmap.astype(np.float32), (ref_w, ref_h)) | |
| merged += resized * hw | |
| total_w += hw | |
| if total_w > 0: | |
| merged /= total_w | |
| merged = np.clip(np.nan_to_num(merged, nan=0.0), 0, 1) | |
| else: | |
| merged = np.zeros((256, 256), dtype=np.float32) | |
| return Verdict( | |
| is_tampered=is_flagged, | |
| confidence=fused_score, | |
| fused_heatmap=merged, | |
| evidence=build_evidence(detections), | |
| per_detector=detections, | |
| label=label, | |
| ) | |