NOBODY204 commited on
Commit
3e69e59
ยท
verified ยท
1 Parent(s): de39d3e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +389 -91
app.py CHANGED
@@ -2,109 +2,407 @@ import gradio as gr
2
  import numpy as np
3
  import matplotlib.pyplot as plt
4
  import cv2
5
- from PIL import Image
6
- from skimage import feature # Ajout pour l'analyse de texture profonde
7
-
8
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
9
- # ๐Ÿ”ฌ ANALYSE THERMIQUE ET TEXTURE (LBP)
10
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
11
- def advanced_forensic_analysis(img):
12
- # 1. Analyse Infrarouge (YCrCb)
 
 
 
13
  ycrcb = cv2.cvtColor(img, cv2.COLOR_RGB2YCrCb)
14
- thermal = ycrcb[:, :, 1]
15
- infrared_map = cv2.applyColorMap(thermal, cv2.COLORMAP_JET)
16
 
17
- # 2. Analyse LBP (Local Binary Pattern) - Dรฉtecte les faux pixels
18
- gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
19
- lbp = feature.local_binary_pattern(gray, P=8, R=1, method="uniform")
20
- # Si l'image est gรฉnรฉrรฉe, le LBP est souvent "trop parfait"
21
- lbp_hist, _ = np.histogram(lbp.ravel(), bins=np.arange(0, 12), density=True)
22
- lbp_consistency = np.max(lbp_hist)
 
 
 
23
 
24
- return infrared_map, np.std(thermal), lbp_consistency
 
 
 
 
 
 
 
25
 
26
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
27
- # ๐Ÿ“Š ANALYSE FRร‰QUENTIELLE (FFT)
28
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
29
- def frequency_analysis(img):
30
- gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  f = np.fft.fft2(gray)
32
  fshift = np.fft.fftshift(f)
33
- magnitude = 20 * np.log(np.abs(fshift) + 1)
34
- # Les IA laissent des traces dans les hautes frรฉquences (les coins du spectre)
35
- h, w = magnitude.shape
36
- center_void = np.mean(magnitude[0:10, 0:10]) # Analyse des coins
37
- return np.mean(magnitude), center_void
38
-
39
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
40
- # ๐Ÿง  SCORE GLOBAL "ANTI-TRICHE"
41
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  def analyze_image(img_input):
43
- if img_input is None: return None, "Erreur"
44
-
45
- # Exรฉcution des analyses
46
- ir_map, thermal_std, lbp_score = advanced_forensic_analysis(img_input)
47
- freq_mean, corner_freq = frequency_analysis(img_input)
48
-
49
- # CALCUL DU SCORE DURCI
50
- # On pรฉnalise si l'image est "trop lisse" (LBP รฉlevรฉ) ou "thermique plate"
51
- ai_score = 0
52
- if lbp_score > 0.25: ai_score += 35 # Texture trop rรฉpรฉtitive (IA)
53
- if thermal_std < 6: ai_score += 25 # Chaleur trop uniforme
54
- if corner_freq > 15: ai_score += 20 # Artefacts de grille IA
55
- if freq_mean < 55: ai_score += 20 # Manque de dรฉtails naturels
56
-
57
- ai_score = min(ai_score, 100)
58
-
59
- # Verdict
60
- if ai_score > 65:
61
- status = "๐Ÿšจ DEEPFAKE Dร‰TECTร‰ (High Probability)"
62
- color = "red"
63
- elif ai_score > 40:
64
- status = "โš ๏ธ IMAGE SUSPECTE (Manipulation probable)"
65
- color = "orange"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  else:
67
- status = "โœ… IMAGE AUTHENTIQUE"
68
- color = "green"
69
-
70
- # Rapport technique pour Mauro Barni
71
- report = f"""๐Ÿ”ฌ MEDIASHIELD PRO - FORENSIC REPORT v3
72
- --------------------------------------
73
- VERDICT FINAL : {status}
74
- INDICE DE FAUX : {ai_score:.2f}%
75
-
76
- Dร‰TAILS TECHNIQUES :
77
- - Cohรฉrence LBP (Texture) : {lbp_score:.4f} {"(Anomalie IA)" if lbp_score > 0.25 else "(OK)"}
78
- - Dรฉviation Thermique : {thermal_std:.2f} {"(Trop stable/IA)" if thermal_std < 6 else "(OK)"}
79
- - Frรฉquences de grille : {corner_freq:.2f} {"(Signal synthรฉtique)" if corner_freq > 15 else "(OK)"}
80
-
81
- CONCLUSION :
82
- L'analyse des micro-textures (LBP) montre des motifs de rรฉpรฉtition non-organiques.
83
- La signature thermique confirme l'absence de bruit de capteur physique.
84
- --------------------------------------
85
- Trusted Sound 2026 - ACoNum Tunisia"""
86
-
87
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 7))
88
- ax1.imshow(img_input)
89
- ax1.set_title("Image Source")
90
- ax1.axis('off')
91
- ax2.imshow(ir_map)
92
- ax2.set_title("Analyse Infrarouge & Texture")
93
- ax2.axis('off')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
  return fig, report
