Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import torch | |
| import gradio as gr | |
| import matplotlib.pyplot as plt | |
| from matplotlib.lines import Line2D | |
| from lsttn_model import build_model | |
| DEVICE = "cuda" if torch.cuda.is_available() else "cpu" | |
| ARTIFACTS_DIR = "lsttn_artifacts" | |
| BG = "#1a1a1a" | |
| GREEN = "#16a34a" | |
| YELLOW = "#eab308" | |
| RED = "#dc2626" | |
| # ---- Carga de artefactos (una sola vez, al iniciar el Space) ---- | |
| meta = np.load(f"{ARTIFACTS_DIR}/meta.npy") | |
| NUM_NODES, WINDOW_SIZE, HORIZON = int(meta[0]), int(meta[1]), int(meta[2]) | |
| A = np.load(f"{ARTIFACTS_DIR}/adjacency.npy") | |
| mean_flujo, std_flujo = np.load(f"{ARTIFACTS_DIR}/mean_std.npy") | |
| demo_data = np.load(f"{ARTIFACTS_DIR}/demo_samples.npz") | |
| X_demo, Y_demo = demo_data["X"], demo_data["Y"] | |
| N_SAMPLES = X_demo.shape[0] | |
| POS = np.load(f"{ARTIFACTS_DIR}/node_positions.npy") | |
| EDGES = np.load(f"{ARTIFACTS_DIR}/edges.npy") | |
| model = build_model(NUM_NODES, WINDOW_SIZE, HORIZON, A, device=DEVICE) | |
| state = torch.load(f"{ARTIFACTS_DIR}/model_state.pt", map_location=DEVICE) | |
| model.load_state_dict(state) | |
| model.eval() | |
| def _predict_all_nodes(sample_id: int, horizon_step: int = 0): | |
| x = torch.tensor(X_demo[sample_id:sample_id + 1], dtype=torch.float32, device=DEVICE) | |
| with torch.no_grad(): | |
| pred = model(x, x) # (1, N, horizon) | |
| flow_norm = pred[0, :, horizon_step].cpu().numpy() | |
| flow_real = np.maximum(0, flow_norm * std_flujo + mean_flujo) | |
| return flow_real | |
| def _flow_color(v, low, high): | |
| if v <= low: | |
| return GREEN | |
| elif v <= high: | |
| return YELLOW | |
| return RED | |
| def plot_network(sample_id: int, horizon_step: int): | |
| sample_id = int(sample_id) | |
| horizon_step = int(horizon_step) | |
| flow_real = _predict_all_nodes(sample_id, horizon_step) | |
| low, high = np.percentile(flow_real, [33, 66]) | |
| fig, ax = plt.subplots(figsize=(8, 8)) | |
| fig.patch.set_facecolor(BG) | |
| ax.set_facecolor(BG) | |
| for i, j in EDGES: | |
| avg = (flow_real[i] + flow_real[j]) / 2 | |
| ax.plot( | |
| [POS[i, 0], POS[j, 0]], [POS[i, 1], POS[j, 1]], | |
| color=_flow_color(avg, low, high), linewidth=2, alpha=0.85, zorder=1, | |
| ) | |
| colors = [_flow_color(v, low, high) for v in flow_real] | |
| ax.scatter(POS[:, 0], POS[:, 1], c=colors, s=35, zorder=2, edgecolors="white", linewidths=0.4) | |
| ax.axis("off") | |
| ax.set_title( | |
| f"Red PEMS-04 — flujo previsto a t+{horizon_step + 1} (muestra {sample_id})", | |
| color="white", fontsize=12, | |
| ) | |
| legend_handles = [ | |
| Line2D([0], [0], color=GREEN, lw=3, label="Flujo bajo"), | |
| Line2D([0], [0], color=YELLOW, lw=3, label="Flujo medio"), | |
| Line2D([0], [0], color=RED, lw=3, label="Flujo alto"), | |
| ] | |
| ax.legend( | |
| handles=legend_handles, loc="lower center", bbox_to_anchor=(0.5, -0.05), | |
| ncol=3, frameon=False, labelcolor="white", | |
| ) | |
| fig.tight_layout() | |
| return fig | |
| def plot_sensor_detail(sensor_id: int, sample_id: int): | |
| sensor_id, sample_id = int(sensor_id), int(sample_id) | |
| x = torch.tensor(X_demo[sample_id:sample_id + 1], dtype=torch.float32, device=DEVICE) | |
| y_true = Y_demo[sample_id, sensor_id] | |
| with torch.no_grad(): | |
| pred = model(x, x) | |
| y_pred = pred[0, sensor_id].cpu().numpy() | |
| true_real = y_true * std_flujo + mean_flujo | |
| pred_real = np.maximum(0, y_pred * std_flujo + mean_flujo) | |
| mae = np.mean(np.abs(true_real - pred_real)) | |
| rmse = np.sqrt(np.mean((true_real - pred_real) ** 2)) | |
| fig, ax = plt.subplots(figsize=(6, 4)) | |
| fig.patch.set_facecolor(BG) | |
| ax.set_facecolor(BG) | |
| steps = np.arange(1, HORIZON + 1) | |
| ax.plot(steps, true_real, marker="o", label="Real", color="white") | |
| ax.plot(steps, pred_real, marker="x", label="Predicción LSTTN", color=GREEN, linestyle="--") | |
| ax.set_xlabel("Paso futuro (x5 min)", color="white") | |
| ax.set_ylabel("Flujo de tráfico (vehículos)", color="white") | |
| ax.set_title(f"Sensor {sensor_id} — muestra {sample_id}", color="white") | |
| ax.tick_params(colors="white") | |
| for spine in ax.spines.values(): | |
| spine.set_color("#555555") | |
| ax.legend(labelcolor="white", facecolor=BG, edgecolor="#555555") | |
| ax.grid(True, linestyle=":", alpha=0.3, color="white") | |
| fig.tight_layout() | |
| metrics_md = f"**MAE:** {mae:.2f} veh **RMSE:** {rmse:.2f} veh" | |
| return fig, metrics_md | |
| with gr.Blocks(title="LSTTN — Pronóstico de tráfico PEMS-04", theme=gr.themes.Base()) as demo: | |
| gr.Markdown( | |
| "# LSTTN — Pronóstico de flujo de tráfico\n" | |
| f"Red vial PEMS-04 · {NUM_NODES} sensores · ventana de {WINDOW_SIZE} pasos → horizonte de {HORIZON} pasos." | |
| ) | |
| with gr.Tabs(): | |
| with gr.Tab("Mapa de red"): | |
| with gr.Row(): | |
| sample_slider_net = gr.Slider(0, N_SAMPLES - 1, value=0, step=1, label="Muestra de test") | |
| horizon_slider = gr.Slider(0, HORIZON - 1, value=0, step=1, label="Paso futuro (horizonte)") | |
| net_plot = gr.Plot() | |
| sample_slider_net.change(plot_network, [sample_slider_net, horizon_slider], net_plot) | |
| horizon_slider.change(plot_network, [sample_slider_net, horizon_slider], net_plot) | |
| demo.load(plot_network, [sample_slider_net, horizon_slider], net_plot) | |
| with gr.Tab("Detalle por sensor"): | |
| with gr.Row(): | |
| sensor_slider = gr.Slider(0, NUM_NODES - 1, value=0, step=1, label="ID de sensor") | |
| sample_slider_det = gr.Slider(0, N_SAMPLES - 1, value=0, step=1, label="Muestra de test") | |
| detail_plot = gr.Plot() | |
| metrics_out = gr.Markdown() | |
| sensor_slider.change(plot_sensor_detail, [sensor_slider, sample_slider_det], [detail_plot, metrics_out]) | |
| sample_slider_det.change(plot_sensor_detail, [sensor_slider, sample_slider_det], [detail_plot, metrics_out]) | |
| demo.load(plot_sensor_detail, [sensor_slider, sample_slider_det], [detail_plot, metrics_out]) | |
| if __name__ == "__main__": | |
| demo.launch() |