import streamlit as st import pandas as pd import os import joblib import numpy as np import math import folium import json import base64 from io import BytesIO from pathlib import Path from PIL import Image from streamlit_folium import st_folium # --- UTILS --- @st.cache_data def get_image_base64(img_path): img = Image.open(img_path).convert("RGBA") w, h = img.size buffered = BytesIO() img.save(buffered, format="PNG") img_str = base64.b64encode(buffered.getvalue()).decode("ascii") return f"data:image/png;base64,{img_str}", w, h def calculate_error(z_real, x_real, y_real, z_pred, x_pred, y_pred): z_err = abs(z_real - z_pred) dist_err = math.sqrt((x_real - x_pred)**2 + (y_real - y_pred)**2) return z_err, dist_err def show_test_inference(cfg): st.subheader("๐Ÿงช Test Inferenza Offline") MODEL_DIR = Path("/data/model") TEST_SAMPLES_DIR = Path("/data/train/testsamples") MAPS_DIR = Path("/data/maps") available_models = sorted([m.name for m in MODEL_DIR.glob("model_camp_*.joblib")], reverse=True) test_files = sorted([f.name for f in TEST_SAMPLES_DIR.glob("*.csv")]) if not available_models or not test_files: st.warning("Verifica la presenza di modelli e campioni di test.") return col1, col2 = st.columns(2) selected_model = col1.selectbox("๐ŸŽฏ Seleziona Modello:", available_models) selected_test = col2.selectbox("๐Ÿ“„ Seleziona Fingerprint:", test_files) if "test_results" not in st.session_state: st.session_state.test_results = None if st.button("๐Ÿš€ ESEGUI TEST DI PRECISIONE", type="primary", use_container_width=True): try: m_pkg = joblib.load(MODEL_DIR / selected_model) delim = cfg.get('paths', {}).get('csv_delimiter', ';') df_test = pd.read_csv(TEST_SAMPLES_DIR / selected_test, sep=delim) row = df_test.iloc[0] z_real, x_real, y_real = int(round(float(row['z']))), float(row['x']), float(row['y']) gws = m_pkg['gateways_order'] fill = m_pkg.get('nan_fill', -110.0) # --- DEBUG VETTORE INPUT --- raw_vals = [] for gw in gws: val = row.get(gw) raw_vals.append(float(val) if val is not None and not pd.isna(val) else fill) X_in = np.array([raw_vals]) # Visualizzazione Debug in UI per l'operatore with st.expander("๐Ÿ” Analisi Vettore di Input (Fingerprint vs Modello)"): debug_df = pd.DataFrame({ "Gateway MAC": gws, "RSSI Letto": [row.get(gw, "NON TROVATO") for gw in gws], "RSSI Finale (Input AI)": raw_vals }) st.dataframe(debug_df) match_count = np.sum(X_in[0] > fill) st.write(f"**Gateway Corrispondenti:** {match_count} su {len(gws)}") if match_count == 0: st.error("ERRORE: Il file di test non contiene nessuno dei Gateway usati per l'addestramento!") # Predizione z_pred = int(m_pkg['floor_clf'].predict(X_in)[0]) x_pred, y_pred = -1.0, -1.0 if z_pred in m_pkg['xy_by_floor']: xy = m_pkg['xy_by_floor'][z_pred].predict(X_in)[0] x_pred, y_pred = float(xy[0]), float(xy[1]) z_err, dist_err = calculate_error(z_real, x_real, y_real, z_pred, x_pred, y_pred) st.session_state.test_results = { "z_real": z_real, "x_real": x_real, "y_real": y_real, "z_pred": z_pred, "x_pred": x_pred, "y_pred": y_pred, "z_err": z_err, "dist_err": dist_err, "model_used": selected_model } except Exception as e: st.error(f"Errore durante l'inferenza: {e}") # --- VISUALIZZAZIONE GRAFICA DEI RISULTATI --- if st.session_state.test_results: res = st.session_state.test_results c_info1, c_info2 = st.columns(2) with c_info1: st.info(f"๐Ÿ“ **Test Reale** | Piano: {res['z_real']} | X: **{res['x_real']}** | Y: **{res['y_real']}**") with c_info2: st.success(f"๐Ÿ”ฎ **Predizione** | Piano: {res['z_pred']} | X: **{round(res['x_pred'],1)}** | Y: **{round(res['y_pred'],1)}**") st.markdown(f"""
๐Ÿ“ Legenda Mappa
โ— PUNTO DI TEST: Posizione reale del rilievo.
โ— PUNTO PREDETTO: Posizione calcolata dal modello AI.
--- LINEA GIALLA: Scostamento di {round(res['dist_err'], 2)} px.
""", unsafe_allow_html=True) img_filename = f"floor_{res['z_pred']}.png" meta_filename = f"meta_{res['z_pred']}.json" img_p = MAPS_DIR / img_filename meta_p = MAPS_DIR / meta_filename if img_p.exists() and meta_p.exists(): with open(meta_p, "r") as f: meta = json.load(f) img_data, w, h = get_image_base64(img_p) bounds = [[0, 0], [h, w]] m = folium.Map(location=[h/2, w/2], crs="Simple", tiles=None, zoom_start=0, attribution_control=False) m.fit_bounds(bounds) m.options.update({"minZoom": -5, "maxZoom": 5, "maxBounds": bounds, "zoomSnap": 0.25}) folium.raster_layers.ImageOverlay(image=img_data, bounds=bounds).add_to(m) if meta.get("calibrated"): def to_px(mx, my): px = (mx * meta["pixel_ratio"]) + meta["origin"][0] py = meta["origin"][1] - (my * meta["pixel_ratio"]) return [py, px] p_real = to_px(res['x_real'], res['y_real']) p_pred = to_px(res['x_pred'], res['y_pred']) # Marker Posizione Reale folium.CircleMarker( location=p_real, radius=11, color="#00838f", fill=True, fill_color="#00bcd4", fill_opacity=0.85, tooltip="PUNTO DI TEST (REALE)" ).add_to(m) # Marker Predizione if res['x_pred'] != -1.0: folium.CircleMarker( location=p_pred, radius=11, color="#e65100", fill=True, fill_color="#ff9800", fill_opacity=0.85, tooltip=f"PREDIZIONE (Modello: {res['model_used']})" ).add_to(m) # Linea di errore (Gialla tratteggiata) folium.PolyLine( locations=[p_real, p_pred], color="#ffeb3b", weight=4, opacity=0.7, dash_array='8' ).add_to(m) st_folium(m, height=700, width=None, key=f"test_map_final", use_container_width=True)