Update app.py
Browse files
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)
|
| 142 |
fig_bar = go.Figure()
|
| 143 |
fig_bar.add_trace(go.Bar(
|
| 144 |
x=['Áreas Detectadas'],
|
| 145 |
y=[porcentaje_ela],
|
| 146 |
-
marker_color=['#
|
| 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='#
|
| 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='#
|
| 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,
|
| 224 |
|
| 225 |
if not os.path.exists(archivo_imagen):
|
| 226 |
-
return [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 |
-
|
| 263 |
-
info_basica = f"
|
| 264 |
-
info_basica += f"
|
| 265 |
-
info_basica += f"
|
| 266 |
-
info_basica += f"
|
| 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
|
| 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"\
|
| 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.\
|
| 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, ""
|
| 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 |
-
|
| 405 |
-
|
| 406 |
-
|
|
|
|
| 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
|