96
 
97
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
98
- # INTERFACE
99
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
100
- with gr.Blocks(theme=gr.themes.Monochrome()) as demo:
101
- gr.Markdown("# ๐Ÿ›ก๏ธ ImageShield PRO - Forensic Suite")
 
 
 
 
 
 
 
 
 
102
  with gr.Row():
103
- in_img = gr.Image(label="Upload image")
104
- out_plot = gr.Plot()
105
- out_txt = gr.Textbox(label="Expert Report", lines=12)
106
- btn = gr.Button("๐Ÿ” ANALYSER MAINTENANT", variant="primary")
107
- btn.click(analyze_image, in_img, [out_plot, out_txt])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
  if __name__ == "__main__":
110
  demo.launch()
 
2
  import numpy as np
3
  import matplotlib.pyplot as plt
4
  import cv2
5
+ from scipy import ndimage
6
+
7
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
8
+ # ๐Ÿ”ฌ MEDIASHIELD PRO v2.0 โ€“ CORRECTION DES Dร‰TECTIONS
9
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
10
+
11
+ def analyze_chrominance_noise(img):
12
+ """
13
+ Dรฉtecte l'uniformitรฉ anormale du bruit chromatique.
14
+ Les IA gรฉnรจrent souvent du bruit trop rรฉgulier.
15
+ """
16
  ycrcb = cv2.cvtColor(img, cv2.COLOR_RGB2YCrCb)
17
+ cr = ycrcb[:, :, 1].astype(np.float32)
18
+ cb = ycrcb[:, :, 2].astype(np.float32)
19
 
20
+ # Variance locale sur des fenรชtres 5x5
21
+ cr_var = ndimage.generic_filter(cr, np.var, size=5)
22
+ cb_var = ndimage.generic_filter(cb, np.var, size=5)
23
+
24
+ # Ratio: รฉcart-type des variances / moyenne des variances
25
+ uniformity_cr = np.std(cr_var) / (np.mean(cr_var) + 1e-8)
26
+ uniformity_cb = np.std(cb_var) / (np.mean(cb_var) + 1e-8)
27
+
28
+ return (uniformity_cr + uniformity_cb) / 2, cr_var
29
 
30
+ def find_peaks(signal, threshold_factor=0.3):
31
+ """Trouve les pics dans un signal 1D (pour dรฉtecter les grilles)"""
32
+ threshold = np.max(signal) * threshold_factor
33
+ peaks = []
34
+ for i in range(1, len(signal)-1):
35
+ if signal[i] > threshold and signal[i] > signal[i-1] and signal[i] > signal[i+1]:
36
+ peaks.append(i)
37
+ return peaks
38
 
39
+ def compute_radial_profile(image):
40
+ """Calcule la moyenne radiale du spectre FFT"""
41
+ cy, cx = np.array(image.shape) // 2
42
+ y, x = np.indices(image.shape)
43
+ r = np.sqrt((x - cx)**2 + (y - cy)**2).astype(int)
44
+ tbin = ndimage.mean(image, labels=r, index=np.arange(0, min(cx, cy)))
45
+ return tbin[~np.isnan(tbin)]
46
+
47
+ def detect_ringing(radial_profile):
48
+ """
49
+ Dรฉtecte les oscillations (ringing) dans le profil radial.
50
+ Caractรฉristique des modรจles de diffusion (SD, Midjourney).
51
+ """
52
+ if len(radial_profile) < 10:
53
+ return 0
54
+ diff = np.diff(radial_profile)
55
+ sign_changes = np.sum(diff[1:] * diff[:-1] < 0)
56
+ return sign_changes / len(radial_profile)
57
+
58
+ def detect_grid_and_ringing(img):
59
+ """
60
+ Dรฉtecte:
61
+ 1. Les patterns de grille (StyleGAN, GANs)
62
+ 2. Les artefacts circulaires (Diffusion models)
63
+ """
64
+ gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32)
65
+
66
+ # FFT
67
  f = np.fft.fft2(gray)
