Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

146 wiersze
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()