leonett commited on
Commit
6695c83
·
verified ·
1 Parent(s): b190ba5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +21 -47
app.py CHANGED
@@ -5,7 +5,6 @@ import numpy as np
5
  import os
6
  import zipfile
7
  import hashlib
8
- import matplotlib.pyplot as plt
9
  import logging
10
  from pathlib import Path
11
  from PIL import ExifTags
@@ -14,7 +13,7 @@ from PIL import ExifTags
14
  logging.basicConfig(level=logging.INFO)
15
  logger = logging.getLogger(__name__)
16
 
17
- # Usar el directorio de trabajo actual (seguro en Hugging Face Spaces)
18
  evidence_dir = "/home/user/app"
19
  os.makedirs(evidence_dir, exist_ok=True)
20
  logger.info(f"Directorio de evidencia creado en: {evidence_dir}")
@@ -29,7 +28,6 @@ def obtener_metadatos(imagen):
29
  metadata = {}
30
  for tag_id, value in exif_data.items():
31
  try:
32
- # Convertir etiquetas EXIF a nombres legibles
33
  tag = ExifTags.TAGS.get(tag_id, tag_id)
34
  metadata[tag] = value
35
  except Exception as e:
@@ -49,7 +47,6 @@ def obtener_coordenadas(exif_data):
49
  if not gps_info:
50
  return None
51
 
52
- # Convertir coordenadas a grados decimales
53
  def gps_to_degrees(coord):
54
  d, m, s = coord
55
  return d + (m / 60.0) + (s / 3600.0)
@@ -83,23 +80,18 @@ def analizar_manipulacion(imagen, metadatos):
83
  manipulada = False
84
  razones = []
85
 
86
- # Verificar marcas de agua o formato indexado
87
  if imagen.mode == "P":
88
  razones.append("La imagen tiene marcas de agua o es una imagen indexada.")
89
  manipulada = True
90
 
91
- # Verificar presencia de metadatos
92
  if not metadatos:
93
  razones.append("La imagen no tiene metadatos EXIF.")
94
  manipulada = True
95
  else:
96
- # Verificar software de edición
97
  if "Software" in metadatos:
98
  razones.append(f"La imagen fue editada con: {metadatos['Software']}")
99
  manipulada = True
100
 
101
- # Verificar hash (simulación)
102
- # Hash SHA3-256 de ejemplo (64 caracteres hexadecimal)
103
  hash_conocido = "d8e3d0e0d7a5e2b2c9d5f9d1c8e7a6f5b0d4e7c3f9d1a2b3c4d5e6f7a8b9c0d1"
104
  hash_actual = calcular_hash(imagen)
105
  if hash_actual != hash_conocido:
@@ -111,29 +103,20 @@ def analizar_manipulacion(imagen, metadatos):
111
  def realizar_ela(imagen, quality=95, scale=100):
112
  """Realiza el Error Level Analysis (ELA) en la imagen."""
113
  try:
114
- # Convertir a formato compatible con OpenCV
115
  img_np = np.array(imagen)
116
  img_cv = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
117
 
118
- # Guardar imagen comprimida
119
  temp_path = "/tmp/temp_image.jpg"
120
  cv2.imwrite(temp_path, img_cv, [cv2.IMWRITE_JPEG_QUALITY, quality])
121
 
122
- # Leer imagen comprimida
123
  img_comprimida = cv2.imread(temp_path)
124
  if img_comprimida is None:
125
  raise ValueError("Error al leer la imagen comprimida")
126
 
127
- # Calcular diferencia absoluta
128
  diferencia = cv2.absdiff(img_cv, img_comprimida)
129
-
130
- # Escalar para resaltar áreas modificadas
131
  ela_imagen = scale * diferencia
132
-
133
- # Convertir a escala de grises
134
  ela_imagen_gray = cv2.cvtColor(ela_imagen, cv2.COLOR_BGR2GRAY)
135
 
136
- # Limpiar archivo temporal
137
  os.remove(temp_path)
138
 
139
  return ela_imagen_gray
@@ -144,31 +127,21 @@ def realizar_ela(imagen, quality=95, scale=100):
144
  def procesar_imagen(archivo_imagen):
145
  """Procesa la imagen completa y genera resultados."""
146
  try:
147
- # Cargar imagen
148
  img = Image.open(archivo_imagen)
149
  logger.info(f"Imagen cargada: {archivo_imagen}")
150
 
151
- # Obtener nombre base sin extensión
152
  nombre = os.path.splitext(os.path.basename(archivo_imagen))[0]
