import numpy as np import pandas as pd import statsmodels.formula.api as smf import statsmodels.api as sm import plotly.graph_objects as go from scipy.optimize import minimize import plotly.express as px from scipy.stats import t, f import gradio as gr import io import zipfile import tempfile from datetime import datetime import docx from docx.shared import Inches, Pt from docx.enum.text import WD_PARAGRAPH_ALIGNMENT import os # --- Global output components --- model_completo_output = gr.HTML() pareto_completo_output = gr.Plot() model_simplificado_output = gr.HTML() pareto_simplificado_output = gr.Plot() equation_output = gr.HTML() optimization_table_output = gr.Dataframe(label="Tabla de Optimización", interactive=False) prediction_table_output = gr.Dataframe(label="Tabla de Predicciones", interactive=False) contribution_table_output = gr.Dataframe(label="Tabla de % de Contribución", interactive=False) anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada", interactive=False) download_all_plots_button = gr.DownloadButton("Descargar Todos los Gráficos (ZIP)") download_excel_button = gr.DownloadButton("Descargar Tablas en Excel") rsm_plot_output = gr.Plot() plot_info = gr.Textbox(label="Información del Gráfico", value="Gráfico 1 de 9", interactive=False) current_index_state = gr.State(0) all_figures_state = gr.State([]) current_model_type_state = gr.State('simplified') model_personalized_output = gr.HTML() pareto_personalized_output = gr.Plot() factor_checkboxes = gr.CheckboxGroup(["factors", "x1_sq", "x2_sq", "x3_sq"], label="Términos de Factores", value=["factors", "x1_sq", "x2_sq", "x3_sq"]) interaction_checkboxes = gr.CheckboxGroup(["x1x2", "x1x3", "x2x3"], label="Términos de Interacción") # --- Clase RSM_BoxBehnken --- class RSM_BoxBehnken: def __init__(self, data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels): self.data = data.copy() self.model = None self.model_simplified = None self.model_personalized = None self.optimized_results = None self.optimal_levels = None self.all_figures_full = [] self.all_figures_simplified = [] self.all_figures_personalized = [] self.x1_name = x1_name self.x2_name = x2_name self.x3_name = x3_name self.y_name = y_name self.x1_levels = x1_levels self.x2_levels = x2_levels self.x3_levels = x3_levels def get_levels(self, variable_name): levels = {self.x1_name: self.x1_levels, self.x2_name: self.x2_levels, self.x3_name: self.x3_levels} return levels.get(variable_name) def fit_model(self): formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2) + {self.x1_name}:{self.x2_name} + {self.x1_name}:{self.x3_name} + {self.x2_name}:{self.x3_name}' self.model = smf.ols(formula, data=self.data).fit() return self.model, self.pareto_chart(self.model, "Pareto - Modelo Completo") def fit_simplified_model(self): formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2)' self.model_simplified = smf.ols(formula, data=self.data).fit() return self.model_simplified, self.pareto_chart(self.model_simplified, "Pareto - Modelo Simplificado") def optimize(self, method='Nelder-Mead'): if self.model_simplified is None: return def objective_function(x): return -self.model_simplified.predict(pd.DataFrame({self.x1_name: [x[0]], self.x2_name: [x[1]], self.x3_name: [x[2]]})).values[0] bounds = [(-1, 1), (-1, 1), (-1, 1)] x0 = [0, 0, 0] self.optimized_results = minimize(objective_function, x0, method=method, bounds=bounds) optimal_levels_natural = [self.coded_to_natural(self.optimal_levels[0], self.x1_name), self.coded_to_natural(self.optimal_levels[1], self.x2_name), self.coded_to_natural(self.optimal_levels[2], self.x3_name)] optimization_table = pd.DataFrame({'Variable': [self.x1_name, self.x2_name, self.x3_name], 'Nivel Óptimo (Natural)': optimal_levels_natural, 'Nivel Óptimo (Codificado)': self.optimal_levels}) return optimization_table.round(3) def fit_personalized_model(self, formula): self.model_personalized = smf.ols(formula, data=self.data).fit() return self.model_personalized, self.pareto_chart(self.model_personalized, "Pareto - Modelo Personalizado") def generate_all_plots(self): if self.model_simplified is None: return self.all_figures_full = [] self.all_figures_simplified = [] self.all_figures_personalized = [] levels_to_plot_natural = {self.x1_name: self.x1_levels, self.x2_name: self.x2_levels, self.x3_name: self.x3_levels} for fixed_variable in [self.x1_name, self.x2_name, self.x3_name]: for level in levels_to_plot_natural[fixed_variable]: fig_full = self.plot_rsm_individual(fixed_variable, level, model_type='full') if fig_full: self.all_figures_full.append(fig_full) fig_simplified = self.plot_rsm_individual(fixed_variable, level, model_type='simplified') if fig_simplified: self.all_figures_simplified.append(fig_simplified) if self.model_personalized is not None: fig_personalized = self.plot_rsm_individual(fixed_variable, level, model_type='personalized') if fig_personalized: self.all_figures_personalized.append(fig_personalized) def plot_rsm_individual(self, fixed_variable, fixed_level, model_type='simplified'): model_to_use = self.model_simplified if model_type == 'simplified' else self.model if model_type == 'full' else self.model_personalized if model_to_use is None: return None model_title_suffix = "(Modelo Simplificado)" if model_type == 'simplified' else "(Modelo Completo)" if model_type == 'full' else "(Modelo Personalizado)" varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable] x_natural_levels = self.get_levels(varying_variables[0]) y_natural_levels = self.get_levels(varying_variables[1]) x_range_natural = np.linspace(x_natural_levels[0], x_natural_levels[-1], 100) y_range_natural = np.linspace(y_natural_levels[0], y_natural_levels[-1], 100) x_grid_natural, y_grid_natural = np.meshgrid(x_range_natural, y_range_natural) x_grid_coded = self.natural_to_coded(x_grid_natural, varying_variables[0]) y_grid_coded = self.natural_to_coded(y_grid_natural, varying_variables[1]) prediction_data = pd.DataFrame({varying_variables[0]: x_grid_coded.flatten(), varying_variables[1]: y_grid_coded.flatten()}) prediction_data[fixed_variable] = self.natural_to_coded(fixed_level, fixed_variable) z_pred = model_to_use.predict(prediction_data).values.reshape(x_grid_coded.shape) fixed_level_coded = self.natural_to_coded(fixed_level, fixed_variable) subset_data = self.data[np.isclose(self.data[fixed_variable], fixed_level_coded)] valid_levels = [-1, 0, 1] experiments_data = subset_data[subset_data[varying_variables[0]].isin(valid_levels) & subset_data[varying_variables[1]].isin(valid_levels)] experiments_x_natural = experiments_data[varying_variables[0]].apply(lambda x: self.coded_to_natural(x, varying_variables[0])) experiments_y_natural = experiments_data[varying_variables[1]].apply(lambda x: self.coded_to_natural(x, varying_variables[1])) fig = go.Figure(data=[go.Surface(z=z_pred, x=x_grid_natural, y=y_grid_natural, colorscale='Viridis', opacity=0.7, showscale=True)]) for i in range(x_grid_natural.shape[0]): fig.add_trace(go.Scatter3d(x=x_grid_natural[i, :], y=y_grid_natural[i, :], z=z_pred[i, :], mode='lines', line=dict(color='gray', width=2), showlegend=False, hoverinfo='skip')) for j in range(x_grid_natural.shape[1]): fig.add_trace(go.Scatter3d(x=x_grid_natural[:, j], y=y_grid_natural[:, j], z=z_pred[:, j], mode='lines', line=dict(color='gray', width=2), showlegend=False, hoverinfo='skip')) colors = px.colors.qualitative.Safe point_labels = [f"{row[self.y_name]:.3f}" for _, row in experiments_data.iterrows()] fig.add_trace(go.Scatter3d(x=experiments_x_natural, y=experiments_y_natural, z=experiments_data[self.y_name].round(3), mode='markers+text', marker=dict(size=4, color=colors[:len(experiments_x_natural)]), text=point_labels, textposition='top center', name='Experimentos')) fig.update_layout(scene=dict(xaxis_title=f"{varying_variables[0]} ({self.get_units(varying_variables[0])})", yaxis_title=f"{varying_variables[1]} ({self.get_units(varying_variables[1])})", zaxis_title=self.y_name), title=f"{self.y_name} vs {varying_variables[0]} y {varying_variables[1]}
{fixed_variable} fijo en {fixed_level:.3f} ({self.get_units(fixed_variable)}) {model_title_suffix}", height=800, width=1000, showlegend=True) return fig def get_units(self, variable_name): units = {'Glucosa_g_L': 'g/L', 'Proteina_Pescado_g_L': 'g/L', 'Sulfato_Manganeso_g_L': 'g/L', 'Abs_600nm': ''} return units.get(variable_name, '') def coded_to_natural(self, coded_value, variable_name): levels = self.get_levels(variable_name) return levels[0] + (coded_value + 1) * (levels[-1] - levels[0]) / 2 def natural_to_coded(self, natural_value, variable_name): levels = self.get_levels(variable_name) return -1 + 2 * (natural_value - levels[0]) / (levels[-1] - levels[0]) def pareto_chart(self, model, title): tvalues = model.tvalues[1:] abs_tvalues = np.abs(tvalues) sorted_idx = np.argsort(abs_tvalues)[::-1] sorted_tvalues = abs_tvalues[sorted_idx] sorted_names = tvalues.index[sorted_idx] alpha = 0.05 dof = model.df_resid t_critical = t.ppf(1 - alpha / 2, dof) fig = px.bar(x=sorted_tvalues.round(3), y=sorted_names, orientation='h', labels={'x': 'Efecto Estandarizado', 'y': 'Término'}, title=title) fig.update_yaxes(autorange="reversed") fig.add_vline(x=t_critical, line_dash="dot", annotation_text=f"t crítico = {t_critical:.3f}", annotation_position="bottom right") return fig def get_simplified_equation(self): if self.model_simplified is None: return None coefficients = self.model_simplified.params equation = f"{self.y_name} = {coefficients['Intercept']:.3f}" for term, coef in coefficients.items(): if term != 'Intercept': if term == f'{self.x1_name}': equation += f" + {coef:.3f}*{self.x1_name}" elif term == f'{self.x2_name}': equation += f" + {coef:.3f}*{self.x2_name}" elif term == f'{self.x3_name}': equation += f" + {coef:.3f}*{self.x3_name}" elif term == f'I({self.x1_name} ** 2)': equation += f" + {coef:.3f}*{self.x1_name}^2" elif term == f'I({self.x2_name} ** 2)': equation += f" + {coef:.3f}*{self.x2_name}^2" elif term == f'I({self.x3_name} ** 2)': equation += f" + {coef:.3f}*{self.x3_name}^2" return equation def generate_prediction_table(self): if self.model_simplified is None: return None self.data['Predicho'] = self.model_simplified.predict(self.data) self.data['Residual'] = self.data[self.y_name] - self.data['Predicho'] return self.data[[self.y_name, 'Predicho', 'Residual']].round(3) def calculate_contribution_percentage(self): if self.model_simplified is None: return None anova_table = sm.stats.anova_lm(self.model_simplified, typ=2) ss_total = anova_table['sum_sq'].sum() contribution_table = pd.DataFrame({'Factor': [], 'Suma de Cuadrados': [], '% Contribución': []}) for index, row in anova_table.iterrows(): if index != 'Residual': factor_name = index if factor_name == f'I({self.x1_name} ** 2)': factor_name = f'{self.x1_name}^2' elif factor_name == f'I({self.x2_name} ** 2)': factor_name = f'{self.x2_name}^2' elif factor_name == f'I({self.x3_name} ** 2)': factor_name = f'{self.x3_name}^2' ss_factor = row['sum_sq'] contribution_percentage = (ss_factor / ss_total) * 100 contribution_table = pd.concat([contribution_table, pd.DataFrame({'Factor': [factor_name], 'Suma de Cuadrados': [ss_factor], '% Contribución': [contribution_percentage]})], ignore_index=True) return contribution_table.round(3) def calculate_detailed_anova(self): if self.model_simplified is None: return None formula_reduced = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2)' model_reduced = smf.ols(formula_reduced, data=self.data).fit() anova_reduced = sm.stats.anova_lm(model_reduced, typ=2) ss_total = np.sum((self.data[self.y_name] - self.data[self.y_name].mean())**2) df_total = len(self.data) - 1 ss_regression = anova_reduced['sum_sq'][:-1].sum() df_regression = len(anova_reduced) - 1 ss_residual = self.model_simplified.ssr df_residual = self.model_simplified.df_resid replicas = self.data[self.data.duplicated(subset=[self.x1_name, self.x2_name, self.x3_name], keep=False)] ss_pure_error = replicas.groupby([self.x1_name, self.x2_name, self.x3_name])[self.y_name].var().sum() * replicas.groupby([self.x1_name, self.x2_name, self.x3_name]).ngroups if not replicas.empty else np.nan df_pure_error = len(replicas) - replicas.groupby([self.x1_name, self.x2_name, self.x3_name]).ngroups if not replicas.empty else np.nan ss_lack_of_fit = ss_residual - ss_pure_error if not np.isnan(ss_pure_error) else np.nan df_lack_of_fit = df_residual - df_pure_error if not np.isnan(df_pure_error) else np.nan ms_regression = ss_regression / df_regression ms_residual = ss_residual / df_residual ms_lack_of_fit = np.nan if not np.isnan(df_lack_of_fit) and df_lack_of_fit != 0: ms_lack_of_fit = ss_lack_of_fit / df_lack_of_fit ms_pure_error = ss_pure_error / df_pure_error if not np.isnan(df_pure_error) else np.nan f_lack_of_fit = ms_lack_of_fit / ms_pure_error if not np.isnan(ms_lack_of_fit) and not np.isnan(ms_pure_error) and ms_pure_error != 0 else np.nan p_lack_of_fit = 1 - f.cdf(f_lack_of_fit, df_lack_of_fit, df_pure_error) if not np.isnan(f_lack_of_fit) and not np.isnan(df_lack_of_fit) and not np.isnan(df_pure_error) else np.nan detailed_anova_table = pd.DataFrame({ 'Fuente de Variación': ['Regresión', 'Curvatura', 'Residual', 'Falta de Ajuste', 'Error Puro', 'Total'], # Curvature added here 'Suma de Cuadrados': [ss_regression, np.nan, ss_residual, ss_lack_of_fit, ss_pure_error, ss_total], # ss_curvature removed from here 'Grados de Libertad': [df_regression, np.nan, df_residual, df_lack_of_fit, df_pure_error, df_total], # df_curvature removed from here 'Cuadrado Medio': [ms_regression, np.nan, ms_residual, ms_lack_of_fit, ms_pure_error, np.nan], 'F': [np.nan, np.nan, np.nan, f_lack_of_fit, np.nan, np.nan], 'Valor p': [np.nan, np.nan, np.nan, p_lack_of_fit, np.nan, np.nan] }) ss_curvature = anova_reduced['sum_sq'][f'I({self.x1_name} ** 2)'] + anova_reduced['sum_sq'][f'I({self.x2_name} ** 2)'] + anova_reduced['sum_sq'][f'I({self.x3_name} ** 2)'] df_curvature = 3 detailed_anova_table.loc[1, ['Fuente de Variación', 'Suma de Cuadrados', 'Grados de Libertad', 'Cuadrado Medio']] = ['Curvatura', ss_curvature, df_curvature, ss_curvature / df_curvature] # Curvature row added here return detailed_anova_table.round(3) def get_all_tables(self): prediction_table = self.generate_prediction_table() contribution_table = self.calculate_contribution_percentage() detailed_anova_table = self.calculate_detailed_anova() return {'Predicciones': prediction_table, '% Contribución': contribution_table, 'ANOVA Detallada': detailed_anova_table} def save_figures_to_zip(self): if not self.all_figures_simplified and not self.all_figures_full and not self.all_figures_personalized: return None zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, 'w') as zip_file: for idx, fig in enumerate(self.all_figures_simplified, start=1): img_bytes = fig.to_image(format="png") zip_file.writestr(f'Grafico_Simplificado_{idx}.png', img_bytes) for idx, fig in enumerate(self.all_figures_full, start=1): img_bytes = fig.to_image(format="png") zip_file.writestr(f'Grafico_Completo_{idx}.png', img_bytes) for idx, fig in enumerate(self.all_figures_personalized, start=1): img_bytes = fig.to_image(format="png") zip_file.writestr(f'Grafico_Personalizado_{idx}.png', img_bytes) zip_buffer.seek(0) with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as temp_file: temp_file.write(zip_buffer.read()) temp_path = temp_file.name return temp_path def save_fig_to_bytes(self, fig): return fig.to_image(format="png") def save_all_figures_png(self): png_paths = [] for idx, fig in enumerate(self.all_figures_simplified, start=1): img_bytes = fig.to_image(format="png") with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file: temp_file.write(img_bytes) png_paths.append(temp_file.name) for idx, fig in enumerate(self.all_figures_full, start=1): img_bytes = fig.to_image(format="png") with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file: temp_file.write(img_bytes) png_paths.append(temp_file.name) for idx, fig in enumerate(self.all_figures_personalized, start=1): img_bytes = fig.to_image(format="png") with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file: temp_file.write(img_bytes) png_paths.append(temp_file.name) return png_paths def save_tables_to_excel(self): tables = self.get_all_tables() excel_buffer = io.BytesIO() with pd.ExcelWriter(excel_buffer, engine='xlsxwriter') as writer: for sheet_name, table in tables.items(): table.to_excel(writer, sheet_name=sheet_name, index=False) excel_buffer.seek(0) excel_bytes = excel_buffer.read() with tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") as temp_file: temp_file.write(excel_bytes) temp_path = temp_file.name return temp_path def export_tables_to_word(self, tables_dict): if not tables_dict: return None doc = docx.Document() style = doc.styles['Normal'] font = style.font font.name = 'Times New Roman' font.size = Pt(12) titulo = doc.add_heading('Informe de Optimización de Producción de Absorbancia', 0) titulo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER doc.add_paragraph(f"Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}").alignment = WD_PARAGRAPH_ALIGNMENT.CENTER doc.add_paragraph('\n') for sheet_name, table in tables_dict.items(): doc.add_heading(sheet_name, level=1) if table.empty: doc.add_paragraph("No hay datos disponibles para esta tabla.") continue table_doc = doc.add_table(rows=1, cols=len(table.columns)) table_doc.style = 'Light List Accent 1' hdr_cells = table_doc.rows[0].cells for idx, col_name in enumerate(table.columns): hdr_cells[idx].text = col_name for _, row in table.iterrows(): row_cells = table_doc.add_row().cells for idx, item in enumerate(row): row_cells[idx].text = str(item) doc.add_paragraph('\n') with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as tmp: doc.save(tmp.name) tmp_path = tmp.name return tmp_path # --- Funciones para la Interfaz de Gradio --- def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x3_levels_str, data_str): try: x1_levels = [float(x.strip()) for x in x1_levels_str.split(',')] x2_levels = [float(x.strip()) for x in x2_levels_str.split(',')] x3_levels = [float(x.strip()) for x in x3_levels_str.split(',')] data_list = [row.split(',') for row in data_str.strip().split('\n')] column_names = ['Exp.', x1_name, x2_name, x3_name, y_name] data = pd.DataFrame(data_list, columns=column_names).apply(pd.to_numeric, errors='coerce') if not all(col in data.columns for col in column_names): raise ValueError("Data format incorrect.") global rsm rsm = RSM_BoxBehnken(data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels) return data.round(3), gr.update(visible=True) except Exception as e: error_message = f"Error loading data: {str(e)}" print(error_message) return None, gr.update(visible=False) def fit_and_optimize_model(): if 'rsm' not in globals(): return [None]*11 model_completo, pareto_completo = rsm.fit_model() model_simplificado, pareto_simplificado = rsm.fit_simplified_model() optimization_table = rsm.optimize() equation = rsm.get_simplified_equation() prediction_table = rsm.generate_prediction_table() contribution_table = rsm.calculate_contribution_percentage() anova_table = rsm.calculate_detailed_anova() rsm.generate_all_plots() equation_formatted = equation.replace(" + ", "
+ ").replace(" ** ", "^").replace("*", " × ") equation_formatted = f"### Ecuación del Modelo Simplificado:
{equation_formatted}" excel_path = rsm.save_tables_to_excel() zip_path = rsm.save_figures_to_zip() return (model_completo.summary().as_html(), pareto_completo, model_simplificado.summary().as_html(), pareto_simplificado, equation_formatted, optimization_table, prediction_table, contribution_table, anova_table, zip_path, excel_path) def fit_custom_model(factor_checkboxes, interaction_checkboxes, model_personalized_output_component, pareto_personalized_output_component): if 'rsm' not in globals(): return [None]*2 formula_parts = [rsm.x1_name, rsm.x2_name, rsm.x3_name] if "factors" in factor_checkboxes else [] if "x1_sq" in factor_checkboxes: formula_parts.append(f'I({rsm.x1_name}**2)') if "x2_sq" in factor_checkboxes: formula_parts.append(f'I({rsm.x2_name}**2)') if "x3_sq" in factor_checkboxes: formula_parts.append(f'I({rsm.x3_name}**2)') if "x1x2" in interaction_checkboxes: formula_parts.append(f'{rsm.x1_name}:{rsm.x2_name}') if "x1x3" in interaction_checkboxes: formula_parts.append(f'{rsm.x1_name}:{rsm.x3_name}') if "x2x3" in interaction_checkboxes: formula_parts.append(f'{rsm.x2_name}:{rsm.x3_name}') formula = f'{rsm.y_name} ~ ' + ' + '.join(formula_parts) if formula_parts else f'{rsm.y_name} ~ 1' custom_model, pareto_custom = rsm.fit_personalized_model(formula) rsm.generate_all_plots() return custom_model.summary().as_html(), pareto_custom def show_plot(current_index, all_figures, model_type): figure_list = rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized if not figure_list: return None, f"No graphs for {model_type}.", current_index selected_fig = figure_list[current_index] plot_info_text = f"Gráfico {current_index + 1} de {len(figure_list)} (Modelo {model_type.capitalize()})" return selected_fig, plot_info_text, current_index def navigate_plot(direction, current_index, all_figures, model_type): figure_list = rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized if not figure_list: return None, f"No graphs for {model_type}.", current_index new_index = (current_index - 1) % len(figure_list) if direction == 'left' else (current_index + 1) % len(figure_list) selected_fig = figure_list[new_index] plot_info_text = f"Gráfico {new_index + 1} de {len(figure_list)} (Modelo {model_type.capitalize()})" return selected_fig, plot_info_text, new_index def download_current_plot(all_figures, current_index, model_type): figure_list = rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized if not figure_list: return None fig = figure_list[current_index] img_bytes = rsm.save_fig_to_bytes(fig) filename = f"Grafico_RSM_{model_type}_{current_index + 1}.png" with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file: temp_file.write(img_bytes) return temp_file.name def download_all_plots_zip(model_type): if 'rsm' not in globals(): return None if model_type == 'full': rsm.all_figures = rsm.all_figures_full elif model_type == 'simplified': rsm.all_figures = rsm.all_figures_simplified elif model_type == 'personalized': rsm.all_figures = rsm.all_figures_personalized zip_path = rsm.save_figures_to_zip() filename = f"Graficos_RSM_{model_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip" return zip_path def download_all_tables_excel(): if 'rsm' not in globals(): return None return rsm.save_tables_to_excel() def exportar_word(rsm_instance, tables_dict): return rsm_instance.export_tables_to_word(tables_dict) def create_gradio_interface(): global model_completo_output, pareto_completo_output, model_simplificado_output, pareto_simplificado_output, equation_output, optimization_table_output, prediction_table_output, contribution_table_output, anova_table_output, download_all_plots_button, download_excel_button, rsm_plot_output, plot_info, current_index_state, all_figures_state, current_model_type_state, model_personalized_output, pareto_personalized_output, factor_checkboxes, interaction_checkboxes with gr.Blocks() as demo: gr.Markdown("# Optimización de la Absorbancia usando RSM") with gr.Row(): with gr.Column(): gr.Markdown("## Configuración del Diseño") x1_name_input = gr.Textbox(label="Nombre de la Variable X1 (ej. Glucosa)", value="Glucosa_g_L") x2_name_input = gr.Textbox(label="Nombre de la Variable X2 (ej. Proteina_Pescado)", value="Proteina_Pescado_g_L") x3_name_input = gr.Textbox(label="Nombre de la Variable X3 (ej. Sulfato_Manganeso)", value="Sulfato_Manganeso_g_L") y_name_input = gr.Textbox(label="Nombre de la Variable Dependiente (ej. Absorbancia)", value="Abs_600nm") x1_levels_input = gr.Textbox(label="Niveles de X1 (separados por comas)", value="0, 5, 10") x2_levels_input = gr.Textbox(label="Niveles de X2 (separados por comas)", value="0, 1.4, 3.2, 5") x3_levels_input = gr.Textbox(label="Niveles de X3 (separados por comas)", value="0.25, 0.5, 0.75") data_input = gr.Textbox(label="Datos del Experimento (formato CSV)", lines=10, value="""Exp.,Glucosa_g_L,Proteina_Pescado_g_L,Sulfato_Manganeso_g_L,Abs_600nm 1,-1,-1,0,1.576 2,1,-1,0,1.474 3,-1,1,0,1.293 4,1,1,0,1.446 5,-1,0,-1,1.537 6,1,0,-1,1.415 7,-1,0,1,1.481 8,1,0,1,1.419 9,0,-1,-1,1.321 10,0,1,-1,1.224 11,0,-1,1,1.459 12,0,1,1,0.345 13,0,0,0,1.279 14,0,0,0,1.181 15,0,0,0,0.662, 16,-1,-1,0,1.760 17,1,-1,0,1.690 18,-1,1,0,1.485 19,1,1,0,1.658 20,-1,0,-1,1.728 21,1,0,-1,1.594 22,-1,0,1,1.673 23,1,0,1,1.607 24,0,-1,-1,1.531 25,0,1,-1,1.424 26,0,-1,1,1.595 27,0,1,1,0.344 28,0,0,0,1.477 29,0,0,0,1.257 30,0,0,0,0.660, 31,-1,-1,0,1.932 32,1,-1,0,1.780 33,-1,1,0,1.689 34,1,1,0,1.876 35,-1,0,-1,1.885 36,1,0,-1,1.824 37,-1,0,1,1.913 38,1,0,1,1.810 39,0,-1,-1,1.852 40,0,1,-1,1.694 41,0,-1,1,1.831 42,0,1,1,0.347 43,0,0,0,1.752 44,0,0,0,1.367 45,0,0,0,0.656""") load_button = gr.Button("Cargar Datos") data_dropdown = gr.Dropdown(["All Data"], value="All Data", label="Seleccionar Datos") with gr.Column(): gr.Markdown("## Datos Cargados") data_output = gr.Dataframe(label="Tabla de Datos", interactive=False) with gr.Row(visible=False) as analysis_row: with gr.Column(): fit_button = gr.Button("Ajustar Modelo Simplificado y Completo") gr.Markdown("**Modelo Completo**") model_completo_output_comp = model_completo_output # Use global output_components pareto_completo_output_comp = pareto_completo_output gr.Markdown("**Modelo Simplificado**") model_simplificado_output_comp = model_simplificado_output pareto_simplificado_output_comp = pareto_simplificado_output gr.Markdown("## Modelo Personalizado") factor_checkboxes_comp = factor_checkboxes interaction_checkboxes_comp = interaction_checkboxes custom_model_button = gr.Button("Ajustar Modelo Personalizado") model_personalized_output_comp = model_personalized_output pareto_personalized_output_comp = pareto_personalized_output gr.Markdown("**Ecuación del Modelo Simplificado**") equation_output_comp = equation_output optimization_table_output_comp = optimization_table_output prediction_table_output_comp = prediction_table_output contribution_table_output_comp = contribution_table_output anova_table_output_comp = anova_table_output gr.Markdown("## Descargar Todas las Tablas") download_excel_button_comp = download_excel_button download_word_button = gr.DownloadButton("Descargar Tablas en Word") with gr.Column(): gr.Markdown("## Gráficos de Superficie de Respuesta") model_type_radio = gr.Radio(["simplified", "full", "personalized"], value="simplified", label="Tipo de Modelo para Gráficos") fixed_variable_input = gr.Dropdown(label="Variable Fija", choices=["Glucosa_g_L", "Proteina_Pescado_g_L", "Sulfato_Manganeso_g_L"], value="Glucosa_g_L") fixed_level_input = gr.Slider(label="Nivel de Variable Fija (Natural Units)", minimum=0, maximum=10, step=0.1, value=5.0) plot_button = gr.Button("Generar Gráficos") with gr.Row(): left_button = gr.Button("<") right_button = gr.Button(">") rsm_plot_output_comp = rsm_plot_output plot_info_comp = plot_info with gr.Row(): download_plot_button_comp = download_plot_button download_all_plots_button_comp = download_all_plots_button current_index_state_comp = current_index_state all_figures_state_comp = all_figures_state current_model_type_state_comp = current_model_type_state load_button.click(load_data, inputs=[x1_name_input, x2_name_input, x3_name_input, y_name_input, x1_levels_input, x2_levels_input, x3_levels_input, data_input], outputs=[data_output, analysis_row]) fit_button.click(fit_and_optimize_model, inputs=[], outputs=[model_completo_output_comp, pareto_completo_output_comp, model_simplificado_output_comp, pareto_simplificado_output_comp, equation_output_comp, optimization_table_output_comp, prediction_table_output_comp, contribution_table_output_comp, anova_table_output_comp, download_all_plots_button_comp, download_excel_button_comp]) custom_model_button.click(fit_custom_model, inputs=[factor_checkboxes_comp, interaction_checkboxes_comp, model_personalized_output_comp, pareto_personalized_output_comp], outputs=[model_personalized_output_comp, pareto_personalized_output_comp]) # Pass output components as input and output plot_button.click(lambda fixed_var, fixed_lvl, model_type: show_plot(0, [], model_type) if not hasattr(rsm, 'all_figures_full') or not rsm.all_figures_full else show_plot(0, [], model_type) if model_type == 'full' and not rsm.all_figures_full else show_plot(0, [], model_type) if model_type == 'simplified' and not rsm.all_figures_simplified else show_plot(0, [], model_type) if model_type == 'personalized' and not rsm.all_figures_personalized else show_plot(0, rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized, model_type), inputs=[fixed_variable_input, fixed_level_input, model_type_radio], outputs=[rsm_plot_output_comp, plot_info_comp, current_index_state_comp, current_model_type_state_comp]) left_button.click(lambda current_index, all_figures, model_type: navigate_plot('left', current_index, all_figures, model_type), inputs=[current_index_state_comp, all_figures_state_comp, current_model_type_state_comp], outputs=[rsm_plot_output_comp, plot_info_comp, current_index_state_comp]) right_button.click(lambda current_index, all_figures, model_type: navigate_plot('right', current_index, all_figures, model_type), inputs=[current_index_state_comp, all_figures_state_comp, current_model_type_state_comp], outputs=[rsm_plot_output_comp, plot_info_comp, current_index_state_comp]) download_plot_button.click(download_current_plot, inputs=[all_figures_state_comp, current_index_state_comp, current_model_type_state_comp], outputs=download_plot_button_comp) download_all_plots_button.click(lambda model_type: download_all_plots_zip(model_type), inputs=[current_model_type_state_comp], outputs=download_all_plots_button_comp) download_excel_button.click(fn=lambda: download_all_tables_excel(), inputs=[], outputs=download_excel_button_comp) download_word_button.click(exportar_word, inputs=[gr.State(rsm), gr.State(rsm.get_all_tables())], outputs=download_word_button) # Pass rsm instance and tables as state return demo # --- Función Principal --- def main(): interface = create_gradio_interface() interface.launch(share=True) if __name__ == "__main__": main()