Tri-Netra-AI / generate_pdf.py
anannyavyas1's picture
Remove GitHub and HF links
427e92d verified
Raw
History Blame Contribute Delete
8.3 kB
"""
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