Update app.py
Browse files
app.py
CHANGED
|
@@ -4,21 +4,20 @@ import numpy as np
|
|
| 4 |
from PIL import Image
|
| 5 |
import os
|
| 6 |
from huggingface_hub import hf_hub_download
|
| 7 |
-
|
| 8 |
import logic
|
| 9 |
|
| 10 |
-
# --- CONFIGURACIÓN INICIAL DE LA PÁGINA ---
|
| 11 |
-
st.set_page_config(page_title="DesignIA - Recomendador de Muebles inteligente", layout="wide")
|
| 12 |
-
|
| 13 |
# --- CONSTANTES Y RUTAS DE RECURSOS ---
|
| 14 |
csv_path = "data/furniture_data.csv"
|
| 15 |
-
cache_path = "vectores_cache.pkl"
|
| 16 |
|
| 17 |
-
# --- CONSTANTES DE DESCARGA DE MODELOS
|
| 18 |
MODEL_REPO_ID = "agerhund/DesignIA_models"
|
| 19 |
MODEL_FILE_BERT = "bert_style_encoder.pth"
|
| 20 |
MODEL_FILE_HORIZON = "horizonnet_model.pth"
|
| 21 |
|
|
|
|
|
|
|
|
|
|
| 22 |
# --- CARGAR ESTILOS CSS ---
|
| 23 |
def cargar_estilo():
|
| 24 |
"""Define y aplica estilos CSS para la UI de Streamlit."""
|
|
@@ -51,7 +50,7 @@ def cargar_estilo():
|
|
| 51 |
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
| 52 |
margin-bottom: 15px;
|
| 53 |
border: 1px solid #eee;
|
| 54 |
-
color: black !important;
|
| 55 |
}
|
| 56 |
</style>
|
| 57 |
""", unsafe_allow_html=True)
|
|
@@ -64,8 +63,8 @@ if 'room_data' not in st.session_state: st.session_state.room_data = None
|
|
| 64 |
if 'muebles_df' not in st.session_state: st.session_state.muebles_df = None
|
| 65 |
if 'data_manager' not in st.session_state: st.session_state.data_manager = None
|
| 66 |
if 'horizon_model_path' not in st.session_state: st.session_state.horizon_model_path = None
|
| 67 |
-
if '
|
| 68 |
-
if '
|
| 69 |
|
| 70 |
|
| 71 |
# --- FUNCIONES DE CARGA CON CACHE ---
|
|
@@ -75,10 +74,7 @@ def download_models():
|
|
| 75 |
"""Descarga ambos modelos grandes desde el Model Repository y devuelve las rutas locales temporales."""
|
| 76 |
st.info("Descargando modelos grandes desde Hugging Face Hub (¡Solo la primera vez!)...")
|
| 77 |
|
| 78 |
-
# 1. Descarga del modelo BERT/Style (para DataManager)
|
| 79 |
bert_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=MODEL_FILE_BERT)
|
| 80 |
-
|
| 81 |
-
# 2. Descarga del modelo HorizonNet (para RoomLayoutDetector)
|
| 82 |
horizon_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=MODEL_FILE_HORIZON)
|
| 83 |
|
| 84 |
return bert_path, horizon_path
|
|
@@ -86,7 +82,6 @@ def download_models():
|
|
| 86 |
@st.cache_resource
|
| 87 |
def init_backend(csv, cache, bert_model_path):
|
| 88 |
"""Inicializa DataManager y carga el DataFrame de muebles, usando la ruta local del modelo BERT."""
|
| 89 |
-
# El DataManager (logic.py) utiliza la ruta del modelo BERT para sus operaciones.
|
| 90 |
dm = logic.DataManager(csv, cache, bert_model_path)
|
| 91 |
df = dm.cargar_datos()
|
| 92 |
return dm, df
|
|
@@ -95,15 +90,12 @@ def init_backend(csv, cache, bert_model_path):
|
|
| 95 |
try:
|
| 96 |
if st.session_state.data_manager is None:
|
| 97 |
|
| 98 |
-
# 1. Descarga los modelos y obtiene las rutas locales temporales
|
| 99 |
bert_path_downloaded, horizon_path_downloaded = download_models()
|
| 100 |
|
| 101 |
with st.spinner("Cargando base de datos de muebles y modelos IA..."):
|
| 102 |
|
| 103 |
-
# 2. Inicializa el DataManager (que solo necesita el modelo BERT)
|
| 104 |
dm, df = init_backend(csv_path, cache_path, bert_path_downloaded)
|
| 105 |
|
| 106 |
-
# 3. Guarda todos los objetos y la RUTA DE HORIZONNET para el PASO 1
|
| 107 |
st.session_state.data_manager = dm
|
| 108 |
st.session_state.muebles_df = df
|
| 109 |
st.session_state.horizon_model_path = horizon_path_downloaded
|
|
@@ -115,7 +107,7 @@ except Exception as e:
|
|
| 115 |
st.stop()
|
| 116 |
|
| 117 |
|
| 118 |
-
# --- SIDEBAR: INFORMACIÓN DEL PROYECTO ---
|
| 119 |
with st.sidebar:
|
| 120 |
st.title("DesignIA - Asistente de Diseño")
|
| 121 |
st.markdown("---")
|
|
@@ -126,7 +118,7 @@ with st.sidebar:
|
|
| 126 |
st.markdown("Desarrollado por **Andrés Gerlotti Slusnys**")
|
| 127 |
st.markdown("© 2025")
|
| 128 |
|
| 129 |
-
# Indicador de estado
|
| 130 |
with st.expander("Estado del Sistema", expanded=False):
|
| 131 |
st.success("Motor Gráfico: Activo")
|
| 132 |
st.success("Modelo NLP (BERT): Cargado")
|
|
@@ -134,43 +126,9 @@ with st.sidebar:
|
|
| 134 |
st.success("HorizonNet: Conectado (Remoto)")
|
| 135 |
else:
|
| 136 |
st.warning("HorizonNet: Pendiente de descarga/inicialización")
|
| 137 |
-
|
| 138 |
-
# Sección de Ejemplos
|
| 139 |
-
st.markdown("---")
|
| 140 |
-
st.subheader("📸 Imágenes de Ejemplo")
|
| 141 |
-
st.caption("Descarga estas imágenes para probar la app:")
|
| 142 |
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
example_files = [f for f in os.listdir(examples_dir) if f.endswith(('.jpg', '.png'))]
|
| 146 |
-
example_files.sort()
|
| 147 |
-
|
| 148 |
-
# Guardar el nombre del archivo seleccionado en la sesión
|
| 149 |
-
selected_example = st.selectbox("Selecciona un ejemplo:", example_files, key='example_select_box')
|
| 150 |
-
|
| 151 |
-
if selected_example:
|
| 152 |
-
file_path = os.path.join(examples_dir, selected_example)
|
| 153 |
-
|
| 154 |
-
# 🛑 Corrección: Guardar la ruta y el nombre del ejemplo seleccionado en session_state
|
| 155 |
-
st.session_state.selected_example_path = file_path
|
| 156 |
-
st.session_state.selected_example_name = selected_example
|
| 157 |
-
|
| 158 |
-
with open(file_path, "rb") as file:
|
| 159 |
-
btn = st.download_button(
|
| 160 |
-
label="⬇️ Descargar Imagen",
|
| 161 |
-
data=file,
|
| 162 |
-
file_name=selected_example,
|
| 163 |
-
mime="image/jpeg"
|
| 164 |
-
)
|
| 165 |
-
|
| 166 |
-
# Mostrar miniatura
|
| 167 |
-
st.image(file_path, caption="Vista previa", use_container_width=True)
|
| 168 |
-
else:
|
| 169 |
-
# Limpiar la ruta si no hay nada seleccionado
|
| 170 |
-
if 'selected_example_path' in st.session_state:
|
| 171 |
-
del st.session_state.selected_example_path
|
| 172 |
-
else:
|
| 173 |
-
st.info("No hay ejemplos disponibles.")
|
| 174 |
|
| 175 |
|
| 176 |
# --- INTERFAZ PRINCIPAL ---
|
|
@@ -180,49 +138,95 @@ st.markdown("Sube una panorámica, detecta el espacio y obtén el diseño ideal
|
|
| 180 |
# --- PASO 1: CARGA DE IMAGEN Y DETECCIÓN ---
|
| 181 |
st.header("1. Escaneo de Habitación")
|
| 182 |
|
| 183 |
-
#
|
| 184 |
-
|
| 185 |
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
# Determinar qué archivo usar (la carga manual tiene prioridad)
|
|
|
|
|
|
|
|
|
|
| 191 |
if uploaded_file is not None:
|
| 192 |
source_file = uploaded_file
|
| 193 |
file_caption = 'Imagen subida'
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
source_file =
|
| 197 |
-
file_caption = f'Imagen de ejemplo: {
|
| 198 |
else:
|
| 199 |
source_file = None
|
| 200 |
|
| 201 |
|
| 202 |
if source_file is not None:
|
| 203 |
-
|
| 204 |
-
if
|
| 205 |
image = Image.open(source_file)
|
| 206 |
else:
|
| 207 |
image = Image.open(source_file)
|
| 208 |
|
| 209 |
-
|
|
|
|
|
|
|
| 210 |
|
| 211 |
if st.button("Analizar la habitación"):
|
| 212 |
with st.spinner("Detectando la geometría..."):
|
| 213 |
|
| 214 |
-
|
|
|
|
| 215 |
temp_file_path = source_file
|
| 216 |
else:
|
| 217 |
temp_file_path = "temp_pano.jpg"
|
| 218 |
with open(temp_file_path, "wb") as f:
|
| 219 |
f.write(source_file.getbuffer())
|
| 220 |
-
|
| 221 |
|
| 222 |
|
| 223 |
# Instanciar y ejecutar el detector de layout (HorizonNet)
|
| 224 |
try:
|
| 225 |
-
# 🛑 USAR LA RUTA TEMPORAL GUARDADA EN session_state
|
| 226 |
detector = logic.RoomLayoutDetector(st.session_state.horizon_model_path)
|
| 227 |
room_data = detector.detect_layout(temp_file_path)
|
| 228 |
|
|
@@ -394,7 +398,6 @@ if st.session_state.stage >= 2:
|
|
| 394 |
st.error("No se pudo generar una distribución válida para este espacio (demasiado pequeño o muchos obstáculos).")
|
| 395 |
else:
|
| 396 |
# 5. Recomendar productos (Knapsack para optimización de precio/estilo)
|
| 397 |
-
# Las 'constraints' se definen por los muebles que el layout PUDO colocar
|
| 398 |
best_combo = recommender.buscar_combinacion(constraints, presupuesto, top_n=1)
|
| 399 |
|
| 400 |
if not best_combo:
|
|
@@ -411,7 +414,7 @@ if st.session_state.stage == 3:
|
|
| 411 |
st.divider()
|
| 412 |
st.header("Tu salón ideal")
|
| 413 |
|
| 414 |
-
# --- VISUALIZACIÓN 3D Interactiva ---
|
| 415 |
st.subheader("Visualización 3D Interactiva")
|
| 416 |
|
| 417 |
# Generar la figura 3D
|
|
|
|
| 4 |
from PIL import Image
|
| 5 |
import os
|
| 6 |
from huggingface_hub import hf_hub_download
|
|
|
|
| 7 |
import logic
|
| 8 |
|
|
|
|
|
|
|
|
|
|
| 9 |
# --- CONSTANTES Y RUTAS DE RECURSOS ---
|
| 10 |
csv_path = "data/furniture_data.csv"
|
| 11 |
+
cache_path = "vectores_cache.pkl"
|
| 12 |
|
| 13 |
+
# --- CONSTANTES DE DESCARGA DE MODELOS ---
|
| 14 |
MODEL_REPO_ID = "agerhund/DesignIA_models"
|
| 15 |
MODEL_FILE_BERT = "bert_style_encoder.pth"
|
| 16 |
MODEL_FILE_HORIZON = "horizonnet_model.pth"
|
| 17 |
|
| 18 |
+
# --- CONFIGURACIÓN INICIAL DE LA PÁGINA ---
|
| 19 |
+
st.set_page_config(page_title="DesignIA - Recomendador de Muebles inteligente", layout="wide")
|
| 20 |
+
|
| 21 |
# --- CARGAR ESTILOS CSS ---
|
| 22 |
def cargar_estilo():
|
| 23 |
"""Define y aplica estilos CSS para la UI de Streamlit."""
|
|
|
|
| 50 |
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
| 51 |
margin-bottom: 15px;
|
| 52 |
border: 1px solid #eee;
|
| 53 |
+
color: black !important;
|
| 54 |
}
|
| 55 |
</style>
|
| 56 |
""", unsafe_allow_html=True)
|
|
|
|
| 63 |
if 'muebles_df' not in st.session_state: st.session_state.muebles_df = None
|
| 64 |
if 'data_manager' not in st.session_state: st.session_state.data_manager = None
|
| 65 |
if 'horizon_model_path' not in st.session_state: st.session_state.horizon_model_path = None
|
| 66 |
+
if 'source_file_path' not in st.session_state: st.session_state.source_file_path = None
|
| 67 |
+
if 'is_example' not in st.session_state: st.session_state.is_example = False
|
| 68 |
|
| 69 |
|
| 70 |
# --- FUNCIONES DE CARGA CON CACHE ---
|
|
|
|
| 74 |
"""Descarga ambos modelos grandes desde el Model Repository y devuelve las rutas locales temporales."""
|
| 75 |
st.info("Descargando modelos grandes desde Hugging Face Hub (¡Solo la primera vez!)...")
|
| 76 |
|
|
|
|
| 77 |
bert_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=MODEL_FILE_BERT)
|
|
|
|
|
|
|
| 78 |
horizon_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=MODEL_FILE_HORIZON)
|
| 79 |
|
| 80 |
return bert_path, horizon_path
|
|
|
|
| 82 |
@st.cache_resource
|
| 83 |
def init_backend(csv, cache, bert_model_path):
|
| 84 |
"""Inicializa DataManager y carga el DataFrame de muebles, usando la ruta local del modelo BERT."""
|
|
|
|
| 85 |
dm = logic.DataManager(csv, cache, bert_model_path)
|
| 86 |
df = dm.cargar_datos()
|
| 87 |
return dm, df
|
|
|
|
| 90 |
try:
|
| 91 |
if st.session_state.data_manager is None:
|
| 92 |
|
|
|
|
| 93 |
bert_path_downloaded, horizon_path_downloaded = download_models()
|
| 94 |
|
| 95 |
with st.spinner("Cargando base de datos de muebles y modelos IA..."):
|
| 96 |
|
|
|
|
| 97 |
dm, df = init_backend(csv_path, cache_path, bert_path_downloaded)
|
| 98 |
|
|
|
|
| 99 |
st.session_state.data_manager = dm
|
| 100 |
st.session_state.muebles_df = df
|
| 101 |
st.session_state.horizon_model_path = horizon_path_downloaded
|
|
|
|
| 107 |
st.stop()
|
| 108 |
|
| 109 |
|
| 110 |
+
# --- SIDEBAR: INFORMACIÓN DEL PROYECTO (SOLO INFORMACIÓN ESTÁTICA) ---
|
| 111 |
with st.sidebar:
|
| 112 |
st.title("DesignIA - Asistente de Diseño")
|
| 113 |
st.markdown("---")
|
|
|
|
| 118 |
st.markdown("Desarrollado por **Andrés Gerlotti Slusnys**")
|
| 119 |
st.markdown("© 2025")
|
| 120 |
|
| 121 |
+
# Indicador de estado
|
| 122 |
with st.expander("Estado del Sistema", expanded=False):
|
| 123 |
st.success("Motor Gráfico: Activo")
|
| 124 |
st.success("Modelo NLP (BERT): Cargado")
|
|
|
|
| 126 |
st.success("HorizonNet: Conectado (Remoto)")
|
| 127 |
else:
|
| 128 |
st.warning("HorizonNet: Pendiente de descarga/inicialización")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
+
st.markdown("---")
|
| 131 |
+
st.info("El selector de imágenes de ejemplo se encuentra en el Paso 1 de la página principal.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
|
| 134 |
# --- INTERFAZ PRINCIPAL ---
|
|
|
|
| 138 |
# --- PASO 1: CARGA DE IMAGEN Y DETECCIÓN ---
|
| 139 |
st.header("1. Escaneo de Habitación")
|
| 140 |
|
| 141 |
+
# --- LÓGICA DE SELECCIÓN DE EJEMPLOS EN EL CUERPO PRINCIPAL ---
|
| 142 |
+
examples_dir = os.path.join(os.path.dirname(__file__), "examples")
|
| 143 |
|
| 144 |
+
if os.path.exists(examples_dir):
|
| 145 |
+
example_files = [f for f in os.listdir(examples_dir) if f.endswith(('.jpg', '.png'))]
|
| 146 |
+
example_files.sort()
|
| 147 |
+
|
| 148 |
+
col_select, col_download = st.columns([3, 1])
|
| 149 |
+
|
| 150 |
+
with col_select:
|
| 151 |
+
# Añadimos la opción de carga manual como la principal
|
| 152 |
+
display_files = ["--- Cargar imagen manualmente (prioridad) ---"] + example_files
|
| 153 |
+
|
| 154 |
+
selected_example_name = st.selectbox(
|
| 155 |
+
"O usa una imagen de ejemplo:",
|
| 156 |
+
display_files,
|
| 157 |
+
key='example_select_box'
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
# Manejo de la selección del ejemplo
|
| 161 |
+
if selected_example_name != display_files[0]:
|
| 162 |
+
file_path = os.path.join(examples_dir, selected_example_name)
|
| 163 |
+
st.session_state.source_file_path = file_path
|
| 164 |
+
st.session_state.is_example = True
|
| 165 |
+
|
| 166 |
+
with col_download:
|
| 167 |
+
st.markdown(" ")
|
| 168 |
+
with open(file_path, "rb") as file:
|
| 169 |
+
st.download_button(
|
| 170 |
+
label=f"⬇️ Descargar: {selected_example_name}",
|
| 171 |
+
data=file,
|
| 172 |
+
file_name=selected_example_name,
|
| 173 |
+
mime="image/jpeg"
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
# Mostrar miniatura del ejemplo
|
| 177 |
+
st.image(file_path, caption="Vista previa del ejemplo", use_container_width=True)
|
| 178 |
+
|
| 179 |
+
else:
|
| 180 |
+
# Limpiar las variables si se selecciona la opción nula
|
| 181 |
+
if 'source_file_path' in st.session_state:
|
| 182 |
+
del st.session_state.source_file_path
|
| 183 |
+
st.session_state.is_example = False
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
# 1. Selector de carga manual (Se mantiene aquí, pero solo para subir el archivo)
|
| 187 |
+
uploaded_file = st.file_uploader("Sube tu imagen panorámica (360)", type=['jpg', 'png', 'jpeg'])
|
| 188 |
|
| 189 |
# Determinar qué archivo usar (la carga manual tiene prioridad)
|
| 190 |
+
source_file_path = st.session_state.get('source_file_path', None)
|
| 191 |
+
is_example = st.session_state.get('is_example', False)
|
| 192 |
+
|
| 193 |
if uploaded_file is not None:
|
| 194 |
source_file = uploaded_file
|
| 195 |
file_caption = 'Imagen subida'
|
| 196 |
+
is_example = False
|
| 197 |
+
elif source_file_path is not None and is_example == True:
|
| 198 |
+
source_file = source_file_path
|
| 199 |
+
file_caption = f'Imagen de ejemplo: {source_file_path.split(os.sep)[-1]}'
|
| 200 |
else:
|
| 201 |
source_file = None
|
| 202 |
|
| 203 |
|
| 204 |
if source_file is not None:
|
| 205 |
+
# Abrir la imagen
|
| 206 |
+
if is_example:
|
| 207 |
image = Image.open(source_file)
|
| 208 |
else:
|
| 209 |
image = Image.open(source_file)
|
| 210 |
|
| 211 |
+
# Mostrar la imagen subida (solo si es subida, ya que el ejemplo se mostró arriba)
|
| 212 |
+
if uploaded_file is not None:
|
| 213 |
+
st.image(image, caption=file_caption, use_container_width=True)
|
| 214 |
|
| 215 |
if st.button("Analizar la habitación"):
|
| 216 |
with st.spinner("Detectando la geometría..."):
|
| 217 |
|
| 218 |
+
# --- MANEJO DEL ARCHIVO TEMPORAL PARA EL DETECTOR ---
|
| 219 |
+
if is_example:
|
| 220 |
temp_file_path = source_file
|
| 221 |
else:
|
| 222 |
temp_file_path = "temp_pano.jpg"
|
| 223 |
with open(temp_file_path, "wb") as f:
|
| 224 |
f.write(source_file.getbuffer())
|
| 225 |
+
# --- FIN MANEJO ARCHIVO TEMPORAL ---
|
| 226 |
|
| 227 |
|
| 228 |
# Instanciar y ejecutar el detector de layout (HorizonNet)
|
| 229 |
try:
|
|
|
|
| 230 |
detector = logic.RoomLayoutDetector(st.session_state.horizon_model_path)
|
| 231 |
room_data = detector.detect_layout(temp_file_path)
|
| 232 |
|
|
|
|
| 398 |
st.error("No se pudo generar una distribución válida para este espacio (demasiado pequeño o muchos obstáculos).")
|
| 399 |
else:
|
| 400 |
# 5. Recomendar productos (Knapsack para optimización de precio/estilo)
|
|
|
|
| 401 |
best_combo = recommender.buscar_combinacion(constraints, presupuesto, top_n=1)
|
| 402 |
|
| 403 |
if not best_combo:
|
|
|
|
| 414 |
st.divider()
|
| 415 |
st.header("Tu salón ideal")
|
| 416 |
|
| 417 |
+
# --- VISUALIZACIÓN 3D Interactiva (Plotly) ---
|
| 418 |
st.subheader("Visualización 3D Interactiva")
|
| 419 |
|
| 420 |
# Generar la figura 3D
|