leonett commited on
Commit
97b8bd6
·
verified ·
1 Parent(s): 4bd2bda

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -57
app.py CHANGED
@@ -138,17 +138,17 @@ def realizar_ela(imagen):
138
  def crear_graficos_estadisticas(porcentaje_ela, dimensiones, metadatos):
139
  """Crear gráficos estadísticos para el análisis ELA"""
140
 
141
- # Gráfico 1: Porcentaje de manipulación (Barra) - VERDE CLARO
142
  fig_bar = go.Figure()
143
  fig_bar.add_trace(go.Bar(
144
  x=['Áreas Detectadas'],
145
  y=[porcentaje_ela],
146
- marker_color=['#4CAF50'], # Verde claro
147
  text=[f'{porcentaje_ela:.3f}%'],
148
  textposition='auto',
149
  ))
150
  fig_bar.update_layout(
151
- title=dict(text="Porcentaje de Áreas Manipuladas", x=0.5),
152
  yaxis_title="Porcentaje (%)",
153
  height=250,
154
  showlegend=False,
@@ -178,7 +178,7 @@ def crear_graficos_estadisticas(porcentaje_ela, dimensiones, metadatos):
178
  textinfo='label'
179
  ))
180
  fig_pie.update_layout(
181
- title=dict(text="Nivel de Probabilidad", x=0.5),
182
  height=250,
183
  showlegend=False,
184
  margin=dict(l=20, r=20, t=40, b=20)
@@ -189,11 +189,11 @@ def crear_graficos_estadisticas(porcentaje_ela, dimensiones, metadatos):
189
  fig_box.add_trace(go.Box(
190
  y=[porcentaje_ela],
191
  name="Distribución ELA",
192
- marker_color='#4CAF50', # Verde claro
193
  boxpoints='all'
194
  ))
195
  fig_box.update_layout(
196
- title=dict(text="Distribución de Anomalías", x=0.5),
197
  yaxis_title="Porcentaje (%)",
198
  height=250,
199
  showlegend=False,
@@ -206,11 +206,11 @@ def crear_graficos_estadisticas(porcentaje_ela, dimensiones, metadatos):
206
  x=['Compresión', 'Ruido', 'Manipulación'],
207
  y=[max(0.1, porcentaje_ela-1), max(0.1, porcentaje_ela-0.5), max(0.1, porcentaje_ela)],
208
  mode='lines+markers',
209
- line=dict(color='#4CAF50', width=3), # Verde claro
210
  marker=dict(size=10)
211
  ))