68
  fshift = np.fft.fftshift(f)
69
+ magnitude = np.abs(fshift)
70
+
71
+ # Masquer le centre (basse frรฉquence)
72
+ rows, cols = gray.shape
73
+ crow, ccol = rows//2, cols//2
74
+ mask_radius = min(rows, cols) // 6
75
+ y, x = np.ogrid[:rows, :cols]
76
+ mask = (x - ccol)**2 + (y - crow)**2 <= mask_radius**2
77
+ magnitude[mask] = 0
78
+
79
+ # Dรฉtection de grille via projection
80
+ proj_x = np.sum(magnitude, axis=0)
81
+ proj_y = np.sum(magnitude, axis=1)
82
+ peaks_x = len(find_peaks(proj_x, 0.2))
83
+ peaks_y = len(find_peaks(proj_y, 0.2))
84
+ grid_score = (peaks_x + peaks_y) / 2
85
+
86
+ # Dรฉtection du ringing
87
+ radial = compute_radial_profile(magnitude)
88
+ ringing = detect_ringing(radial)
89
+
90
+ # Visualisation
91
+ fft_vis = np.log(magnitude + 1)
92
+ fft_vis = (fft_vis - fft_vis.min()) / (fft_vis.max() - fft_vis.min()) * 255
93
+ fft_vis = cv2.applyColorMap(fft_vis.astype(np.uint8), cv2.COLORMAP_VIRIDIS)
94
+
95
+ return grid_score, ringing, fft_vis
96
+
97
+ def detect_noise_characteristics(img):
98
+ """
99
+ Analyse le bruit local (les images IA ont des statistiques de bruit anormales)
100
+ """
101
+ gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
102
+
103
+ # Diffรฉrence entre image et version lissรฉe
104
+ blur = cv2.GaussianBlur(gray, (5, 5), 0)
105
+ noise = cv2.absdiff(gray, blur)
106
+
107
+ # Statistiques
108
+ noise_mean = np.mean(noise)
109
+ noise_std = np.std(noise)
110
+
111
+ # Analyse des gradients (Sobel)
112
+ sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
113
+ sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
114
+ grad_mag = np.sqrt(sobelx**2 + sobely**2)
115
+
116
+ # Distribution des gradients
117
+ hist = np.histogram(grad_mag, bins=50, range=(0, 255))[0]
118
+ grad_uniformity = np.std(hist) / (np.mean(hist) + 1e-8)
119
+
120
+ # Hautes frรฉquences (Laplacian)
121
+ laplacian = cv2.Laplacian(gray, cv2.CV_64F).var()
122
+
123
+ return {
124
+ 'noise_mean': noise_mean,
125
+ 'noise_std': noise_std,
126
+ 'grad_uniformity': grad_uniformity,
127
+ 'laplacian': laplacian
128
+ }
129
+
130
+ def error_level_analysis(img, quality=85):
131
+ """
132
+ ELA: Recompresser l'image et comparer.
133
+ Les zones gรฉnรฉrรฉes par IA ont souvent des niveaux d'erreur anormaux.
134
+ """
135
+ encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
136
+ _, enc = cv2.imencode('.jpg', cv2.cvtColor(img, cv2.COLOR_RGB2BGR), encode_param)
137
+ dec = cv2.imdecode(enc, 1)
138
+ dec_rgb = cv2.cvtColor(dec, cv2.COLOR_BGR2RGB)
139
+
140
+ diff = cv2.absdiff(img, dec_rgb)
141
+ ela_score = np.mean(diff)
142
+
143
+ # Amรฉliorer la visualisation
144
+ ela_enhanced = cv2.convertScaleAbs(diff, alpha=5.0)
145
+
146
+ return ela_score, ela_enhanced
147
+
148
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
149
+ # ๐Ÿง  ALGORITHME DE SCORING CORRIGร‰
150
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
151
+
152
+ def compute_ai_score(metrics):
153
+ """
154
+ Systรจme de scoring pondรฉrรฉ avec logique floue.
155
+ Seuils calibrรฉs pour minimiser les faux positifs.
156
+ """
157
+ score = 0
158
+ reasons = []
159
+ confidence = 0
160
+
161
+ # 1. GRID PATTERNS (GANs like StyleGAN) - Poids fort
162
+ if metrics['grid_score'] > 15:
163
+ score += 30
164
+ reasons.append("โš ๏ธ Strong grid patterns (GAN signature)")
165
+ confidence += 1
166
+ elif metrics['grid_score'] > 8:
167
+ score += 15
168
+ reasons.append("โšก Weak grid patterns detected")
169
+
170
+ # 2. RINGING (Diffusion Models) - Poids fort
171
+ if metrics['ringing'] > 0.25:
172
+ score += 25
173
+ reasons.append("โš ๏ธ Circular artifacts (Stable Diffusion/Midjourney)")
174
+ confidence += 1
175
+ elif metrics['ringing'] > 0.15:
176
+ score += 12
177
+ reasons.append("โšก Possible diffusion artifacts")
178
+
179
+ # 3. CHROMINANCE (Natural vs AI noise)
180
+ if metrics['chrom_uniformity'] < 1.0: # Trop uniforme = suspect
181
+ score += 15
182
+ reasons.append("โšก Unnatural chrominance uniformity")
183
+ elif metrics['chrom_uniformity'] < 0.5:
184
+ score += 25
185
+ reasons.append("โš ๏ธ Artificial color noise detected")
186
+ confidence += 1
187
+
188
+ # 4. NOISE LEVEL
189
+ if metrics['noise_mean'] < 2.5: # Trop propre
190
+ score += 15
191
+ reasons.append("โšก Suspiciously low noise (over-smoothed)")
192
+ elif metrics['noise_mean'] > 40: # Bruit artificiel
193
+ score += 10
194
+ reasons.append("โšก Artificial noise pattern")
195
+
196
+ # 5. GRADIENT DISTRIBUTION
197
+ if metrics['grad_uniformity'] < 2.0:
198
+ score += 10
199
+ reasons.append("โšก Unnatural edge distribution")
200
+
201
+ # 6. LAPLACIAN (Texture)
202
+ if metrics['laplacian'] < 50:
203
+ score += 10
204
+ reasons.append("โšก Lack of fine texture details")
205
+ elif metrics['laplacian'] > 500000: # Images trรจs dรฉtaillรฉes (possible upscaling IA)
206
+ score += 10
207
+ reasons.append("โšก Excessive sharpness (AI upscaling?)")
208
+
209
+ # 7. ELA
210
+ if metrics['ela_score'] < 1.5:
211
+ score += 15
212
+ reasons.append("โš ๏ธ Suspicious compression history")
213
+ confidence += 1
214
+
215
+ # Ajuster selon la confiance
216
+ if confidence >= 3:
217
+ score = min(score + 10, 100) # Bonus si plusieurs indicateurs forts
218
+
219
+ return min(score, 100), reasons
220
+
221
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
222
+ # ๐ŸŽฏ FONCTION PRINCIPALE CORRIGร‰E
223
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
224
+
225
  def analyze_image(img_input):