153
 
154
- # Rutas de archivos en directorio de evidencia
155
  original_path = os.path.join(evidence_dir, f"{nombre}_original.jpg")
156
  ela_path = os.path.join(evidence_dir, f"{nombre}_ela.jpg")
157
  text_path = os.path.join(evidence_dir, f"{nombre}_analisis.txt")
158
  zip_path = os.path.join(evidence_dir, f"{nombre}_errorELA.zip")
159
 
160
- # Guardar resultados
161
  img.save(original_path)
162
  ela_imagen = realizar_ela(img)
163
-
164
- # Convertir ELA a imagen con colores para resaltar áreas modificadas
165
- # Usamos un mapa de color para diferenciar las zonas con error
166
  ela_color = cv2.applyColorMap(ela_imagen, cv2.COLORMAP_JET)
167
-
168
- # Guardar imagen con ELA en color
169
  cv2.imwrite(ela_path, ela_color)
170
 
171
- # Generar texto de análisis
172
  info_basica = f"Formato: {img.format}\nTamaño: {img.size} píxeles\nModo: {img.mode}\n"
173
 
174
  metadatos = obtener_metadatos(img)
@@ -198,11 +171,9 @@ def procesar_imagen(archivo_imagen):
198
  else:
199
  info_metadatos += "No se encontraron metadatos EXIF\n"
200
 
201
- # Añadir SHA3-256 al análisis
202
  sha3_hash = calcular_hash(img)
203
  info_metadatos += f"\nSHA3-256: {sha3_hash}\n"
204
 
205
- # Análisis de manipulación
206
  manipulada, razones = analizar_manipulacion(img, metadatos)
207
  info_manipulacion = "\nANÁLISIS DE MANIPULACIÓN:\n"
208
  if manipulada:
@@ -212,13 +183,11 @@ def procesar_imagen(archivo_imagen):
212
  else:
213
  info_manipulacion += "LA IMAGEN NO HA SIDO MANIPULADA.\n"
214
 
215
- # Generar texto completo
216
  analysis_text = f"{info_basica}\n{info_metadatos}\n{info_manipulacion}"
217
 
218
  with open(text_path, "w") as f:
219
  f.write(analysis_text)
220
 
221
- # Crear ZIP
222
  with zipfile.ZipFile(zip_path, "w") as zipf:
223
  zipf.write(original_path, os.path.basename(original_path))
224
  zipf.write(ela_path, os.path.basename(ela_path))
@@ -226,15 +195,26 @@ def procesar_imagen(archivo_imagen):
226
 
227
  logger.info(f"Análisis completado. Archivo ZIP: {zip_path}")
228
 
229
- # Devolver el archivo ZIP como resultado
230
  return zip_path, analysis_text
231
-
232
  except Exception as e:
233
  logger.error(f"Error en procesamiento: {str(e)}")
234
  return f"Error: {str(e)}", f"Error al procesar la imagen: {str(e)}"
235
 
236
- # Interfaz Gradio con tema oscuro
237
- with gr.Blocks(title="Análisis Forense de Imágenes con ELA", theme=gr.themes.Dark()) as demo:
 
 
 
 
 
 
 
 
 
 
 
