Gaston
up hugging
b2293d5
Raw
History Blame Contribute Delete
28.4 kB
"""
===================================================================
VISUALIZER.PY - Generación de Visualizaciones
===================================================================
Este módulo crea todas las visualizaciones de la aplicación:
1. Imágenes con overlays de heatmaps
2. Marcadores de ROI y zonas de activación
3. Comparaciones de 4 paneles (Real vs Sintética)
4. Gráficos de estadísticas de neuronas
Uso:
viz = Visualizer()
fig = viz.create_heatmap_overlay(image, heatmap, roi_center)
===================================================================
"""
import torch
from config import (
HEATMAP_COLORMAP,
HEATMAP_ALPHA,
ROI_SIZE,
ROI_BORDER_COLOR,
ROI_BORDER_WIDTH,
ROI_MARKER_SIZE,
FIGURE_DPI
)
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import Rectangle, FancyBboxPatch
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
from typing import Tuple, Optional, List, Dict
from PIL import Image, ImageDraw
from skimage.transform import resize as sk_resize
# Importar configuración
import sys
sys.path.append('..')
class Visualizer:
"""
Generador de visualizaciones para la dashboard.
Crea figuras matplotlib para mostrar activaciones, heatmaps,
comparaciones y estadísticas.
"""
def __init__(
self,
colormap: str = HEATMAP_COLORMAP,
alpha: float = HEATMAP_ALPHA,
dpi: int = FIGURE_DPI
):
"""
Inicializa el visualizador.
Args:
colormap: Colormap para heatmaps ('jet', 'hot', etc.)
alpha: Transparencia de overlays (0-1)
dpi: DPI para figuras
"""
self.colormap = colormap
self.alpha = alpha
self.dpi = dpi
print(f"✅ Visualizer inicializado")
print(f" Colormap: {colormap}")
print(f" Alpha: {alpha}")
def create_heatmap_overlay(
self,
image: np.ndarray,
heatmap: np.ndarray,
roi_center: Optional[Tuple[int, int]] = None,
roi_size: Tuple[int, int] = ROI_SIZE,
title: str = "Mapa de Activación"
) -> plt.Figure:
"""
Crea overlay de heatmap sobre imagen con marcador de ROI.
Args:
image: Array [H, W, 3] en [0, 1]
heatmap: Array [H, W] en [0, 1]
roi_center: (y, x) centro del ROI (opcional)
roi_size: (height, width) del ROI
title: Título de la figura
Returns:
Figura de matplotlib
"""
fig, axes = plt.subplots(1, 2, figsize=(12, 5), dpi=self.dpi)
# Panel 1: Imagen original con ROI
axes[0].imshow(image)
axes[0].set_title('Imagen Original', fontsize=12, fontweight='bold')
axes[0].axis('off')
# Dibujar ROI si se proporciona
if roi_center is not None:
y_center, x_center = roi_center
h, w = roi_size
# Rectángulo del ROI
rect = Rectangle(
(x_center - w//2, y_center - h//2),
w, h,
linewidth=ROI_BORDER_WIDTH,
edgecolor=ROI_BORDER_COLOR,
facecolor='none'
)
axes[0].add_patch(rect)
# Marcador de estrella en el centro
axes[0].scatter(
x_center, y_center,
marker='*',
s=ROI_MARKER_SIZE,
c='yellow',
edgecolors='black',
linewidths=1,
zorder=10
)
# Panel 2: Heatmap overlay
axes[1].imshow(image)
# Aplicar heatmap con transparencia
cmap = plt.get_cmap(self.colormap)
heatmap_colored = cmap(heatmap)
axes[1].imshow(heatmap_colored, alpha=self.alpha)
axes[1].set_title('Mapa de Calor', fontsize=12, fontweight='bold')
axes[1].axis('off')
# Dibujar ROI también en heatmap
if roi_center is not None:
y_center, x_center = roi_center
h, w = roi_size
rect = Rectangle(
(x_center - w//2, y_center - h//2),
w, h,
linewidth=ROI_BORDER_WIDTH,
edgecolor=ROI_BORDER_COLOR,
facecolor='none'
)
axes[1].add_patch(rect)
axes[1].scatter(
x_center, y_center,
marker='*',
s=ROI_MARKER_SIZE,
c='yellow',
edgecolors='black',
linewidths=1,
zorder=10
)
plt.suptitle(title, fontsize=14, fontweight='bold', y=0.98)
plt.tight_layout()
return fig
def create_4panel_comparison(
self,
image_full: np.ndarray,
roi_real: np.ndarray,
roi_synthetic: np.ndarray,
roi_center: Tuple[int, int],
neuron_idx: int,
real_activation: float,
synthetic_activation: float
) -> plt.Figure:
"""
Crea comparación de 4 paneles: Real vs Ideal (como en la imagen ejemplo).
Args:
image_full: Imagen completa [H, W, 3] en [0, 1]
roi_real: ROI real extraído [roi_h, roi_w, 3]
roi_synthetic: Patrón sintético [roi_h, roi_w, 3]
roi_center: (y, x) del ROI
neuron_idx: Índice de la neurona
real_activation: Activación en imagen real
synthetic_activation: Activación en patrón sintético
Returns:
Figura de matplotlib
"""
fig = plt.figure(figsize=(16, 4), dpi=self.dpi)
# Título principal
fig.suptitle(
f'Comparación: Real vs Ideal - Neurona {neuron_idx}',
fontsize=16,
fontweight='bold',
y=0.98
)
# ---------------------------------------------------------------
# Panel 1: Imagen completa con ROI marcado
# ---------------------------------------------------------------
ax1 = plt.subplot(1, 4, 1)
ax1.imshow(image_full)
# Dibujar ROI
y_center, x_center = roi_center
h, w = ROI_SIZE
rect = Rectangle(
(x_center - w//2, y_center - h//2),
w, h,
linewidth=3,
edgecolor='red',
facecolor='none'
)
ax1.add_patch(rect)
# Estrella
ax1.scatter(
x_center, y_center,
marker='*',
s=150,
c='yellow',
edgecolors='black',
linewidths=1.5,
zorder=10
)
ax1.set_title(
f'Imagen Real Completa\nROI: {w}x{h}',
fontsize=11,
fontweight='bold'
)
ax1.axis('off')
# ---------------------------------------------------------------
# Panel 2: ROI Real (zona de máxima activación)
# ---------------------------------------------------------------
ax2 = plt.subplot(1, 4, 2)
# Asegurar que roi_real está en [0, 1] o escalar
if roi_real.max() > 1.0:
roi_real = roi_real / 255.0
ax2.imshow(roi_real)
ax2.set_title(
'Región Real\n(Zona de máxima activación)',
fontsize=11,
fontweight='bold'
)
ax2.axis('off')
# Añadir borde
for spine in ax2.spines.values():
spine.set_edgecolor('blue')
spine.set_linewidth(2)
# ---------------------------------------------------------------
# Panel 3: Patrón Sintético (Ideal)
# ---------------------------------------------------------------
ax3 = plt.subplot(1, 4, 3)
# Asegurar que roi_synthetic está en [0, 1] o escalar
if roi_synthetic.max() > 1.0:
roi_synthetic = roi_synthetic / 255.0
ax3.imshow(roi_synthetic)
ax3.set_title(
'Patrón Ideal (Sintética)\n(Redimensionada)',
fontsize=11,
fontweight='bold'
)
ax3.axis('off')
# Añadir borde
for spine in ax3.spines.values():
spine.set_edgecolor('green')
spine.set_linewidth(2)
# ---------------------------------------------------------------
# Panel 4: Superposición 50/50
# ---------------------------------------------------------------
ax4 = plt.subplot(1, 4, 4)
# Crear overlay 50/50
overlay = 0.5 * roi_real + 0.5 * roi_synthetic
overlay = np.clip(overlay, 0, 1)
ax4.imshow(overlay)
ax4.set_title(
'Superposición 50/50\n(Coincidencias visuales)',
fontsize=11,
fontweight='bold'
)
ax4.axis('off')
# ---------------------------------------------------------------
# Añadir métricas en la parte inferior
# ---------------------------------------------------------------
metrics_text = (
f'Activación Real: {real_activation:.3f} | '
f'Activación Sintética: {synthetic_activation:.3f} | '
f'Mejora: {(synthetic_activation/max(real_activation, 1e-8)):.2f}x'
)
fig.text(
0.5, 0.02,
metrics_text,
ha='center',
fontsize=11,
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)
)
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
return fig
def create_neuron_ranking(
self,
stats: List[Dict],
top_k: int = 10,
criterion: str = 'mean'
) -> plt.Figure:
"""
Crea gráfico de ranking de neuronas.
Args:
stats: Lista de estadísticas por neurona
top_k: Número de neuronas a mostrar
criterion: Criterio de ranking ('mean', 'max', 'std')
Returns:
Figura de matplotlib
"""
# Ordenar por criterio
sorted_stats = sorted(stats, key=lambda x: x[criterion], reverse=True)
top_stats = sorted_stats[:top_k]
# Extraer datos
neuron_indices = [s['neuron_idx'] for s in top_stats]
values = [s[criterion] for s in top_stats]
# Crear figura
fig, ax = plt.subplots(figsize=(10, 6), dpi=self.dpi)
# Barras horizontales
bars = ax.barh(range(len(neuron_indices)), values, color='steelblue')
# Colorear la barra más alta
bars[0].set_color('crimson')
# Etiquetas
ax.set_yticks(range(len(neuron_indices)))
ax.set_yticklabels([f'Neurona {idx}' for idx in neuron_indices])
ax.set_xlabel(f'Activación ({criterion.capitalize()})', fontsize=11)
ax.set_title(
f'Top {top_k} Neuronas Más Activas',
fontsize=13,
fontweight='bold'
)
# Añadir valores en las barras
for i, (bar, val) in enumerate(zip(bars, values)):
ax.text(
val,
bar.get_y() + bar.get_height()/2,
f' {val:.3f}',
va='center',
fontsize=9
)
# Grid
ax.grid(axis='x', alpha=0.3, linestyle='--')
plt.tight_layout()
return fig
def create_activation_heatmap_grid(
self,
image: np.ndarray,
activations: np.ndarray,
neuron_indices: List[int],
max_display: int = 8
) -> plt.Figure:
"""
Crea grid de mapas de activación para múltiples neuronas.
Args:
image: Imagen original [H, W, 3]
activations: Tensor [C, H, W] de activaciones
neuron_indices: Lista de índices de neuronas a mostrar
max_display: Máximo de neuronas a mostrar
Returns:
Figura de matplotlib
"""
num_neurons = min(len(neuron_indices), max_display)
# Calcular grid
ncols = min(4, num_neurons)
nrows = (num_neurons + ncols - 1) // ncols + \
1 # +1 para imagen original
fig = plt.figure(figsize=(4*ncols, 4*nrows), dpi=self.dpi)
# Primera fila: Imagen original
ax = plt.subplot(nrows, ncols, 1)
ax.imshow(image)
ax.set_title('Imagen Original', fontsize=11, fontweight='bold')
ax.axis('off')
# Resto de filas: Mapas de activación
for idx, neuron_idx in enumerate(neuron_indices[:max_display], start=2):
ax = plt.subplot(nrows, ncols, idx)
# Obtener mapa de activación
act_map = activations[neuron_idx]
# Normalizar
if act_map.max() > act_map.min():
act_map_norm = (act_map - act_map.min()) / \
(act_map.max() - act_map.min())
else:
act_map_norm = act_map
# Mostrar heatmap
im = ax.imshow(act_map_norm, cmap=self.colormap,
interpolation='bilinear')
# Título con estadísticas
mean_act = act_map.mean()
max_act = act_map.max()
ax.set_title(
f'Neurona {neuron_idx}\nμ={mean_act:.3f}, max={max_act:.3f}',
fontsize=10,
fontweight='bold'
)
ax.axis('off')
# Colorbar
plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
plt.suptitle(
'Mapas de Activación por Neurona',
fontsize=14,
fontweight='bold',
y=0.98
)
plt.tight_layout()
return fig
def add_roi_to_image(
self,
image: np.ndarray,
roi_center: Tuple[int, int],
roi_size: Tuple[int, int] = ROI_SIZE
) -> np.ndarray:
"""
Añade marcador visual de ROI a una imagen (sin matplotlib).
Args:
image: Array [H, W, 3] en [0, 1] o [0, 255]
roi_center: (y, x) centro del ROI
roi_size: (height, width) del ROI
Returns:
Imagen con ROI dibujado [H, W, 3]
"""
# Convertir a PIL para dibujar
if image.max() <= 1.0:
img_pil = Image.fromarray((image * 255).astype(np.uint8))
else:
img_pil = Image.fromarray(image.astype(np.uint8))
draw = ImageDraw.Draw(img_pil)
# Calcular coordenadas del rectángulo
y_center, x_center = roi_center
h, w = roi_size
x1 = x_center - w // 2
y1 = y_center - h // 2
x2 = x_center + w // 2
y2 = y_center + h // 2
# Dibujar rectángulo
draw.rectangle(
[x1, y1, x2, y2],
outline='red',
width=ROI_BORDER_WIDTH
)
# Dibujar punto central (aproximación de estrella)
star_size = 8
draw.ellipse(
[x_center-star_size, y_center-star_size,
x_center+star_size, y_center+star_size],
fill='yellow',
outline='black'
)
# Convertir de vuelta a numpy
img_with_roi = np.array(img_pil)
# Mantener el mismo rango que entrada
if image.max() <= 1.0:
img_with_roi = img_with_roi / 255.0
return img_with_roi
def create_pattern_overlay_comparison(
self,
image_full: np.ndarray,
synthetic_pattern: np.ndarray,
roi_center: tuple[int, int],
neuron_idx: int,
real_activation: float,
synthetic_activation: float
) -> plt.Figure:
"""
Crea comparación con patrón sintético repetido sobre toda la imagen.
Args:
image_full: Imagen completa [H, W, 3]
synthetic_pattern: Patrón sintético [H_s, W_s, 3]
roi_center: Centro del ROI (y, x)
neuron_idx: Índice de la neurona
real_activation: Activación en imagen real
synthetic_activation: Activación en patrón sintético
Returns:
Figura de matplotlib
"""
fig = plt.figure(figsize=(16, 5), dpi=self.dpi)
# Título principal
fig.suptitle(
f'Análisis de Neurona {neuron_idx} - Patrón Ideal Superpuesto',
fontsize=16,
fontweight='bold',
y=0.98
)
# Panel 1: Imagen original con ROI
ax1 = plt.subplot(1, 3, 1)
ax1.imshow(image_full)
# Marcar ROI
y_center, x_center = roi_center
h, w = ROI_SIZE
rect = Rectangle(
(x_center - w//2, y_center - h//2),
w, h,
linewidth=3,
edgecolor='red',
facecolor='none'
)
ax1.add_patch(rect)
ax1.scatter(
x_center, y_center,
marker='*',
s=150,
c='yellow',
edgecolors='black',
linewidths=1.5,
zorder=10
)
ax1.set_title(
'Imagen Real\n(ROI: zona de máxima activación)',
fontsize=12,
fontweight='bold'
)
ax1.axis('off')
# Panel 2: Patrón sintético (grande, para referencia)
ax2 = plt.subplot(1, 3, 2)
# Asegurar rango correcto
if synthetic_pattern.max() > 1.0:
synthetic_display = synthetic_pattern / 255.0
else:
synthetic_display = synthetic_pattern
ax2.imshow(synthetic_display)
ax2.set_title(
'Patrón Ideal\n(Maximiza activación)',
fontsize=12,
fontweight='bold'
)
ax2.axis('off')
for spine in ax2.spines.values():
spine.set_edgecolor('green')
spine.set_linewidth(3)
# Panel 3: Imagen con patrón repetido superpuesto
ax3 = plt.subplot(1, 3, 3)
# Crear patrón tiled del tamaño de la imagen
img_h, img_w = image_full.shape[:2]
# ESCALAR el patrón a un tamaño apropiado para tiling
# Redimensionar patrón a tamaño de ROI para que se vea bien repetido
target_tile_size = ROI_SIZE # (32, 32) típicamente
pattern_scaled = sk_resize(
synthetic_display,
target_tile_size,
anti_aliasing=True
)
pat_h, pat_w = pattern_scaled.shape[:2]
# Calcular cuántas repeticiones necesitamos
n_tiles_y = int(np.ceil(img_h / pat_h)) + 1
n_tiles_x = int(np.ceil(img_w / pat_w)) + 1
# Crear patrón tiled
pattern_tiled = np.tile(pattern_scaled, (n_tiles_y, n_tiles_x, 1))
# Recortar al tamaño exacto de la imagen
pattern_tiled = pattern_tiled[:img_h, :img_w, :]
# Superponer imagen + patrón
# Imagen base con más peso
alpha_image = 0.6
alpha_pattern = 0.4
overlay = alpha_image * image_full + alpha_pattern * pattern_tiled
overlay = np.clip(overlay, 0, 1)
ax3.imshow(overlay)
# Marcar ROI también aquí
rect = Rectangle(
(x_center - w//2, y_center - h//2),
w, h,
linewidth=3,
edgecolor='red',
facecolor='none',
linestyle='--'
)
ax3.add_patch(rect)
ax3.set_title(
'Superposición Global\n(Patrón repetido sobre imagen)',
fontsize=12,
fontweight='bold'
)
ax3.axis('off')
# Métricas en la parte inferior
metrics_text = (
f'Activación Real: {real_activation:.3f} | '
f'Activación Sintética: {synthetic_activation:.3f} | '
f'Mejora: {(synthetic_activation/max(real_activation, 1e-8)):.2f}x'
)
fig.text(
0.5, 0.02,
metrics_text,
ha='center',
fontsize=11,
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)
)
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
return fig
def create_heatmap_with_neuron_markers(
self,
image: np.ndarray,
activations: torch.Tensor,
neuron_stats: List[Dict],
top_n: int = 10,
title: str = "Mapa de Activación con Neuronas Marcadas"
) -> plt.Figure:
"""
Crea mapa de calor con marcadores numerados para top neuronas.
Args:
image: Imagen original [H, W, 3]
activations: Tensor de activaciones [1, C, H, W]
neuron_stats: Lista de estadísticas de neuronas
top_n: Número de neuronas top a marcar
title: Título de la figura
Returns:
Figura de matplotlib
"""
from config import IMAGE_SIZE
from scipy.ndimage import zoom
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 7), dpi=self.dpi)
# Título principal
fig.suptitle(title, fontsize=16, fontweight='bold', y=0.98)
# Panel 1: Imagen original con marcadores
ax1.imshow(image)
ax1.set_title('Imagen Original\n(Neuronas numeradas por activación)',
fontsize=12, fontweight='bold')
ax1.axis('off')
# Panel 2: Mapa de calor con marcadores
# Generar heatmap global (máximo entre todas las neuronas)
if activations.dim() == 4:
acts = activations[0] # [C, H, W]
else:
acts = activations
# Heatmap global
heatmap = acts.max(dim=0)[0].cpu().numpy() # [H, W]
# Redimensionar al tamaño de la imagen
scale_y = IMAGE_SIZE[0] / heatmap.shape[0]
scale_x = IMAGE_SIZE[1] / heatmap.shape[1]
heatmap_resized = zoom(heatmap, (scale_y, scale_x), order=1)
# Normalizar
heatmap_norm = (heatmap_resized - heatmap_resized.min()) / \
(heatmap_resized.max() - heatmap_resized.min() + 1e-8)
# Importar configuración
from config import HEATMAP_COLORMAP, HEATMAP_ALPHA
# Mostrar imagen con overlay
ax2.imshow(image)
heatmap_colored = plt.get_cmap(HEATMAP_COLORMAP)(heatmap_norm)
ax2.imshow(heatmap_colored, alpha=HEATMAP_ALPHA)
ax2.set_title('Mapa de Calor\n(Posiciones de máxima activación)',
fontsize=12, fontweight='bold')
ax2.axis('off')
# Ordenar neuronas por activación
ranked = sorted(neuron_stats, key=lambda x: x['mean'], reverse=True)[
:top_n]
# Marcar cada neurona en ambos paneles
for i, stat in enumerate(ranked, 1):
neuron_idx = stat['neuron_idx']
# Obtener mapa de activación de esta neurona específica
neuron_map = acts[neuron_idx].cpu().numpy() # [H, W]
# Encontrar posición de máximo
max_pos = np.unravel_index(neuron_map.argmax(), neuron_map.shape)
# Escalar a coordenadas de imagen
y_img = int(max_pos[0] * scale_y)
x_img = int(max_pos[1] * scale_x)
# Color diferente para la neurona #1
if i == 1:
marker_color = 'red'
marker_size = 200
edge_color = 'yellow'
edge_width = 3
else:
marker_color = 'lime'
marker_size = 150
edge_color = 'white'
edge_width = 2
# Marcar en ambos paneles
for ax in [ax1, ax2]:
# Punto
ax.scatter(
x_img, y_img,
s=marker_size,
c=marker_color,
marker='o',
edgecolors=edge_color,
linewidths=edge_width,
zorder=10,
alpha=0.8
)
# Número
ax.text(
x_img, y_img,
str(i),
color='white',
fontsize=10 if i == 1 else 8,
fontweight='bold',
ha='center',
va='center',
zorder=11,
bbox=dict(
boxstyle='circle,pad=0.1',
facecolor='black',
alpha=0.7,
edgecolor='none'
)
)
# Leyenda
from matplotlib.patches import Patch
legend_elements = [
Patch(facecolor='red', edgecolor='yellow',
label='#1 Neurona más activa'),
Patch(facecolor='lime', edgecolor='white',
label='Top 2-10 neuronas')
]
ax2.legend(
handles=legend_elements,
loc='upper right',
framealpha=0.9,
fontsize=9
)
plt.tight_layout(rect=[0, 0, 1, 0.96])
return fig
# ===================================================================
# FUNCIONES DE UTILIDAD
# ===================================================================
def resize_to_match(
source: np.ndarray,
target_shape: Tuple[int, int]
) -> np.ndarray:
"""
Redimensiona una imagen para que coincida con un shape objetivo.
Args:
source: Array a redimensionar
target_shape: (height, width) objetivo
Returns:
Array redimensionado
"""
from skimage.transform import resize
if source.ndim == 3:
return resize(
source,
(target_shape[0], target_shape[1], source.shape[2]),
anti_aliasing=True
)
else:
return resize(
source,
target_shape,
anti_aliasing=True
)
# ===================================================================
# TESTING
# ===================================================================
if __name__ == "__main__":
print("🧪 Testing Visualizer...\n")
# Crear visualizer
viz = Visualizer()
# Crear datos de prueba
print("1️⃣ Creando datos de prueba...")
image = np.random.rand(224, 224, 3)
heatmap = np.random.rand(224, 224)
roi_center = (112, 112)
# Test 1: Heatmap overlay
print("\n2️⃣ Creando heatmap overlay...")
fig1 = viz.create_heatmap_overlay(image, heatmap, roi_center)
print(" ✓ Figura creada")
plt.close(fig1)
# Test 2: ROI en imagen
print("\n3️⃣ Añadiendo ROI a imagen...")
img_with_roi = viz.add_roi_to_image(image, roi_center)
print(f" ✓ Imagen con ROI: {img_with_roi.shape}")
# Test 3: Ranking
print("\n4️⃣ Creando ranking de neuronas...")
fake_stats = [
{'neuron_idx': i, 'mean': np.random.rand(), 'max': np.random.rand(),
'std': 0.1}
for i in range(20)
]
fig2 = viz.create_neuron_ranking(fake_stats, top_k=10)
print(" ✓ Figura de ranking creada")
plt.close(fig2)
# Test 4: Comparación 4-panel
print("\n5️⃣ Creando comparación 4-panel...")
roi_real = np.random.rand(32, 32, 3)
roi_synthetic = np.random.rand(32, 32, 3)
fig3 = viz.create_4panel_comparison(
image, roi_real, roi_synthetic,
roi_center, neuron_idx=38,
real_activation=18.3,
synthetic_activation=24.7
)
print(" ✓ Figura de comparación creada")
plt.close(fig3)
print("\n✅ Testing completado!")