226
+ if img_input is None:
227
+ return None, "Erreur : aucune image fournie"
228
+
229
+ # Conversion RGBA โ†’ RGB si nรฉcessaire
230
+ if img_input.shape[2] == 4:
231
+ img = cv2.cvtColor(img_input, cv2.COLOR_RGBA2RGB)
232
+ else:
233
+ img = img_input.copy()
234
+
235
+ # Limiter la taille pour accรฉlรฉrer (mais garder la qualitรฉ)
236
+ h, w = img.shape[:2]
237
+ if max(h, w) > 1024:
238
+ scale = 1024 / max(h, w)
239
+ img = cv2.resize(img, (int(w*scale), int(h*scale)), interpolation=cv2.INTER_AREA)
240
+
241
+ # Exรฉcuter les analyses
242
+ chrom_uniformity, chrom_vis = analyze_chrominance_noise(img)
243
+ grid_score, ringing, fft_vis = detect_grid_and_ringing(img)
244
+ noise_data = detect_noise_characteristics(img)
245
+ ela_score, ela_vis = error_level_analysis(img)
246
+
247
+ metrics = {
248
+ 'chrom_uniformity': chrom_uniformity,
249
+ 'grid_score': grid_score,
250
+ 'ringing': ringing,
251
+ 'noise_mean': noise_data['noise_mean'],
252
+ 'noise_std': noise_data['noise_std'],
253
+ 'grad_uniformity': noise_data['grad_uniformity'],
254
+ 'laplacian': noise_data['laplacian'],
255
+ 'ela_score': ela_score
256
+ }
257
+
258
+ # Calcul du score
259
+ ai_score, reasons = compute_ai_score(metrics)
260
+
261
+ # Classification avec nuance
262
+ if ai_score >= 80:
263
+ status = "๐Ÿšจ AI GENERATED (High Confidence)"
264
+ color = "#ff4444"
265
+ elif ai_score >= 60:
266
+ status = "โš ๏ธ LIKELY AI / HEAVILY EDITED"
267
+ color = "#ff8844"
268
+ elif ai_score >= 40:
269
+ status = "๐Ÿ” SUSPICIOUS โ€“ Manual Review Recommended"
270
+ color = "#ffaa44"
271
+ elif ai_score >= 20:
272
+ status = "โœ“ PROBABLY REAL (Low Confidence)"
273
+ color = "#88cc44"
274
+ else:
275
+ status = "โœ… AUTHENTIC IMAGE"
276
+ color = "#44aa44"
277
+
278
+ # Dรฉtection du type
279
+ if grid_score > 10 and ringing > 0.2:
280
+ model_type = "StyleGAN or ProGAN (grid + ringing)"
281
+ elif ringing > 0.25:
282
+ model_type = "Stable Diffusion / Midjourney / DALL-E"
283
+ elif grid_score > 10:
284
+ model_type = "GAN-based (StyleGAN legacy)"
285
+ elif ai_score > 50:
286
+ model_type = "Unknown AI / Heavy processing"
287
  else:
