Spaces:
Sleeping
Sleeping
| # ========================================================== | |
| # 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 |