212
  fig_scatter.update_layout(
213
- title=dict(text="Análisis de Componentes", x=0.5),
214
  yaxis_title="Intensidad",
215
  height=250,
216
  margin=dict(l=20, r=20, t=40, b=20)
@@ -220,10 +220,10 @@ def crear_graficos_estadisticas(porcentaje_ela, dimensiones, metadatos):
220
 
221
  def procesar_imagen(archivo_imagen):
222
  if not archivo_imagen:
223
- return [None, None, "", None, None, None, None]
224
 
225
  if not os.path.exists(archivo_imagen):
226
- return [None, None, "", None, None, None, None]
227
 
228
  try:
229
  img = Image.open(archivo_imagen)
@@ -259,60 +259,59 @@ def procesar_imagen(archivo_imagen):
259
  else:
260
  file_size = f"{file_size_bytes / (1024 * 1024):.2f} MB"
261
 
262
- # TEXTO SIN NEGRITAS (eliminados los **)
263
- info_basica = f"Nombre del archivo: {nombre_original}\n"
264
- info_basica += f"Tamaño del archivo: {file_size}\n"
265
- info_basica += f"Dimensiones: {img.size[0]} x {img.size[1]} píxeles\n"
266
- info_basica += f"Formato: {img.format}\n"
267
- info_basica += f"Modo: {img.mode}\n\n"
268
 
269
  metadatos = obtener_metadatos(img)
270
- info_metadatos = "ANÁLISIS FORENSE DE LOS METADATOS:\n\n"
271
 
272
  google_maps_url = None
273
 
274
  if metadatos:
275
  for tag, value in metadatos.items():
276
  if tag == "DateTime":
277
- info_metadatos += f"- Fecha y hora de captura: {value}\n"
278
  elif tag == "Make":
279
- info_metadatos += f"- Fabricante de cámara: {value}\n"
280
  elif tag == "Model":
281
- info_metadatos += f"- Modelo de cámara: {value}\n"
282
  elif tag == "Software":
283
- info_metadatos += f"- Software de edición: {value}\n"
284
  elif tag == "GPSInfo":
285
  coords = obtener_coordenadas(metadatos)
286
  if coords:
287
  lat, lon = coords
288
- info_metadatos += f"- Coordenadas GPS: {lat:.6f}, {lon:.6f}\n"
289
  google_maps_url = f"https://www.google.com/maps?q={lat},{lon}"
290
- info_metadatos += f"- Enlace a Google Maps: {google_maps_url}\n"
291
  else:
292
- info_metadatos += "- No se encontraron coordenadas GPS válidas\n"
293
  else:
294
- info_metadatos += f"- {tag}: {value}\n"
295
  else:
296
- info_metadatos += "- No se encontraron metadatos EXIF\n"
297
 
298
  sha3_hash = calcular_hash(img)
299
- info_metadatos += f"\nSHA3-256: {sha3_hash}\n\n"
300
 
301
  manipulada, razones = analizar_manipulacion(img, metadatos)
302
- info_manipulacion = "ANÁLISIS DE MANIPULACIÓN:\n\n"
303
- info_manipulacion += f"- Porcentaje de áreas detectadas: {porcentaje_ela:.3f}%\n"
304
- info_manipulacion += f"- Estimación forense: {probabilidad}\n\n"
305
 
306
  if manipulada:
307
- info_manipulacion += "⚠️ LA IMAGEN HA SIDO MANIPULADA.\nRazones:\n"
308
  for r in razones:
309
- info_manipulacion += f"- {r}\n"
310
  else:
311
- info_manipulacion += "✅ LA IMAGEN NO HA SIDO MANIPULADA.\n"
312
 
313
  analysis_text = info_basica + info_metadatos + info_manipulacion
314
 
315
- with open(text_path, "w", encoding="utf-8", newline="\n") as f:
316
  f.write(analysis_text)
317
 
318
  with zipfile.ZipFile(zip_path, "w") as zipf:
@@ -324,14 +323,14 @@ def procesar_imagen(archivo_imagen):
324
 
325
  ela_rgb = cv2.cvtColor(ela_result, cv2.COLOR_BGR2RGB)
326
 
327
- return [zip_path, ela_rgb, google_maps_url or "", fig_bar, fig_pie, fig_box, fig_scatter]
328
 
329
  except Exception as e:
330
  logger.error(f"Error en procesamiento: {str(e)}")
331
- mensaje_error = f"❌ ERROR CRÍTICO: {str(e)}"
332
  error_img = np.zeros((300, 600, 3), dtype=np.uint8)
333
  cv2.putText(error_img, "ERROR INTERNO", (80, 160), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
334
- return [None, error_img, "", None, None, None, None]
335
 
336
  # 🎨 Tema moderno
337
  theme = gr.themes.Soft(primary_hue="blue", secondary_hue="slate", neutral_hue="stone")
@@ -352,30 +351,26 @@ with gr.Blocks(title="Análisis Forense de Imágenes con ELA", theme=theme, css=
352
  .equal-height {
353
  height: 400px !important;
354
  }
355
- .stats-title {
356
- font-weight: bold !important;
357
- font-size: 14px !important;
358
- }
359
  """) as demo:
360
  gr.Markdown("""
361
  # 📸 Análisis Forense de Imágenes con Error Level Analysis (ELA)
362
- Programa de computación forense para analizar imágenes en busca de evidencia de manipulación o edición.
363
  """)
364
- gr.Markdown("Desarrollado por José R. Leonett para el Grupo de Peritos Forenses Digitales de Guatemala - [www.forensedigital.gt](https://www.forensedigital.gt)")
365
 
366
  with gr.Row():
367
  with gr.Column():
368
  input_image = gr.Image(
369
- label="Subir imagen (JPG/PNG)",
370
  type="filepath",
371
  height=400,
372
  sources=["upload"],
373
  elem_classes=["equal-height"]
374
  )
375
- process_btn = gr.Button("Analizar imagen", variant="primary")
376
 
377
  download_zip = gr.DownloadButton(
378
- label="⬇️ Descargar resultados (ZIP)",
379
  variant="secondary",
380
  visible=False,
381
  interactive=True,
@@ -385,28 +380,30 @@ with gr.Blocks(title="Análisis Forense de Imágenes con ELA", theme=theme, css=
385
  # Estadísticas en 2x2 grid
386
  with gr.Row(visible=False) as stats_row:
387
  with gr.Column():
388
- stats_bar = gr.Plot(label="Porcentaje de Manipulación", show_label=True)
389
- stats_pie = gr.Plot(label="Nivel de Probabilidad", show_label=True)
390
  with gr.Column():
391
- stats_box = gr.Plot(label="Distribución de Anomalías", show_label=True)
392
- stats_scatter = gr.Plot(label="Análisis de Componentes", show_label=True)
393
 
394
  with gr.Column():
395
- with gr.Accordion("Resultado del Análisis ELA", open=True):
396
  ela_image = gr.Image(
397
- label="🔍 ÁREAS AMARILLAS = posibles manipulaciones o compresiones",
398
  type="numpy",
399
  height=400,
400
  show_label=True,
401
  elem_classes=["equal-height"]
402
  )
403
 
404
- # ELIMINADO EL CUADRO DE TEXTO DE RESULTADOS
405
- google_maps_btn = gr.Button("📍 Ver ubicación en Google Maps", visible=False)
406
- google_maps_url_state = gr.State("")
 
407
 
408
  def reset_on_upload():
409
  return (
 
410
  gr.update(value=None),
411
  gr.update(visible=False),
412
  gr.update(visible=False),
@@ -418,7 +415,7 @@ with gr.Blocks(title="Análisis Forense de Imágenes con ELA", theme=theme, css=
418
  process_btn.click(
419
  fn=procesar_imagen,
420
  inputs=input_image,
421
- outputs=[download_zip, ela_image, google_maps_url_state, stats_bar, stats_pie, stats_box, stats_scatter],
422
  api_name="analyze_image"
423
  ).then(
424
  fn=lambda url: gr.update(visible=bool(url)),
@@ -444,7 +441,7 @@ with gr.Blocks(title="Análisis Forense de Imágenes con ELA", theme=theme, css=
444
  input_image.upload(
445
  fn=reset_on_upload,
446
  inputs=None,
447
- outputs=[ela_image, download_zip, google_maps_btn, google_maps_url_state, stats_row, stats_bar, stats_pie, stats_box, stats_scatter]
448
  )
449
 
450
  # ▶️ Ejecución
 
138
  def crear_graficos_estadisticas(porcentaje_ela, dimensiones, metadatos):
139
  """Crear gráficos estadísticos para el análisis ELA"""
140
 
141
+ # Gráfico 1: Porcentaje de manipulación (Barra)
142
  fig_bar = go.Figure()
143
  fig_bar.add_trace(go.Bar(
144
  x=['Áreas Detectadas'],
145
  y=[porcentaje_ela],
146
+ marker_color=['#FF6B35' if porcentaje_ela > 2 else '#36A2EB'],
147
  text=[f'{porcentaje_ela:.3f}%'],
148
  textposition='auto',
149
  ))
150
  fig_bar.update_layout(
151
+ title=dict(text="<b>Porcentaje de Áreas Manipuladas</b>", x=0.5),
152
  yaxis_title="Porcentaje (%)",
153
  height=250,
154
  showlegend=False,
 
178
  textinfo='label'
179
  ))
180
  fig_pie.update_layout(
181
+ title=dict(text="<b>Nivel de Probabilidad</b>", x=0.5),
182
  height=250,
183
  showlegend=False,
184
  margin=dict(l=20, r=20, t=40, b=20)
 
189
  fig_box.add_trace(go.Box(
190
  y=[porcentaje_ela],
191
  name="Distribución ELA",
192
+ marker_color='#FF6B35',
193
  boxpoints='all'
194
  ))
195
  fig_box.update_layout(
196
+ title=dict(text="<b>Distribución de Anomalías</b>", x=0.5),
197
  yaxis_title="Porcentaje (%)",
198
  height=250,
199
  showlegend=False,
 
206
  x=['Compresión', 'Ruido', 'Manipulación'],
207
  y=[max(0.1, porcentaje_ela-1), max(0.1, porcentaje_ela-0.5), max(0.1, porcentaje_ela)],
208
  mode='lines+markers',
209
+ line=dict(color='#FF6B35', width=3),
210
  marker=dict(size=10)
211
  ))
212
  fig_scatter.update_layout(
213
+ title=dict(text="<b>Análisis de Componentes</b>", x=0.5),
214
  yaxis_title="Intensidad",
215
  height=250,
216
  margin=dict(l=20, r=20, t=40, b=20)
 
220
 
221
  def procesar_imagen(archivo_imagen):
222
  if not archivo_imagen:
223
+ return [None, "❌ **ERROR: Por favor, cargue una imagen antes de analizar.**", None, ""] + [None] * 4
224
 
225
  if not os.path.exists(archivo_imagen):
226
+ return [None, "❌ **ERROR: El archivo de imagen no existe o es inválido.**", None, ""] + [None] * 4
227
 
228
  try:
229
  img = Image.open(archivo_imagen)
 
259
  else:
260
  file_size = f"{file_size_bytes / (1024 * 1024):.2f} MB"
261
 
262
+ info_basica = f"**Nombre del archivo:** {nombre_original}\r\n"
263
+ info_basica += f"**Tamaño del archivo:** {file_size}\r\n"
264
+ info_basica += f"**Dimensiones:** {img.size[0]} x {img.size[1]} píxeles\r\n"
265
+ info_basica += f"**Formato:** {img.format}\r\n"
266
+ info_basica += f"**Modo:** {img.mode}\r\n\r\n"
 
267
 
268
  metadatos = obtener_metadatos(img)
269
+ info_metadatos = "**ANÁLISIS FORENSE DE LOS METADATOS:**\r\n\r\n"
270
 
271
  google_maps_url = None
272
 
273
  if metadatos:
274
  for tag, value in metadatos.items():
275
  if tag == "DateTime":
276
+ info_metadatos += f"- **Fecha y hora de captura:** {value}\r\n"
277
  elif tag == "Make":
278
+ info_metadatos += f"- **Fabricante de cámara:** {value}\r\n"
279
  elif tag == "Model":
280
+ info_metadatos += f"- **Modelo de cámara:** {value}\r\n"
281
  elif tag == "Software":
282
+ info_metadatos += f"- **Software de edición:** {value}\r\n"
283
  elif tag == "GPSInfo":
284
  coords = obtener_coordenadas(metadatos)
285
  if coords:
286
  lat, lon = coords
287
+ info_metadatos += f"- **Coordenadas GPS:** {lat:.6f}, {lon:.6f}\r\n"
288
  google_maps_url = f"https://www.google.com/maps?q={lat},{lon}"
289
+ info_metadatos += f"- **Enlace a Google Maps:** {google_maps_url}\r\n"
290
  else:
291
+ info_metadatos += "- **Coordenadas GPS:** No se encontraron coordenadas válidas\r\n"
292
  else:
293
+ info_metadatos += f"- **{tag}:** {value}\r\n"
294
  else:
295
+ info_metadatos += "- **Metadatos EXIF:** No se encontraron metadatos EXIF\r\n"
296
 
297
  sha3_hash = calcular_hash(img)
298
+ info_metadatos += f"\r\n**SHA3-256:** {sha3_hash}\r\n\r\n"
299
 
300
  manipulada, razones = analizar_manipulacion(img, metadatos)
301
+ info_manipulacion = "**ANÁLISIS DE MANIPULACIÓN:**\r\n\r\n"
302
+ info_manipulacion += f"- **Porcentaje de áreas detectadas:** {porcentaje_ela:.3f}%\r\n"
303
+ info_manipulacion += f"- **Estimación forense:** {probabilidad}\r\n\r\n"
304
 
305
  if manipulada:
306
+ info_manipulacion += "⚠️ **LA IMAGEN HA SIDO MANIPULADA.**\r\n**Razones:**\r\n"
307
  for r in razones:
308
+ info_manipulacion += f"- {r}\r\n"
309
  else:
310
+ info_manipulacion += "✅ **LA IMAGEN NO HA SIDO MANIPULADA.**\r\n"
311
 
312
  analysis_text = info_basica + info_metadatos + info_manipulacion
313
 
314
+ with open(text_path, "w", encoding="utf-8", newline="\r\n") as f:
315
  f.write(analysis_text)
316
 
317
  with zipfile.ZipFile(zip_path, "w") as zipf:
 
323
 
324
  ela_rgb = cv2.cvtColor(ela_result, cv2.COLOR_BGR2RGB)
325
 
326
+ return [zip_path, analysis_text, ela_rgb, google_maps_url or "", fig_bar, fig_pie, fig_box, fig_scatter]
327
 
328
  except Exception as e:
329
  logger.error(f"Error en procesamiento: {str(e)}")
330
+ mensaje_error = f"❌ **ERROR CRÍTICO:** {str(e)}"
331
  error_img = np.zeros((300, 600, 3), dtype=np.uint8)
332
  cv2.putText(error_img, "ERROR INTERNO", (80, 160), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
333
+ return [None, mensaje_error, error_img, ""] + [None] * 4
334
 
335
  # 🎨 Tema moderno
336
  theme = gr.themes.Soft(primary_hue="blue", secondary_hue="slate", neutral_hue="stone")
 
351
  .equal-height {
352
  height: 400px !important;
353
  }
 
 
 
 
354
  """) as demo:
355
  gr.Markdown("""
356
  # 📸 Análisis Forense de Imágenes con Error Level Analysis (ELA)
357
+ **Programa de computación forense para analizar imágenes en busca de evidencia de manipulación o edición.**
358
  """)
359
+ gr.Markdown("**Desarrollado por José R. Leonett para el Grupo de Peritos Forenses Digitales de Guatemala - [www.forensedigital.gt](https://www.forensedigital.gt)**")
360
 
361
  with gr.Row():
362
  with gr.Column():
363
  input_image = gr.Image(
364
+ label="**Subir imagen (JPG/PNG)**",
365
  type="filepath",
366
  height=400,
367
  sources=["upload"],
368
  elem_classes=["equal-height"]
369
  )
370
+ process_btn = gr.Button("**Analizar imagen**", variant="primary")
371
 
372
  download_zip = gr.DownloadButton(
373
+ label="⬇️ **Descargar resultados (ZIP)**",
374
  variant="secondary",
375
  visible=False,
376
  interactive=True,
 
380
  # Estadísticas en 2x2 grid
381
  with gr.Row(visible=False) as stats_row:
382
  with gr.Column():
383
+ stats_bar = gr.Plot(label="**Porcentaje de Manipulación**", show_label=True)
384
+ stats_pie = gr.Plot(label="**Nivel de Probabilidad**", show_label=True)
385
  with gr.Column():
386
+ stats_box = gr.Plot(label="**Distribución de Anomalías**", show_label=True)
387
+ stats_scatter = gr.Plot(label="**Análisis de Componentes**", show_label=True)
388
 
389
  with gr.Column():
390
+ with gr.Accordion("**Resultado del Análisis ELA**", open=True):
391
  ela_image = gr.Image(
392
+ label="**🔍 ÁREAS AMARILLAS = posibles manipulaciones o compresiones**",
393
  type="numpy",
394
  height=400,
395
  show_label=True,
396
  elem_classes=["equal-height"]
397
  )
398
 
399
+ with gr.Accordion("**Informe Detallado**", open=True):
400
+ analysis_text = gr.Textbox(label="**📝 Resultados del análisis forense**", lines=15, max_lines=25)
401
+ google_maps_btn = gr.Button("📍 **Ver ubicación en Google Maps**", visible=False)
402
+ google_maps_url_state = gr.State("")
403
 
404
  def reset_on_upload():
405
  return (
406
+ gr.update(value=None),
407
  gr.update(value=None),
408
  gr.update(visible=False),
409
  gr.update(visible=False),
 
415
  process_btn.click(
416
  fn=procesar_imagen,
417
  inputs=input_image,
418
+ outputs=[download_zip, analysis_text, ela_image, google_maps_url_state, stats_bar, stats_pie, stats_box, stats_scatter],
419
  api_name="analyze_image"
420
  ).then(
421
  fn=lambda url: gr.update(visible=bool(url)),
 
441
  input_image.upload(
442
  fn=reset_on_upload,
443
  inputs=None,
444
+ outputs=[analysis_text, ela_image, download_zip, google_maps_btn, google_maps_url_state, stats_row, stats_bar, stats_pie, stats_box, stats_scatter]
445
  )
446
 
447
  # ▶️ Ejecución