288
+ model_type = "Natural/Camera capture"
289
+
290
+ # Visualisations
291
+ fig, axes = plt.subplots(2, 2, figsize=(14, 12))
292
+ fig.patch.set_facecolor('#f0f0f0')
293
+
294
+ # Original
295
+ axes[0,0].imshow(img)
296
+ axes[0,0].set_title('๐Ÿ“ธ Original Image', fontsize=12, fontweight='bold')
297
+ axes[0,0].axis('off')
298
+
299
+ # FFT
300
+ axes[0,1].imshow(fft_vis)
301
+ axes[0,1].set_title(f'๐Ÿ”ฎ Frequency Analysis\nGrid: {grid_score:.1f} | Ringing: {ringing:.2f}',
302
+ fontsize=11, fontweight='bold')
303
+ axes[0,1].axis('off')
304
+
305
+ # ELA
306
+ axes[1,0].imshow(ela_vis)
307
+ axes[1,0].set_title(f'๐Ÿ” Error Level Analysis\nScore: {ela_score:.2f}',
308
+ fontsize=11, fontweight='bold')
309
+ axes[1,0].axis('off')
310
+
311
+ # Chrominance
312
+ im = axes[1,1].imshow(chrom_vis, cmap='hot')
313
+ axes[1,1].set_title(f'๐ŸŒˆ Chrominance Noise\nUniformity: {chrom_uniformity:.2f}',
314
+ fontsize=11, fontweight='bold')
315
+ axes[1,1].axis('off')
316
+ plt.colorbar(im, ax=axes[1,1], fraction=0.046)
317
+
318
+ plt.tight_layout()
319
+
320
+ # Rapport dรฉtaillรฉ
321
+ report = f"""
322
+ โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
323
+ โ•‘ ๐Ÿ›ก๏ธ MEDIASHIELD PRO โ€“ FORENSIC ANALYSIS v2.0 โ•‘
324
+ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
325
+
326
+ ๐Ÿ“Š FINAL VERDICT: {status}
327
+ ๐ŸŽฏ AI PROBABILITY SCORE: {ai_score}/100
328
+
329
+ ๐Ÿ”ฌ DETAILED METRICS:
330
+ โ”œโ”€ Grid Pattern Score: {grid_score:>6.2f} {'โš ๏ธ' if grid_score > 8 else 'โœ“'}
331
+ โ”œโ”€ Circular Artifacts: {ringing:>6.3f} {'โš ๏ธ' if ringing > 0.15 else 'โœ“'}
332
+ โ”œโ”€ Chrominance Uniformity: {chrom_uniformity:>6.2f} {'โš ๏ธ' if chrom_uniformity < 1.0 else 'โœ“'}
333
+ โ”œโ”€ Noise Level: {noise_data['noise_mean']:>6.2f} ยฑ{noise_data['noise_std']:.2f}
334
+ โ”œโ”€ Gradient Uniformity: {noise_data['grad_uniformity']:>6.2f}
335
+ โ”œโ”€ Texture (Laplacian): {noise_data['laplacian']:>6.0f}
336
+ โ””โ”€ ELA Score: {ela_score:>6.2f} {'โš ๏ธ' if ela_score < 1.5 else 'โœ“'}
337
+
338
+ ๐Ÿ“ DETECTED INDICATORS:"""
339
+
340
+ if reasons:
341
+ for reason in reasons:
342
+ report += f"\n {reason}"
343
+ else:
344
+ report += "\n โœ… No suspicious patterns detected"
345
+
346
+ report += f"""
347
+
348
+ ๐Ÿค– ESTIMATED SOURCE: {model_type}
349
+
350
+ ๐Ÿ’ก INTERPRETATION:
351
+ """
352
+
353
+ if ai_score >= 80:
354
+ report += "This image exhibits multiple strong indicators of AI generation."
355
+ elif ai_score >= 60:
356
+ report += "This image likely contains AI-generated elements or heavy manipulation."
357
+ elif ai_score >= 40:
358
+ report += "Some anomalies detected. Recommend expert verification."
359
+ else:
360
+ report += "Image characteristics consistent with natural photography."
361
+
362
+ report += """
363
+
364
+ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
365
+ MediaShield Pro โ€“ Trusted Sound 2026 โ€“ Tunisia
366
+ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"""
367
 
