Spaces:
Runtime error
Runtime error
| """ | |
| Tri-Netra - Patient Diagnostic Summary PDF Generator | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| Generates a clean, official-looking PDF report with structured sections | |
| for patient info, model inference statistics, a Grad-CAM heatmap | |
| placeholder, and a clinician signature block. | |
| Requires: fpdf2 (pip install fpdf2) | |
| Author : Anannya Vyas | |
| Email : | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from datetime import datetime | |
| from pathlib import Path | |
| try: | |
| from fpdf import FPDF | |
| except ImportError: | |
| raise ImportError( | |
| "fpdf2 is required. Install it with: pip install fpdf2" | |
| ) | |
| # - Brand colours (teal / emerald / gold matching Tri-Netra theme) - | |
| _TEAL = (79, 70, 229) # primary | |
| _DARK_TEAL = (30, 41, 59) # header bg | |
| _GOLD = (245, 158, 11) # accent | |
| _LIGHT_BG = (248, 250, 252) # section bg | |
| _WHITE = (255, 255, 255) | |
| _BLACK = (30, 30, 30) | |
| _GRAY = (120, 120, 120) | |
| class _ReportPDF(FPDF): | |
| """Custom FPDF subclass with Tri-Netra branded header / footer.""" | |
| def header(self): | |
| # - Dark-teal banner - | |
| self.set_fill_color(*_DARK_TEAL) | |
| self.rect(0, 0, 210, 28, style="F") | |
| # Load and place logo if available | |
| logo_path = Path(__file__).resolve().parent / "Dashboard_Images" / "logo.png" | |
| text_start_x = 10 | |
| # Brand name | |
| self.set_font("Helvetica", "B", 18) | |
| self.set_text_color(*_WHITE) | |
| self.set_xy(text_start_x, 6) | |
| self.cell(0, 10, "Tri-Netra", align="L") | |
| # Subtitle | |
| self.set_font("Helvetica", "", 9) | |
| self.set_text_color(200, 230, 225) | |
| self.set_xy(text_start_x, 16) | |
| self.cell(0, 6, "Patient Diagnostic Summary", align="L") | |
| # Right-side tagline | |
| self.set_font("Helvetica", "I", 8) | |
| self.set_text_color(200, 230, 225) | |
| self.set_xy(-70, 10) | |
| self.cell(60, 6, "AI-Assisted MRI Analysis", align="R") | |
| self.ln(30) | |
| def footer(self): | |
| self.set_y(-18) | |
| self.set_draw_color(*_TEAL) | |
| self.line(10, self.get_y(), 200, self.get_y()) | |
| self.ln(2) | |
| self.set_font("Helvetica", "I", 7) | |
| self.set_text_color(*_GRAY) | |
| self.cell( | |
| 0, 5, | |
| "Generated by Tri-Netra | Anannya Vyas", | |
| align="C", | |
| ) | |
| self.ln(3) | |
| self.set_font("Helvetica", "", 7) | |
| self.cell(0, 4, f"Page {self.page_no()}/{{nb}}", align="C") | |
| # - Helper: section heading - | |
| def _section_heading(pdf: _ReportPDF, title: str) -> None: | |
| pdf.set_font("Helvetica", "B", 11) | |
| pdf.set_text_color(*_DARK_TEAL) | |
| pdf.set_fill_color(*_LIGHT_BG) | |
| pdf.cell(0, 8, f" {title}", new_x="LMARGIN", new_y="NEXT", fill=True) | |
| pdf.ln(2) | |
| def _label_value_row(pdf: _ReportPDF, label: str, value: str) -> None: | |
| pdf.set_font("Helvetica", "B", 9) | |
| pdf.set_text_color(*_BLACK) | |
| pdf.cell(55, 6, label, border=0) | |
| pdf.set_font("Helvetica", "", 9) | |
| pdf.set_text_color(60, 60, 60) | |
| pdf.cell(0, 6, value, new_x="LMARGIN", new_y="NEXT") | |
| # - Public API - | |
| def generate_report( | |
| output_path: str | Path = "diagnostic_summary.pdf", | |
| prediction_stats: dict | None = None, | |
| gradcam_path: str | Path | None = None, | |
| patient_name: str = "", | |
| patient_id: str = "", | |
| diagnostic_note: str = "", | |
| ) -> Path: | |
| """Generate a branded Patient Diagnostic Summary PDF. | |
| Parameters | |
| ---------- | |
| output_path : str or Path | |
| Where to save the PDF. | |
| prediction_stats : dict or None | |
| Model inference statistics. Expected keys (all optional): | |
| ``model_type``, ``prediction_confidence``, ``tumor_class``, | |
| ``inference_time_ms``, ``timestamp``. | |
| gradcam_path : str, Path or None | |
| Absolute or relative path to a Grad-CAM heatmap image (PNG/JPG). | |
| If ``None``, a placeholder box is drawn instead. | |
| patient_name : str | |
| Leave blank to show a fillable placeholder. | |
| patient_id : str | |
| Leave blank to show a fillable placeholder. | |
| diagnostic_note : str | |
| Free-text note (e.g. from ``report_assistant`` LLM output). | |
| Returns | |
| ------- | |
| Path | |
| The resolved path to the generated PDF file. | |
| """ | |
| stats = prediction_stats or {} | |
| output_path = Path(output_path) | |
| pdf = _ReportPDF(orientation="P", unit="mm", format="A4") | |
| pdf.alias_nb_pages() | |
| pdf.set_auto_page_break(auto=True, margin=15) | |
| pdf.add_page() | |
| # - 1. Patient Information - | |
| _section_heading(pdf, "PATIENT INFORMATION") | |
| _label_value_row(pdf, "Patient Name:", patient_name or "______________________________") | |
| _label_value_row(pdf, "Patient ID:", patient_id or "______________________________") | |
| _label_value_row(pdf, "Report Date:", datetime.now().strftime("%Y-%m-%d %H:%M")) | |
| _label_value_row(pdf, "Referring Physician:", "______________________________") | |
| pdf.ln(4) | |
| # - 2. Analysis Results - | |
| _section_heading(pdf, "ANALYSIS RESULTS") | |
| tumor_class = stats.get("tumor_class", "-") | |
| verdict = "Tumor Detected" if tumor_class.lower() != "no_tumor" else "No Abnormalities Detected" | |
| _label_value_row(pdf, "Diagnosis:", verdict) | |
| _label_value_row( | |
| pdf, "Confidence:", | |
| f"{stats.get('unified_confidence', stats.get('prediction_confidence', '-'))}%" | |
| ) | |
| if "volume_cm3" in stats: | |
| _label_value_row(pdf, "Estimated Tumor Volume:", f"{stats['volume_cm3']} cm³") | |
| pdf.ln(4) | |
| # - 3. Risk Assessment & Follow Up - | |
| _section_heading(pdf, "RISK ASSESSMENT & FOLLOW-UP") | |
| _label_value_row(pdf, "Risk Score (0-100):", str(stats.get('risk_score', 'N/A'))) | |
| _label_value_row(pdf, "Risk Level:", str(stats.get('risk_level', 'N/A'))) | |
| _label_value_row(pdf, "Recommended Follow-Up:", str(stats.get('follow_up', 'Please consult your doctor.'))) | |
| pdf.ln(4) | |
| # - 4. Grad-CAM Heatmap - | |
| _section_heading(pdf, "VISUALIZATION") | |
| if gradcam_path and Path(gradcam_path).exists(): | |
| # Insert actual image, scaled to fit | |
| img_x = 30 | |
| img_w = 150 | |
| pdf.image(str(gradcam_path), x=img_x, w=img_w) | |
| else: | |
| # Draw placeholder box | |
| box_x, box_y = 30, pdf.get_y() | |
| box_w, box_h = 150, 60 | |
| pdf.set_draw_color(*_TEAL) | |
| pdf.set_line_width(0.5) | |
| pdf.rect(box_x, box_y, box_w, box_h, style="D") | |
| # Label | |
| pdf.set_xy(box_x, box_y + 25) | |
| pdf.set_font("Helvetica", "I", 10) | |
| pdf.set_text_color(*_GRAY) | |
| pdf.cell(box_w, 8, "[ No Heatmap Available ]", align="C") | |
| pdf.set_y(box_y + box_h + 4) | |
| pdf.ln(4) | |
| # - 5. Clinical Disclaimer - | |
| _section_heading(pdf, "CLINICAL DISCLAIMER") | |
| pdf.set_font("Helvetica", "I", 9) | |
| pdf.set_text_color(220, 50, 50) | |
| pdf.multi_cell(0, 5, "DISCLAIMER: This report is generated by an AI research prototype. It is NOT a verified clinical diagnosis. The risk scores and follow-up recommendations are for informational purposes only. Do not make medical decisions based on this report. Please consult a qualified healthcare professional.") | |
| pdf.ln(6) | |
| # - 6. Clinician Signature Block - | |
| _section_heading(pdf, "CLINICIAN REVIEW & SIGNATURE") | |
| pdf.ln(2) | |
| col_w = 90 | |
| start_x = pdf.get_x() | |
| start_y = pdf.get_y() | |
| # Left column | |
| pdf.set_xy(start_x, start_y) | |
| _label_value_row(pdf, "Clinician Name:", "______________________________") | |
| _label_value_row(pdf, "Designation:", "______________________________") | |
| _label_value_row(pdf, "Hospital / Institution:", "______________________________") | |
| # Right column (signature box) | |
| sig_x = start_x + col_w + 10 | |
| pdf.set_draw_color(*_TEAL) | |
| pdf.set_line_width(0.4) | |
| pdf.rect(sig_x, start_y, 80, 25, style="D") | |
| pdf.set_xy(sig_x, start_y + 8) | |
| pdf.set_font("Helvetica", "I", 8) | |
| pdf.set_text_color(*_GRAY) | |
| pdf.cell(80, 6, "Signature", align="C") | |
| pdf.set_y(start_y + 30) | |
| _label_value_row(pdf, "Date of Review:", "______________________________") | |
| pdf_out = Path(output_path) | |
| pdf.output(str(pdf_out)) | |
| return pdf_out | |