238
  gr.Markdown("""
239
  # 📸 Análisis Forense de Imágenes con Error Level Analysis (ELA)
240
  Programa de computación forense para analizar imágenes en busca de evidencia de manipulación o edición.
@@ -242,44 +222,38 @@ with gr.Blocks(title="Análisis Forense de Imágenes con ELA", theme=gr.themes.D
242
 
243
  with gr.Row():
244
  with gr.Column():
245
- # Eliminar opción de webcam
246
  input_image = gr.Image(label="Subir imagen (JPG/PNG)", type="filepath", height=400)
247
  process_btn = gr.Button("Analizar imagen", variant="primary")
248
 
249
  with gr.Column():
250
- # Sección de resultados
251
  with gr.Accordion("Descargar resultados ZIP", open=True):
252
  download_zip = gr.File(label="Descargar archivo ZIP", interactive=False)
253
  metadata_text = gr.Textbox(label="Metadatos y SHA3-256", lines=10, max_lines=20)
254
 
255
- # Sección de análisis detallado
256
  with gr.Accordion("Análisis detallado", open=True):
257
  ela_image = gr.Image(label="Imagen con Error Level Analysis (ELA)", type="numpy", elem_id="ela_image")
258
  analysis_text = gr.Textbox(label="Análisis detallado", lines=15, max_lines=25)
259
 
260
- # Evento de procesamiento
261
  process_btn.click(
262
  fn=procesar_imagen,
263
  inputs=input_image,
264
- outputs=[download_zip, metadata_text, ela_image, analysis_text],
265
  api_name="analyze_image"
266
  )
267
 
268
- # Evento de actualización
269
  input_image.change(
270
- fn=lambda x: f"Análisis en progreso para: {os.path.basename(x)}",
271
  inputs=input_image,
272
  outputs=analysis_text
273
  )
274
 
275
- # Configuración de ejecución
276
  if __name__ == "__main__":
277
- # Configuración para Hugging Face Spaces
278
  demo.launch(
279
  server_name="0.0.0.0",
280
  server_port=7860,
281
  share=True,
282
  inbrowser=True,
283
  favicon_path="https://huggingface.co/datasets/huggingface/logo/resolve/main/hf-logo.png"
284
- )
285
-
 
5
  import os
6
  import zipfile
7
  import hashlib
 
8
  import logging
9
  from pathlib import Path
10
  from PIL import ExifTags
 
13
  logging.basicConfig(level=logging.INFO)
14
  logger = logging.getLogger(__name__)
15
 
16
+ # Directorio de evidencia (seguro en Hugging Face Spaces)
17
  evidence_dir = "/home/user/app"
18
  os.makedirs(evidence_dir, exist_ok=True)
19
  logger.info(f"Directorio de evidencia creado en: {evidence_dir}")
 
28
  metadata = {}
29
  for tag_id, value in exif_data.items():
30
  try:
 
31
  tag = ExifTags.TAGS.get(tag_id, tag_id)
32
  metadata[tag] = value
33
  except Exception as e:
 
47
  if not gps_info:
48
  return None
49
 
 
50
  def gps_to_degrees(coord):
51
  d, m, s = coord
52
  return d + (m / 60.0) + (s / 3600.0)
 
80
  manipulada = False
81
  razones = []
82
 
 
83
  if imagen.mode == "P":
84
  razones.append("La imagen tiene marcas de agua o es una imagen indexada.")
85
  manipulada = True
86
 
 
87
  if not metadatos:
88
  razones.append("La imagen no tiene metadatos EXIF.")
89
  manipulada = True
90
  else:
 
91
  if "Software" in metadatos:
92
  razones.append(f"La imagen fue editada con: {metadatos['Software']}")
93
  manipulada = True
94
 
 
 
95
  hash_conocido = "d8e3d0e0d7a5e2b2c9d5f9d1c8e7a6f5b0d4e7c3f9d1a2b3c4d5e6f7a8b9c0d1"
96
  hash_actual = calcular_hash(imagen)
97
  if hash_actual != hash_conocido:
 
103
  def realizar_ela(imagen, quality=95, scale=100):
104
  """Realiza el Error Level Analysis (ELA) en la imagen."""
105
  try:
 
106
  img_np = np.array(imagen)
107
  img_cv = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
108
 
 
109
  temp_path = "/tmp/temp_image.jpg"
110
  cv2.imwrite(temp_path, img_cv, [cv2.IMWRITE_JPEG_QUALITY, quality])
111
 
 
112
  img_comprimida = cv2.imread(temp_path)
113
  if img_comprimida is None:
114
  raise ValueError("Error al leer la imagen comprimida")
115
 
 
116
  diferencia = cv2.absdiff(img_cv, img_comprimida)
 
 
117
  ela_imagen = scale * diferencia
 
 
118
  ela_imagen_gray = cv2.cvtColor(ela_imagen, cv2.COLOR_BGR2GRAY)
119
 
 
120
  os.remove(temp_path)
121
 
122
  return ela_imagen_gray
 
127
  def procesar_imagen(archivo_imagen):
128
  """Procesa la imagen completa y genera resultados."""
129
  try:
 
130
  img = Image.open(archivo_imagen)
131
  logger.info(f"Imagen cargada: {archivo_imagen}")
132
 
 
133
  nombre = os.path.splitext(os.path.basename(archivo_imagen))[0]
134
 
 
135
  original_path = os.path.join(evidence_dir, f"{nombre}_original.jpg")
136
  ela_path = os.path.join(evidence_dir, f"{nombre}_ela.jpg")
137
  text_path = os.path.join(evidence_dir, f"{nombre}_analisis.txt")
138
  zip_path = os.path.join(evidence_dir, f"{nombre}_errorELA.zip")
139
 
 
140
  img.save(original_path)
141
  ela_imagen = realizar_ela(img)
 
 
 
142
  ela_color = cv2.applyColorMap(ela_imagen, cv2.COLORMAP_JET)
 
 
143
  cv2.imwrite(ela_path, ela_color)
144
 
 
145
  info_basica = f"Formato: {img.format}\nTamaño: {img.size} píxeles\nModo: {img.mode}\n"
146
 
147
  metadatos = obtener_metadatos(img)
 
171
  else:
172
  info_metadatos += "No se encontraron metadatos EXIF\n"
173
 
 
174
  sha3_hash = calcular_hash(img)
175
  info_metadatos += f"\nSHA3-256: {sha3_hash}\n"
176
 
 
177
  manipulada, razones = analizar_manipulacion(img, metadatos)
178
  info_manipulacion = "\nANÁLISIS DE MANIPULACIÓN:\n"
179
  if manipulada:
 
183
  else:
184
  info_manipulacion += "LA IMAGEN NO HA SIDO MANIPULADA.\n"
185
 
 
186
  analysis_text = f"{info_basica}\n{info_metadatos}\n{info_manipulacion}"
187
 
188
  with open(text_path, "w") as f:
189
  f.write(analysis_text)
190
 
 
191
  with zipfile.ZipFile(zip_path, "w") as zipf:
192
  zipf.write(original_path, os.path.basename(original_path))
193
  zipf.write(ela_path, os.path.basename(ela_path))
 
195
 
196
  logger.info(f"Análisis completado. Archivo ZIP: {zip_path}")
197
 
198
+ # Devolver solo 2 valores (coincide con los 2 outputs definidos)
199
  return zip_path, analysis_text
200
+
201
  except Exception as e:
202
  logger.error(f"Error en procesamiento: {str(e)}")
203
  return f"Error: {str(e)}", f"Error al procesar la imagen: {str(e)}"
204
 
205
+ # 🎨 Tema oscuro moderno para Gradio v4+
206
+ theme = gr.themes.Base().set(
207
+ body_background_fill_dark="#121212",
208
+ block_background_fill_dark="#1e1e1e",
209
+ input_background_fill_dark="#2d2d2d",
210
+ button_primary_background_fill_dark="#0066cc",
211
+ button_secondary_background_fill_dark="#333333",
212
+ text_color_dark="#ffffff",
213
+ border_color_primary_dark="#444444"
214
+ )
215
+
216
+ # 🖥️ Interfaz Gradio
217
+ with gr.Blocks(title="Análisis Forense de Imágenes con ELA", theme=theme) as demo:
218
  gr.Markdown("""
219
  # 📸 Análisis Forense de Imágenes con Error Level Analysis (ELA)
220
  Programa de computación forense para analizar imágenes en busca de evidencia de manipulación o edición.
 
222
 
223
  with gr.Row():
224
  with gr.Column():
 
225
  input_image = gr.Image(label="Subir imagen (JPG/PNG)", type="filepath", height=400)
226
  process_btn = gr.Button("Analizar imagen", variant="primary")
227
 
228
  with gr.Column():
 
229
  with gr.Accordion("Descargar resultados ZIP", open=True):
230
  download_zip = gr.File(label="Descargar archivo ZIP", interactive=False)
231
  metadata_text = gr.Textbox(label="Metadatos y SHA3-256", lines=10, max_lines=20)
232
 
 
233
  with gr.Accordion("Análisis detallado", open=True):
234
  ela_image = gr.Image(label="Imagen con Error Level Analysis (ELA)", type="numpy", elem_id="ela_image")
235
  analysis_text = gr.Textbox(label="Análisis detallado", lines=15, max_lines=25)
236
 
237
+ # ⚙️ Conexión de eventos
238
  process_btn.click(
239
  fn=procesar_imagen,
240
  inputs=input_image,
241
+ outputs=[download_zip, analysis_text], # 👈 Solo 2 outputs (ZIP y texto)
242
  api_name="analyze_image"
243
  )
244
 
 
245
  input_image.change(
246
+ fn=lambda x: f"Análisis en progreso para: {os.path.basename(x)}" if x else "",
247
  inputs=input_image,
248
  outputs=analysis_text
249
  )
250
 
251
+ # ▶️ Ejecución
252
  if __name__ == "__main__":
 
253
  demo.launch(
254
  server_name="0.0.0.0",
255
  server_port=7860,
256
  share=True,
257
  inbrowser=True,
258
  favicon_path="https://huggingface.co/datasets/huggingface/logo/resolve/main/hf-logo.png"
259
+ )