368
  return fig, report
369
 
370
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
371
+ # ๐ŸŽจ INTERFACE GRADIO
372
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
373
+
374
+ css = """
375
+ .output_text { font-family: 'Courier New', monospace; white-space: pre-line; }
376
+ """
377
+
378
+ with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
379
+ gr.Markdown("""
380
+ # ๐Ÿ›ก๏ธ MediaShield PRO โ€“ AI Image Detection v2.0
381
+ ### Dรฉtection avancรฉe de Deepfakes, Stable Diffusion, Midjourney et GANs
382
+ """)
383
+
384
  with gr.Row():
385
+ with gr.Column(scale=1):
386
+ input_img = gr.Image(label="๐Ÿ“ค Upload Image", type="numpy")
387
+ analyze_btn = gr.Button("๐Ÿ” START FORENSIC ANALYSIS", variant="primary", size="lg")
388
+
389
+ gr.Markdown("""
390
+ **โ„น๏ธ Guide d'interprรฉtation:**
391
+ - ๐Ÿšจ **80-100**: Image clairement gรฉnรฉrรฉe par IA
392
+ - โš ๏ธ **60-79**: Probablement IA ou trรจs retouchรฉe
393
+ - ๐Ÿ” **40-59**: Suspecte โ€“ vรฉrification manuelle recommandรฉe
394
+ - โœ… **0-39**: Image probablement authentique
395
+ """)
396
+
397
+ with gr.Column(scale=2):
398
+ output_plot = gr.Plot(label="๐Ÿ”ฌ Forensic Visualizations")
399
+ output_text = gr.Textbox(label="๐Ÿ“‹ Analysis Report", lines=25, elem_classes=["output_text"])
400
+
401
+ analyze_btn.click(
402
+ fn=analyze_image,
403
+ inputs=input_img,
404
+ outputs=[output_plot, output_text]
405
+ )
406
 
407
  if __name__ == "__main__":
408
  demo.launch()