MedSpace / scripts /generate_final_diagrams.py
kbsss's picture
Upload folder using huggingface_hub
f373e2b verified
Raw
History Blame Contribute Delete
11.8 kB
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch, PathPatch
from matplotlib.path import Path
import numpy as np
from pathlib import Path
# ==========================================
# Configuration & Style
# ==========================================
OUTPUT_DIR = Path("images/publication_final")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# Enterprise Color Palette
C_PRIMARY = "#003366" # Navy Blue (Borders/Main)
C_FILL_SVC = "#E6F2FF" # Light Blue (Services)
C_FILL_DB = "#E6FFEA" # Light Green (Databases)
C_FILL_EXT = "#F0F0F0" # Light Grey (External/Users)
C_ACCENT = "#FF6600" # Orange (Highlights)
C_TEXT = "#000000"
C_ARROW = "#333333"
plt.rcParams.update({
'font.family': 'sans-serif',
'font.sans-serif': ['Arial', 'DejaVu Sans'],
'font.size': 10,
'axes.linewidth': 1,
})
class DrawEngine:
def __init__(self, ax):
self.ax = ax
self.ax.set_aspect('equal')
self.ax.axis('off')
def rect(self, x, y, w, h, label, fill=C_FILL_SVC, border=C_PRIMARY, subtitle=None):
"""Standard Service Rectangle"""
box = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0,rounding_size=0.1",
ec=border, fc=fill, lw=1.5, zorder=10)
self.ax.add_patch(box)
cx, cy = x + w/2, y + h/2
self.ax.text(cx, cy + (0.15 if subtitle else 0), label, ha='center', va='center',
fontweight='bold', color=C_TEXT, zorder=11)
if subtitle:
self.ax.text(cx, cy - 0.15, subtitle, ha='center', va='center',
fontsize=8, color='#555555', zorder=11)
return (x, y, w, h)
def database(self, x, y, w, h, label):
"""Cylinder Shape for DB"""
# Ellipse top
top = mpatches.Ellipse((x + w/2, y + h), w, h*0.3, ec=C_PRIMARY, fc=C_FILL_DB, lw=1.5, zorder=12)
# Rectangle body
body = mpatches.Rectangle((x, y + h*0.15), w, h*0.85, ec='none', fc=C_FILL_DB, zorder=11)
# Bottom curve
bottom = mpatches.Arc((x + w/2, y + h*0.15), w, h*0.3, theta1=180, theta2=360, ec=C_PRIMARY, lw=1.5, zorder=12)
# Side lines
self.ax.plot([x, x], [y + h*0.15, y + h], color=C_PRIMARY, lw=1.5, zorder=12)
self.ax.plot([x+w, x+w], [y + h*0.15, y + h], color=C_PRIMARY, lw=1.5, zorder=12)
self.ax.add_patch(top)
self.ax.add_patch(body)
self.ax.add_patch(bottom)
self.ax.text(x + w/2, y + h*0.5, label, ha='center', va='center',
fontweight='bold', fontsize=8, zorder=13)
def actor(self, x, y, label):
"""Stick figure user"""
# Head
head = mpatches.Circle((x, y + 0.8), 0.2, ec=C_PRIMARY, fc='white', lw=1.5)
self.ax.add_patch(head)
# Body
self.ax.plot([x, x], [y + 0.6, y + 0.3], color=C_PRIMARY, lw=1.5)
# Arms
self.ax.plot([x - 0.25, x + 0.25], [y + 0.5, y + 0.5], color=C_PRIMARY, lw=1.5)
# Legs
self.ax.plot([x, x - 0.2], [y + 0.3, y], color=C_PRIMARY, lw=1.5)
self.ax.plot([x, x + 0.2], [y + 0.3, y], color=C_PRIMARY, lw=1.5)
self.ax.text(x, y - 0.2, label, ha='center', va='top', fontweight='bold')
def connector(self, p1, p2, label=None, style='->'):
"""Orthogonal or straight arrow"""
# Simple straight line for now, or elbow if needed
# We'll use annotate for correct arrow heads
self.ax.annotate("", xy=p2, xytext=p1,
arrowprops=dict(arrowstyle=style, color=C_ARROW, lw=1.5))
if label:
mid = ((p1[0]+p2[0])/2, (p1[1]+p2[1])/2)
self.ax.text(mid[0], mid[1] + 0.1, label, ha='center', fontsize=8,
bbox=dict(facecolor='white', edgecolor='none', alpha=0.8))
def title(self, label):
self.ax.text(0.5, 0.95, label, transform=self.ax.transAxes,
ha='center', fontsize=16, fontweight='bold', color=C_PRIMARY)
# ==========================================
# Diagram Functions
# ==========================================
def slide6_system_overview():
fig, ax = plt.subplots(figsize=(12, 7))
d = DrawEngine(ax)
ax.set_xlim(0, 12)
ax.set_ylim(0, 8)
d.title("High-Level System Architecture")
# Components
d.actor(1, 4, "Patient")
d.rect(2.5, 3.5, 2, 1.5, "Frontend UI", subtitle="Streamlit")
d.rect(5.5, 3.5, 2, 1.5, "Orchestrator", subtitle="FastAPI")
# RAG Container
rag_box = FancyBboxPatch((8, 1), 3.5, 6, boxstyle="round,pad=0.2",
ec=C_PRIMARY, fc="#F5F5F5", linestyle="--")
ax.add_patch(rag_box)
ax.text(9.75, 6.7, "RAG Engine", ha='center', fontweight='bold', color='#555555')
d.rect(8.5, 5, 2.5, 1, "Retrieval", subtitle="Hybrid (BM25+Dense)")
d.rect(8.5, 3.5, 2.5, 1, "Reranker", subtitle="Cross-Encoder")
d.rect(8.5, 2, 2.5, 1, "Generative Model", subtitle="BioMistral-7B")
# Database
d.database(8.75, 0, 2, 1.2, "Vector DB\n(Chroma)")
# Flows
d.connector((1.3, 4.5), (2.5, 4.5)) # User -> UI
d.connector((4.5, 4.25), (5.5, 4.25), "JSON") # UI -> API
d.connector((7.5, 4.25), (8.5, 5.5), "Query") # API -> Retrieval
# Internal RAG flows
d.connector((9.75, 5), (9.75, 4.5))
d.connector((9.75, 3.5), (9.75, 3))
# Return path
d.connector((9.75, 2), (7.5, 3.8), "Response") # Gen -> API
plt.tight_layout()
plt.savefig(OUTPUT_DIR / "slide6_system_overview.png", dpi=300, facecolor='white')
plt.close()
def slide7_detailed_system():
fig, ax = plt.subplots(figsize=(14, 9)) # Wider
d = DrawEngine(ax)
ax.set_xlim(0, 14)
ax.set_ylim(0, 9)
d.title("Detailed Healthcare RAG Pipeline Component View")
# Layout Grid
y_main = 5
# 1. Input Processing
d.rect(0.5, y_main, 2, 1, "Query Processing", subtitle="Clean/NER")
# 2. Embedding
d.rect(3, y_main, 2, 1, "Embedding Model", subtitle="MedCPT")
# 3. Retrieval
d.rect(5.5, y_main+1.5, 2, 1, "Dense Retrieval")
d.rect(5.5, y_main-1.5, 2, 1, "Sparse Retrieval", subtitle="BM25")
# DB
d.database(5.5, y_main-0.25, 2, 1.5, "ChromaDB")
# 4. Fusion
d.rect(8, y_main, 2, 1, "Hybrid Fusion\n& Reranking")
# 5. Generation
d.rect(10.5, y_main, 2.5, 1, "LLM Generation", subtitle="BioMistral (QLoRA)")
# 6. XAI
d.rect(10.5, 2, 2.5, 1.5, "XAI Module", subtitle="SHAP/Citations")
# 7. Output processing
d.rect(10.5, 0.5, 2.5, 1, "Output Formatter")
# Connectors
d.connector((2.5, 5.5), (3, 5.5))
d.connector((5, 5.5), (5.5, 5.5)) # To Middle? No
# Arrows (Manual precise)
ax.annotate("", xy=(5.5, 6), xytext=(4, 6), arrowprops=dict(arrowstyle="->", connectionstyle="angle,angleA=0,angleB=90,rad=10"))
# Embedding -> Dense
d.connector((5, 5.5), (5.5, 6.5)) # Emb -> Dense (Approx)
d.connector((5, 5.5), (5.5, 4)) # Emb -> Sparse
d.connector((7.5, 6.5), (8, 6)) # Dense -> Fusion
d.connector((7.5, 4), (8, 5)) # Sparse -> Fusion
d.connector((10, 5.5), (10.5, 5.5)) # Fusion -> Gen
d.connector((11.75, 5), (11.75, 3.5)) # Gen -> XAI
d.connector((11.75, 2), (11.75, 1.5)) # XAI -> Format
plt.tight_layout()
plt.savefig(OUTPUT_DIR / "slide7_detailed_system.png", dpi=300, facecolor='white')
plt.close()
def slide9_hybrid_retrieval():
fig, ax = plt.subplots(figsize=(10, 6))
d = DrawEngine(ax)
ax.set_xlim(0, 10)
ax.set_ylim(0, 6)
d.title("Hybrid Retrieval Architecture")
# Input
d.rect(0.5, 2.5, 1.5, 1, "Query")
# Split
d.connector((2, 3), (3, 4.5))
d.connector((2, 3), (3, 1.5))
# Path 1: Dense
d.rect(3, 4, 2, 1, "Dense Enc", subtitle="MedCPT")
d.database(5.5, 3.8, 1.5, 1.2, "Vector DB")
# Path 2: Sparse
d.rect(3, 1, 2, 1, "Keyword Ext")
d.database(5.5, 0.8, 1.5, 1.2, "Inv. Index")
# Fusion
d.rect(8, 2, 1.5, 2, "Hybrid\nFusion", subtitle="Reciprocal Rank")
# Connectors
d.connector((5, 4.5), (5.5, 4.5))
d.connector((5, 1.5), (5.5, 1.5))
d.connector((7, 4.5), (8, 3.5))
d.connector((7, 1.5), (8, 2.5))
plt.tight_layout()
plt.savefig(OUTPUT_DIR / "slide9_hybrid_retrieval.png", dpi=300, facecolor='white')
plt.close()
def slide10_corrective_rag():
fig, ax = plt.subplots(figsize=(10, 7))
d = DrawEngine(ax)
ax.set_xlim(0, 10)
ax.set_ylim(0, 8)
d.title("Corrective RAG (CRAG) Logic")
# Flow
d.rect(1, 6, 2, 1, "Retrieved Docs")
# Decision Diamond
# Draw logic check box
d.rect(4, 5.5, 2, 2, "Grounding\nEvaluator", subtitle="Relevance Check")
# Paths
d.rect(7, 6.5, 2, 1, "Relevant", fill="#D4EDDA", border="#28A745")
d.rect(7, 4.5, 2, 1, "Irrelevant", fill="#F8D7DA", border="#DC3545")
d.rect(7, 2, 2, 1.5, "Knowledge\nRefinement", subtitle="Web Search / Filter")
d.rect(4, 0.5, 2, 1, "Final Context")
# Connectors
d.connector((3, 6.5), (4, 6.5))
d.connector((6, 7), (7, 7))
d.connector((6, 5), (7, 5))
d.connector((9, 7), (9, 3.5), style="-")
d.connector((9, 5), (9, 3.5))
d.connector((9, 3.5), (9, 2)) # Down to refinement
d.connector((8, 2), (6, 1))
plt.tight_layout()
plt.savefig(OUTPUT_DIR / "slide10_corrective_rag.png", dpi=300, facecolor='white')
plt.close()
def slide11_xai_module_linear():
fig, ax = plt.subplots(figsize=(12, 6))
d = DrawEngine(ax)
ax.set_xlim(0, 12)
ax.set_ylim(0, 6)
d.title("XAI Module Processing Pipeline")
# 1. Input
d.rect(0.5, 2.5, 2, 1, "Raw LLM Output")
# 2. Parallel Analysis Processors
d.rect(3.5, 4.5, 2.5, 1, "Feature Importance", subtitle="SHAP / LIME")
d.rect(3.5, 2.5, 2.5, 1, "Confidence Scorer", subtitle="Logits Analysis")
d.rect(3.5, 0.5, 2.5, 1, "Source Attribution", subtitle="Citation Matching")
# 3. Aggregation
d.rect(7, 2, 2, 2, "Explanation\nAggregator")
# 4. Output
d.rect(10, 2.25, 1.5, 1.5, "Explainable\nResponse", fill="#FFF3CD", border=C_ACCENT)
# Flow
# Split
d.connector((2.5, 3), (3.5, 5))
d.connector((2.5, 3), (3.5, 3))
d.connector((2.5, 3), (3.5, 1))
# Join
d.connector((6, 5), (7, 3.5))
d.connector((6, 3), (7, 3))
d.connector((6, 1), (7, 2.5))
d.connector((9, 3), (10, 3))
plt.tight_layout()
plt.savefig(OUTPUT_DIR / "slide11_xai_module.png", dpi=300, facecolor='white')
plt.close()
# Charts
def generate_charts():
# Accuracy
plt.figure(figsize=(8, 5))
models = ['Baseline', 'Naive RAG', 'Hybrid', 'Our System']
acc = [0.62, 0.74, 0.81, 0.89]
plt.bar(models, acc, color=[C_PRIMARY]*3 + [C_ACCENT])
plt.title("Accuracy Comparison")
plt.ylim(0, 1)
plt.savefig(OUTPUT_DIR / "slide12_accuracy_results.png", dpi=300)
plt.close()
# Latency Scatter
plt.figure(figsize=(8, 5))
lat = [200, 600, 900, 1200]
acc = [0.62, 0.74, 0.81, 0.89]
plt.scatter(lat, acc, s=100, c='gray')
plt.scatter([1200], [0.89], s=150, c=C_ACCENT, label='Our System')
plt.xlabel('Latency (ms)')
plt.ylabel('Accuracy')
plt.title('Performance Trade-off')
plt.grid(True, linestyle='--', alpha=0.5)
plt.savefig(OUTPUT_DIR / "slide13_latency_results.png", dpi=300)
plt.close()
if __name__ == "__main__":
slide6_system_overview()
slide7_detailed_system()
slide9_hybrid_retrieval()
slide10_corrective_rag()
slide11_xai_module_linear() # New linear layout
generate_charts()