|
- import os
- import json
- import streamlit as st
- import pandas as pd
- from PIL import Image, ImageDraw
- from pathlib import Path
- from streamlit_drawable_canvas import st_canvas
- import time
-
- def show_mapper(cfg):
- # --- 1. CONFIGURAZIONE PERCORSI ---
- MAPS_DIR = Path(cfg['maps']['map_dir'])
- SAMPLES_DIR = Path(cfg['train']['samples_dir'])
- PENDING_DIR = Path(cfg['collect_train']['jobs_dir']) / "pending"
- BEACONS_FILE = "/data/config/beacons.csv"
-
- [p.mkdir(parents=True, exist_ok=True) for p in [MAPS_DIR, SAMPLES_DIR, PENDING_DIR]]
-
- def load_map_metadata(f_id):
- meta_path = MAPS_DIR / f"{cfg['maps']['meta_prefix']}{f_id}.json"
- defaults = {"pixel_ratio": 1.0, "calibrated": False, "origin": [0, 0], "grid_size": 50}
- if meta_path.exists():
- with open(meta_path, "r") as f: return {**defaults, **json.load(f)}
- return defaults
-
- st.subheader("🗺️ Gestione Mappatura e Rilevamento")
-
- # Selezione Piano
- maps = sorted([f.replace(cfg['maps']['floor_prefix'], "").split('.')[0] for f in os.listdir(MAPS_DIR) if f.startswith(cfg['maps']['floor_prefix'])])
- if not maps: st.error("Nessuna mappa trovata."); return
- floor_id = st.selectbox("Seleziona Piano (Z):", maps)
- meta = load_map_metadata(floor_id)
-
- # --- 2. STATO SISTEMA ---
- c_s1, c_s2 = st.columns(2)
- with c_s1:
- st.info(f"📏 Scala: {'✅' if meta['calibrated'] else '❌'} ({meta['pixel_ratio']:.4f} px/cm)")
- with c_s2:
- st.info(f"🎯 Origine: {'✅' if meta['origin'] != [0,0] else '❌'} (X:{meta['origin'][0]}, Y:{meta['origin'][1]})")
-
- # --- 3. CONTROLLI VISUALIZZAZIONE ---
- with st.expander("🎨 Opzioni Visualizzazione", expanded=True):
- c_v1, c_v2, c_v3 = st.columns(3)
- zoom = c_v1.slider("🔍 Zoom Mappa", 0.1, 4.0, 1.0, 0.1)
- dot_size = c_v2.slider("🔵/🟢 Dimensione Punti", 10, 100, 40)
- mode = c_v3.radio("Modalità Interazione:", ["🖐️ NAVIGA", "🎯 AZIONE"], horizontal=True)
-
- # --- 4. SELEZIONE STRUMENTO ---
- t1, t2, t3 = st.columns(3)
- if t1.button("📏 IMPOSTA SCALA", use_container_width=True): st.session_state.map_tool = "Calibra"
- if t2.button("🎯 SET ORIGINE", use_container_width=True): st.session_state.map_tool = "Origine"
-
- is_ready = meta.get("calibrated", False) and meta["origin"] != [0, 0]
- if t3.button("📡 RILEVA", use_container_width=True, disabled=not is_ready): st.session_state.map_tool = "Rileva"
-
- tool = st.session_state.get('map_tool', 'Rileva')
- st.write(f"Strumento attivo: **{tool}**")
-
- # --- 5. PREPARAZIONE IMMAGINE E STORICO ---
- img_path = MAPS_DIR / f"{cfg['maps']['floor_prefix']}{floor_id}.png"
- if not img_path.exists(): img_path = MAPS_DIR / f"{cfg['maps']['floor_prefix']}{floor_id}.jpg"
- img = Image.open(img_path).convert("RGBA")
- draw = ImageDraw.Draw(img)
-
- if is_ready:
- samples_files = list(SAMPLES_DIR.glob(f"{floor_id}_*.csv"))
- pending_files = list(PENDING_DIR.glob(f"{floor_id}_*.csv"))
-
- def draw_points(files, color):
- for f in files:
- try:
- p = f.stem.split("_")
- px = (int(p[1]) * meta["pixel_ratio"]) + meta["origin"][0]
- py = (int(p[2]) * meta["pixel_ratio"]) + meta["origin"][1]
- r = dot_size // 2
- draw.ellipse([px-r, py-r, px+r, py+r], fill=color, outline="white", width=2)
- except: continue
-
- draw_points(samples_files, "#228B22") # Verde: Completati
- draw_points(pending_files, "#0000FF") # Blu: In corso
-
- # --- 6. CANVAS ---
- d_mode = "transform" if mode == "🖐️ NAVIGA" else ("line" if tool == "Calibra" else "point")
-
- canvas_result = st_canvas(
- background_image=img,
- height=int(img.size[1] * zoom),
- width=int(img.size[0] * zoom),
- drawing_mode=d_mode,
- display_toolbar=True,
- update_streamlit=True,
- key=f"canvas_v10_{floor_id}_{zoom}_{mode}_{tool}",
- )
-
- # --- 7. LOGICA AZIONI E SALVATAGGIO CSV ---
- if mode == "🎯 AZIONE" and canvas_result.json_data and canvas_result.json_data["objects"]:
- last = canvas_result.json_data["objects"][-1]
- raw_x, raw_y = last["left"] / zoom, last["top"] / zoom
-
- if tool == "Calibra" and last["type"] == "line":
- px_dist = ((last["x2"] - last["x1"])**2 + (last["y2"] - last["y1"])**2)**0.5 / zoom
- dist_cm = st.number_input("Distanza reale (cm):", value=100.0)
- if st.button("APPLICA SCALA"):
- meta.update({"pixel_ratio": px_dist / dist_cm, "calibrated": True})
- with open(MAPS_DIR / f"{cfg['maps']['meta_prefix']}{floor_id}.json", "w") as f:
- json.dump(meta, f)
- st.rerun()
-
- elif tool == "Origine":
- if st.button(f"Fissa Origine qui: {int(raw_x)}, {int(raw_y)}"):
- meta["origin"] = [int(raw_x), int(raw_y)]
- with open(MAPS_DIR / f"{cfg['maps']['meta_prefix']}{floor_id}.json", "w") as f:
- json.dump(meta, f)
- st.rerun()
-
- elif tool == "Rileva" and is_ready:
- dx = (raw_x - meta["origin"][0]) / meta["pixel_ratio"]
- dy = (raw_y - meta["origin"][1]) / meta["pixel_ratio"]
- grid = meta.get("grid_size", 50)
- sx, sy = int(round(dx/grid)*grid), int(round(dy/grid)*grid)
-
- st.write(f"### 📍 Punto: {sx}cm, {sy}cm")
-
- if os.path.exists(BEACONS_FILE):
- b_df = pd.read_csv(BEACONS_FILE, sep=";")
- sel_b = st.selectbox("Seleziona Beacon:", b_df.apply(lambda x: f"{x['BeaconName']} | {x['MAC']}", axis=1))
- if st.button("🚀 REGISTRA LETTURA", type="primary", use_container_width=True):
- b_name, b_mac = sel_b.split(" | ")
- fname = f"{floor_id}_{sx}_{sy}.csv"
-
- # --- FIX SINTASSI CSV ---
- # Formato: Position;Floor;RoomName;X;Y;Z;BeaconName;MAC
- data_out = {
- "Position": f"P_{sx}_{sy}",
- "Floor": floor_id,
- "RoomName": "Area_Rilevazione",
- "X": sx,
- "Y": sy,
- "Z": 0,
- "BeaconName": b_name,
- "MAC": b_mac
- }
- pd.DataFrame([data_out]).to_csv(PENDING_DIR / fname, index=False, sep=";")
- st.toast(f"Punto Blu registrato!")
- time.sleep(1); st.rerun()
|