| """
|
| ===================================================================
|
| 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
|
|
|
| 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)
|
|
|
|
|
| axes[0].imshow(image)
|
| axes[0].set_title('Imagen Original', fontsize=12, fontweight='bold')
|
| axes[0].axis('off')
|
|
|
|
|
| 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[0].add_patch(rect)
|
|
|
|
|
| axes[0].scatter(
|
| x_center, y_center,
|
| marker='*',
|
| s=ROI_MARKER_SIZE,
|
| c='yellow',
|
| edgecolors='black',
|
| linewidths=1,
|
| zorder=10
|
| )
|
|
|
|
|
| axes[1].imshow(image)
|
|
|
|
|
| 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')
|
|
|
|
|
| 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)
|
|
|
|
|
| fig.suptitle(
|
| f'Comparación: Real vs Ideal - Neurona {neuron_idx}',
|
| fontsize=16,
|
| fontweight='bold',
|
| y=0.98
|
| )
|
|
|
|
|
|
|
|
|
| ax1 = plt.subplot(1, 4, 1)
|
| ax1.imshow(image_full)
|
|
|
|
|
| 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(
|
| f'Imagen Real Completa\nROI: {w}x{h}',
|
| fontsize=11,
|
| fontweight='bold'
|
| )
|
| ax1.axis('off')
|
|
|
|
|
|
|
|
|
| ax2 = plt.subplot(1, 4, 2)
|
|
|
|
|
| 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')
|
|
|
|
|
| for spine in ax2.spines.values():
|
| spine.set_edgecolor('blue')
|
| spine.set_linewidth(2)
|
|
|
|
|
|
|
|
|
| ax3 = plt.subplot(1, 4, 3)
|
|
|
|
|
| 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')
|
|
|
|
|
| for spine in ax3.spines.values():
|
| spine.set_edgecolor('green')
|
| spine.set_linewidth(2)
|
|
|
|
|
|
|
|
|
| ax4 = plt.subplot(1, 4, 4)
|
|
|
|
|
| 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')
|
|
|
|
|
|
|
|
|
| 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
|
| """
|
|
|
| sorted_stats = sorted(stats, key=lambda x: x[criterion], reverse=True)
|
| top_stats = sorted_stats[:top_k]
|
|
|
|
|
| neuron_indices = [s['neuron_idx'] for s in top_stats]
|
| values = [s[criterion] for s in top_stats]
|
|
|
|
|
| fig, ax = plt.subplots(figsize=(10, 6), dpi=self.dpi)
|
|
|
|
|
| bars = ax.barh(range(len(neuron_indices)), values, color='steelblue')
|
|
|
|
|
| bars[0].set_color('crimson')
|
|
|
|
|
| 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'
|
| )
|
|
|
|
|
| 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
|
| )
|
|
|
|
|
| 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)
|
|
|
|
|
| ncols = min(4, num_neurons)
|
| nrows = (num_neurons + ncols - 1) // ncols + \
|
| 1
|
|
|
| fig = plt.figure(figsize=(4*ncols, 4*nrows), dpi=self.dpi)
|
|
|
|
|
| ax = plt.subplot(nrows, ncols, 1)
|
| ax.imshow(image)
|
| ax.set_title('Imagen Original', fontsize=11, fontweight='bold')
|
| ax.axis('off')
|
|
|
|
|
| for idx, neuron_idx in enumerate(neuron_indices[:max_display], start=2):
|
| ax = plt.subplot(nrows, ncols, idx)
|
|
|
|
|
| act_map = activations[neuron_idx]
|
|
|
|
|
| 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
|
|
|
|
|
| im = ax.imshow(act_map_norm, cmap=self.colormap,
|
| interpolation='bilinear')
|
|
|
|
|
| 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')
|
|
|
|
|
| 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]
|
| """
|
|
|
| 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)
|
|
|
|
|
| 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
|
|
|
|
|
| draw.rectangle(
|
| [x1, y1, x2, y2],
|
| outline='red',
|
| width=ROI_BORDER_WIDTH
|
| )
|
|
|
|
|
| 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'
|
| )
|
|
|
|
|
| img_with_roi = np.array(img_pil)
|
|
|
|
|
| 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)
|
|
|
|
|
| fig.suptitle(
|
| f'Análisis de Neurona {neuron_idx} - Patrón Ideal Superpuesto',
|
| fontsize=16,
|
| fontweight='bold',
|
| y=0.98
|
| )
|
|
|
|
|
| ax1 = plt.subplot(1, 3, 1)
|
| ax1.imshow(image_full)
|
|
|
|
|
| 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')
|
|
|
|
|
| ax2 = plt.subplot(1, 3, 2)
|
|
|
|
|
| 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)
|
|
|
|
|
| ax3 = plt.subplot(1, 3, 3)
|
|
|
|
|
| img_h, img_w = image_full.shape[:2]
|
|
|
|
|
|
|
|
|
| target_tile_size = ROI_SIZE
|
| pattern_scaled = sk_resize(
|
| synthetic_display,
|
| target_tile_size,
|
| anti_aliasing=True
|
| )
|
|
|
| pat_h, pat_w = pattern_scaled.shape[:2]
|
|
|
|
|
| n_tiles_y = int(np.ceil(img_h / pat_h)) + 1
|
| n_tiles_x = int(np.ceil(img_w / pat_w)) + 1
|
|
|
|
|
| pattern_tiled = np.tile(pattern_scaled, (n_tiles_y, n_tiles_x, 1))
|
|
|
|
|
| pattern_tiled = pattern_tiled[:img_h, :img_w, :]
|
|
|
|
|
|
|
| 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)
|
|
|
|
|
| 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')
|
|
|
|
|
| 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)
|
|
|
|
|
| fig.suptitle(title, fontsize=16, fontweight='bold', y=0.98)
|
|
|
|
|
| ax1.imshow(image)
|
| ax1.set_title('Imagen Original\n(Neuronas numeradas por activación)',
|
| fontsize=12, fontweight='bold')
|
| ax1.axis('off')
|
|
|
|
|
|
|
| if activations.dim() == 4:
|
| acts = activations[0]
|
| else:
|
| acts = activations
|
|
|
|
|
| heatmap = acts.max(dim=0)[0].cpu().numpy()
|
|
|
|
|
| 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)
|
|
|
|
|
| heatmap_norm = (heatmap_resized - heatmap_resized.min()) / \
|
| (heatmap_resized.max() - heatmap_resized.min() + 1e-8)
|
|
|
|
|
| from config import HEATMAP_COLORMAP, HEATMAP_ALPHA
|
|
|
| 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')
|
|
|
|
|
| ranked = sorted(neuron_stats, key=lambda x: x['mean'], reverse=True)[
|
| :top_n]
|
|
|
|
|
| for i, stat in enumerate(ranked, 1):
|
| neuron_idx = stat['neuron_idx']
|
|
|
|
|
| neuron_map = acts[neuron_idx].cpu().numpy()
|
|
|
|
|
| max_pos = np.unravel_index(neuron_map.argmax(), neuron_map.shape)
|
|
|
|
|
| y_img = int(max_pos[0] * scale_y)
|
| x_img = int(max_pos[1] * scale_x)
|
|
|
|
|
| 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
|
|
|
|
|
| for ax in [ax1, ax2]:
|
|
|
| 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
|
| )
|
|
|
|
|
| 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'
|
| )
|
| )
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| )
|
|
|
|
|
|
|
|
|
|
|
|
|
| if __name__ == "__main__":
|
| print("🧪 Testing Visualizer...\n")
|
|
|
|
|
| viz = Visualizer()
|
|
|
|
|
| print("1️⃣ Creando datos de prueba...")
|
| image = np.random.rand(224, 224, 3)
|
| heatmap = np.random.rand(224, 224)
|
| roi_center = (112, 112)
|
|
|
|
|
| print("\n2️⃣ Creando heatmap overlay...")
|
| fig1 = viz.create_heatmap_overlay(image, heatmap, roi_center)
|
| print(" ✓ Figura creada")
|
| plt.close(fig1)
|
|
|
|
|
| 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}")
|
|
|
|
|
| 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)
|
|
|
|
|
| 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!")
|
|
|