You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

146 regels
6.5 KiB

  1. import os
  2. import json
  3. import streamlit as st
  4. import pandas as pd
  5. from PIL import Image, ImageDraw
  6. from pathlib import Path
  7. from streamlit_drawable_canvas import st_canvas
  8. import time
  9. def show_mapper(cfg):
  10. # --- 1. CONFIGURAZIONE PERCORSI ---
  11. MAPS_DIR = Path(cfg['maps']['map_dir'])
  12. SAMPLES_DIR = Path(cfg['train']['samples_dir'])
  13. PENDING_DIR = Path(cfg['collect_train']['jobs_dir']) / "pending"
  14. BEACONS_FILE = "/data/config/beacons.csv"
  15. [p.mkdir(parents=True, exist_ok=True) for p in [MAPS_DIR, SAMPLES_DIR, PENDING_DIR]]
  16. def load_map_metadata(f_id):
  17. meta_path = MAPS_DIR / f"{cfg['maps']['meta_prefix']}{f_id}.json"
  18. defaults = {"pixel_ratio": 1.0, "calibrated": False, "origin": [0, 0], "grid_size": 50}
  19. if meta_path.exists():
  20. with open(meta_path, "r") as f: return {**defaults, **json.load(f)}
  21. return defaults
  22. st.subheader("🗺️ Gestione Mappatura e Rilevamento")
  23. # Selezione Piano
  24. maps = sorted([f.replace(cfg['maps']['floor_prefix'], "").split('.')[0] for f in os.listdir(MAPS_DIR) if f.startswith(cfg['maps']['floor_prefix'])])
  25. if not maps: st.error("Nessuna mappa trovata."); return
  26. floor_id = st.selectbox("Seleziona Piano (Z):", maps)
  27. meta = load_map_metadata(floor_id)
  28. # --- 2. STATO SISTEMA ---
  29. c_s1, c_s2 = st.columns(2)
  30. with c_s1:
  31. st.info(f"📏 Scala: {'✅' if meta['calibrated'] else '❌'} ({meta['pixel_ratio']:.4f} px/cm)")
  32. with c_s2:
  33. st.info(f"🎯 Origine: {'✅' if meta['origin'] != [0,0] else '❌'} (X:{meta['origin'][0]}, Y:{meta['origin'][1]})")
  34. # --- 3. CONTROLLI VISUALIZZAZIONE ---
  35. with st.expander("🎨 Opzioni Visualizzazione", expanded=True):
  36. c_v1, c_v2, c_v3 = st.columns(3)
  37. zoom = c_v1.slider("🔍 Zoom Mappa", 0.1, 4.0, 1.0, 0.1)
  38. dot_size = c_v2.slider("🔵/🟢 Dimensione Punti", 10, 100, 40)
  39. mode = c_v3.radio("Modalità Interazione:", ["🖐️ NAVIGA", "🎯 AZIONE"], horizontal=True)
  40. # --- 4. SELEZIONE STRUMENTO ---
  41. t1, t2, t3 = st.columns(3)
  42. if t1.button("📏 IMPOSTA SCALA", use_container_width=True): st.session_state.map_tool = "Calibra"
  43. if t2.button("🎯 SET ORIGINE", use_container_width=True): st.session_state.map_tool = "Origine"
  44. is_ready = meta.get("calibrated", False) and meta["origin"] != [0, 0]
  45. if t3.button("📡 RILEVA", use_container_width=True, disabled=not is_ready): st.session_state.map_tool = "Rileva"
  46. tool = st.session_state.get('map_tool', 'Rileva')
  47. st.write(f"Strumento attivo: **{tool}**")
  48. # --- 5. PREPARAZIONE IMMAGINE E STORICO ---
  49. img_path = MAPS_DIR / f"{cfg['maps']['floor_prefix']}{floor_id}.png"
  50. if not img_path.exists(): img_path = MAPS_DIR / f"{cfg['maps']['floor_prefix']}{floor_id}.jpg"
  51. img = Image.open(img_path).convert("RGBA")
  52. draw = ImageDraw.Draw(img)
  53. if is_ready:
  54. samples_files = list(SAMPLES_DIR.glob(f"{floor_id}_*.csv"))
  55. pending_files = list(PENDING_DIR.glob(f"{floor_id}_*.csv"))
  56. def draw_points(files, color):
  57. for f in files:
  58. try:
  59. p = f.stem.split("_")
  60. px = (int(p[1]) * meta["pixel_ratio"]) + meta["origin"][0]
  61. py = (int(p[2]) * meta["pixel_ratio"]) + meta["origin"][1]
  62. r = dot_size // 2
  63. draw.ellipse([px-r, py-r, px+r, py+r], fill=color, outline="white", width=2)
  64. except: continue
  65. draw_points(samples_files, "#228B22") # Verde: Completati
  66. draw_points(pending_files, "#0000FF") # Blu: In corso
  67. # --- 6. CANVAS ---
  68. d_mode = "transform" if mode == "🖐️ NAVIGA" else ("line" if tool == "Calibra" else "point")
  69. canvas_result = st_canvas(
  70. background_image=img,
  71. height=int(img.size[1] * zoom),
  72. width=int(img.size[0] * zoom),
  73. drawing_mode=d_mode,
  74. display_toolbar=True,
  75. update_streamlit=True,
  76. key=f"canvas_v10_{floor_id}_{zoom}_{mode}_{tool}",
  77. )
  78. # --- 7. LOGICA AZIONI E SALVATAGGIO CSV ---
  79. if mode == "🎯 AZIONE" and canvas_result.json_data and canvas_result.json_data["objects"]:
  80. last = canvas_result.json_data["objects"][-1]
  81. raw_x, raw_y = last["left"] / zoom, last["top"] / zoom
  82. if tool == "Calibra" and last["type"] == "line":
  83. px_dist = ((last["x2"] - last["x1"])**2 + (last["y2"] - last["y1"])**2)**0.5 / zoom
  84. dist_cm = st.number_input("Distanza reale (cm):", value=100.0)
  85. if st.button("APPLICA SCALA"):
  86. meta.update({"pixel_ratio": px_dist / dist_cm, "calibrated": True})
  87. with open(MAPS_DIR / f"{cfg['maps']['meta_prefix']}{floor_id}.json", "w") as f:
  88. json.dump(meta, f)
  89. st.rerun()
  90. elif tool == "Origine":
  91. if st.button(f"Fissa Origine qui: {int(raw_x)}, {int(raw_y)}"):
  92. meta["origin"] = [int(raw_x), int(raw_y)]
  93. with open(MAPS_DIR / f"{cfg['maps']['meta_prefix']}{floor_id}.json", "w") as f:
  94. json.dump(meta, f)
  95. st.rerun()
  96. elif tool == "Rileva" and is_ready:
  97. dx = (raw_x - meta["origin"][0]) / meta["pixel_ratio"]
  98. dy = (raw_y - meta["origin"][1]) / meta["pixel_ratio"]
  99. grid = meta.get("grid_size", 50)
  100. sx, sy = int(round(dx/grid)*grid), int(round(dy/grid)*grid)
  101. st.write(f"### 📍 Punto: {sx}cm, {sy}cm")
  102. if os.path.exists(BEACONS_FILE):
  103. b_df = pd.read_csv(BEACONS_FILE, sep=";")
  104. sel_b = st.selectbox("Seleziona Beacon:", b_df.apply(lambda x: f"{x['BeaconName']} | {x['MAC']}", axis=1))
  105. if st.button("🚀 REGISTRA LETTURA", type="primary", use_container_width=True):
  106. b_name, b_mac = sel_b.split(" | ")
  107. fname = f"{floor_id}_{sx}_{sy}.csv"
  108. # --- FIX SINTASSI CSV ---
  109. # Formato: Position;Floor;RoomName;X;Y;Z;BeaconName;MAC
  110. data_out = {
  111. "Position": f"P_{sx}_{sy}",
  112. "Floor": floor_id,
  113. "RoomName": "Area_Rilevazione",
  114. "X": sx,
  115. "Y": sy,
  116. "Z": 0,
  117. "BeaconName": b_name,
  118. "MAC": b_mac
  119. }
  120. pd.DataFrame([data_out]).to_csv(PENDING_DIR / fname, index=False, sep=";")
  121. st.toast(f"Punto Blu registrato!")
  122. time.sleep(1); st.rerun()