# ========================================================== # ISPG VISUAL GENERATOR (PATCHED STABLE VERSION) # FILE: models/visual_generator.py # ========================================================== # PURPOSE: # - Generate Flowchart PNG (Methodology) # - Generate Results Table PNG # - Generate Simple Bar Chart PNG (Metrics) # - Extract Numeric Metrics from bullets # # IMPORTANT PATCH: # - Use matplotlib Agg backend (NO tkinter GUI) # - Prevent Flask threading crash: "main thread is not in main loop" # ========================================================== import os import re from typing import List, Dict, Any # ========================================================== # IMPORTANT FIX (MUST BE BEFORE pyplot import) # ========================================================== import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt # ========================================================== # HELPER: Extract numeric values from bullet strings # ========================================================== def extract_numeric_metrics(bullets: List[str]) -> Dict[str, float]: """ Example bullet: "Accuracy: 92.5%" "F1-score = 0.84" "Execution time 12.4 seconds" Returns dict: {"Accuracy":92.5, "F1-score":0.84, "Execution time":12.4} """ metrics = {} if not bullets or not isinstance(bullets, list): return metrics for b in bullets: if not isinstance(b, str): continue text = b.strip() if not text: continue # Match key + number (float/int) match = re.search(r"([A-Za-z0-9\-\s]+)[:=]?\s*([0-9]+(\.[0-9]+)?)", text) if match: key = match.group(1).strip() val = match.group(2).strip() try: val = float(val) except: continue if key: metrics[key] = val return metrics # ========================================================== # FLOWCHART PNG GENERATOR # ========================================================== def generate_flowchart_png(steps: List[str], output_path: str) -> bool: """ Creates a simple vertical flowchart image using matplotlib. """ try: if not steps or not isinstance(steps, list): return False steps = [s.strip() for s in steps if isinstance(s, str) and s.strip()] if not steps: return False os.makedirs(os.path.dirname(output_path), exist_ok=True) fig, ax = plt.subplots(figsize=(8, max(6, len(steps) * 1.2))) ax.axis("off") y = 1.0 spacing = 1.0 / (len(steps) + 1) for i, step in enumerate(steps): ax.text( 0.5, y, f"{i+1}. {step}", ha="center", va="center", fontsize=12, fontweight="bold", bbox=dict( boxstyle="round,pad=0.5", fc="white", ec="black" ) ) # Draw arrow except last if i < len(steps) - 1: ax.annotate( "", xy=(0.5, y - spacing * 0.7), xytext=(0.5, y - spacing * 0.2), arrowprops=dict(arrowstyle="->", lw=2) ) y -= spacing plt.tight_layout() plt.savefig(output_path, dpi=200, bbox_inches="tight") plt.close(fig) return os.path.exists(output_path) except Exception as e: print("❌ generate_flowchart_png failed:", str(e)) return False # ========================================================== # RESULTS TABLE PNG GENERATOR # ========================================================== def generate_results_table_png(results_tables: List[Dict[str, Any]], output_path: str) -> bool: """ Generate PNG table from first detected results table. """ try: if not results_tables or not isinstance(results_tables, list): return False table = results_tables[0] if not isinstance(table, dict): return False headers = table.get("headers", []) rows = table.get("rows", []) if not headers or not rows: return False os.makedirs(os.path.dirname(output_path), exist_ok=True) # Adjust figure height dynamically fig_height = max(4, min(10, len(rows) * 0.6)) fig, ax = plt.subplots(figsize=(12, fig_height)) ax.axis("off") table_plot = ax.table( cellText=rows, colLabels=headers, cellLoc="center", loc="center" ) table_plot.auto_set_font_size(False) table_plot.set_fontsize(10) table_plot.scale(1.2, 1.3) plt.tight_layout() plt.savefig(output_path, dpi=200, bbox_inches="tight") plt.close(fig) return os.path.exists(output_path) except Exception as e: print("❌ generate_results_table_png failed:", str(e)) return False # ========================================================== # RESULTS CHART PNG GENERATOR # ========================================================== def generate_results_chart_png(metrics_dict: Dict[str, float], output_path: str) -> bool: """ Generate bar chart from numeric metrics. """ try: if not metrics_dict or not isinstance(metrics_dict, dict): return False labels = list(metrics_dict.keys())[:8] values = [] for k in labels: try: values.append(float(metrics_dict[k])) except: values.append(0) if not labels or not values: return False os.makedirs(os.path.dirname(output_path), exist_ok=True) fig, ax = plt.subplots(figsize=(10, 5)) ax.bar(labels, values) ax.set_title("Results Metrics") ax.set_ylabel("Value") ax.tick_params(axis="x", rotation=25) plt.tight_layout() plt.savefig(output_path, dpi=200, bbox_inches="tight") plt.close(fig) return os.path.exists(output_path) except Exception as e: print("❌ generate_results_chart_png failed:", str(e)) return False