~/cxr-reference-card/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Image, Table, TableStyle,
PageBreak, HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.pdfgen import canvas
from reportlab.platypus.flowables import Flowable
import os
IMG_DIR = "/home/daytona/workspace/cxr-reference-card/images"
OUT_PATH = "/home/daytona/workspace/cxr-reference-card/CXR_Quick_Reference.pdf"
# ββ Colors ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
DARK_BLUE = colors.HexColor("#1A3A5C")
MED_BLUE = colors.HexColor("#2E6DA4")
LIGHT_BLUE = colors.HexColor("#D6E8F7")
ACCENT_RED = colors.HexColor("#C0392B")
ACCENT_GRN = colors.HexColor("#1A7A4A")
YELLOW_HL = colors.HexColor("#FFF3CD")
WHITE = colors.white
LIGHT_GREY = colors.HexColor("#F5F5F5")
MID_GREY = colors.HexColor("#CCCCCC")
DARK_GREY = colors.HexColor("#444444")
BLACK = colors.black
PAGE_W, PAGE_H = A4
MARGIN = 1.5 * cm
# ββ Styles βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
styles = getSampleStyleSheet()
title_style = ParagraphStyle(
"MainTitle",
fontSize=22, fontName="Helvetica-Bold",
textColor=WHITE, alignment=TA_CENTER,
spaceAfter=4, leading=28
)
subtitle_style = ParagraphStyle(
"SubTitle",
fontSize=11, fontName="Helvetica",
textColor=LIGHT_BLUE, alignment=TA_CENTER,
spaceAfter=2
)
section_head = ParagraphStyle(
"SectionHead",
fontSize=13, fontName="Helvetica-Bold",
textColor=WHITE, alignment=TA_LEFT,
leftIndent=6, spaceAfter=0, leading=18
)
card_title = ParagraphStyle(
"CardTitle",
fontSize=11, fontName="Helvetica-Bold",
textColor=DARK_BLUE, spaceAfter=3, leading=14
)
body_style = ParagraphStyle(
"Body",
fontSize=8.5, fontName="Helvetica",
textColor=DARK_GREY, leading=12, spaceAfter=2
)
caption_style = ParagraphStyle(
"Caption",
fontSize=7.5, fontName="Helvetica-Oblique",
textColor=colors.HexColor("#666666"),
alignment=TA_CENTER, spaceAfter=4, leading=10
)
table_header_style = ParagraphStyle(
"TblHdr",
fontSize=8, fontName="Helvetica-Bold",
textColor=WHITE, alignment=TA_CENTER, leading=10
)
table_cell_style = ParagraphStyle(
"TblCell",
fontSize=7.5, fontName="Helvetica",
textColor=DARK_GREY, leading=10, alignment=TA_LEFT
)
emergency_style = ParagraphStyle(
"Emergency",
fontSize=8.5, fontName="Helvetica-Bold",
textColor=ACCENT_RED, leading=11
)
tip_style = ParagraphStyle(
"Tip",
fontSize=8, fontName="Helvetica",
textColor=colors.HexColor("#7B4F00"), leading=11
)
# ββ Header / Footer canvas βββββββββββββββββββββββββββββββββββββββββββββββββββ
def add_page_deco(canvas_obj, doc):
canvas_obj.saveState()
# Footer
canvas_obj.setFillColor(DARK_BLUE)
canvas_obj.rect(0, 0, PAGE_W, 1.0*cm, fill=1, stroke=0)
canvas_obj.setFillColor(WHITE)
canvas_obj.setFont("Helvetica", 7)
canvas_obj.drawCentredString(
PAGE_W/2, 0.35*cm,
"Sources: Harrison's IM 22E | Fishman's Pulmonary | Murray & Nadel | Grainger & Allison | Fuster & Hurst's Heart 15E"
)
canvas_obj.drawRightString(PAGE_W - MARGIN, 0.35*cm, f"Page {doc.page}")
canvas_obj.restoreState()
# ββ Helper: coloured section banner βββββββββββββββββββββββββββββββββββββββββ
def section_banner(text, color=DARK_BLUE):
data = [[Paragraph(text, section_head)]]
t = Table(data, colWidths=[PAGE_W - 2*MARGIN])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [4]),
]))
return t
# ββ Helper: image with caption βββββββββββββββββββββββββββββββββββββββββββββββ
def img_with_caption(fname, caption, width=7.5*cm):
path = os.path.join(IMG_DIR, fname)
if not os.path.exists(path):
return Paragraph(f"[Image not found: {fname}]", body_style)
try:
img = Image(path, width=width, height=width*0.72, kind="proportional")
cap = Paragraph(caption, caption_style)
data = [[img], [cap]]
t = Table(data, colWidths=[width + 0.4*cm])
t.setStyle(TableStyle([
("ALIGN", (0,0), (-1,-1), "CENTER"),
("BACKGROUND", (0,0), (0,0), colors.HexColor("#1A1A1A")),
("BACKGROUND", (0,1), (0,1), LIGHT_GREY),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 3),
("RIGHTPADDING", (0,0), (-1,-1), 3),
("BOX", (0,0), (-1,-1), 1, MID_GREY),
]))
return t
except Exception as e:
return Paragraph(f"[Error loading {fname}: {e}]", body_style)
# ββ Helper: pathology table βββββββββββββββββββββββββββββββββββββββββββββββββββ
def make_table(headers, rows, col_widths, header_color=MED_BLUE):
hdr = [Paragraph(h, table_header_style) for h in headers]
data = [hdr]
for row in rows:
data.append([Paragraph(str(c), table_cell_style) for c in row])
t = Table(data, colWidths=col_widths, repeatRows=1)
style = [
("BACKGROUND", (0,0), (-1,0), header_color),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("ALIGN", (0,0), (-1,-1), "LEFT"),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7.5),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("GRID", (0,0), (-1,-1), 0.4, MID_GREY),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_GREY]),
("VALIGN", (0,0), (-1,-1), "TOP"),
]
t.setStyle(TableStyle(style))
return t
# ββ Build content βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def build_pdf():
doc = SimpleDocTemplate(
OUT_PATH,
pagesize=A4,
rightMargin=MARGIN, leftMargin=MARGIN,
topMargin=MARGIN, bottomMargin=1.6*cm,
title="Chest X-Ray Quick Reference Card",
author="Orris Medical Library"
)
story = []
CW = PAGE_W - 2*MARGIN # content width
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# PAGE 1 β COVER + NORMAL CXR + BASICS
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Cover banner
cover_data = [[
Paragraph("π« CHEST X-RAY", title_style),
Paragraph("Quick Reference Card", subtitle_style),
Paragraph("Basic se Advance β Visual Guide", subtitle_style),
]]
cover = Table([[
Paragraph("π« CHEST X-RAY", title_style)],
[Paragraph("Quick Reference Card | Basic se Advance β Visual Guide", subtitle_style)
]], colWidths=[CW])
cover.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 12),
("BOTTOMPADDING", (0,0), (-1,-1), 12),
("LEFTPADDING", (0,0), (-1,-1), 10),
("ROUNDEDCORNERS", [6]),
]))
story.append(cover)
story.append(Spacer(1, 0.4*cm))
# X-Ray Density cheatsheet
story.append(section_banner("X-Ray Density β Kya Color Kya Matlab?", MED_BLUE))
story.append(Spacer(1, 0.2*cm))
density_rows = [
["Jet Black", "Air / Pneumothorax (most lucent)"],
["Dark Grey/Black", "Normal aerated lung"],
["Grey", "Soft tissue β heart, muscles, liver"],
["White (opaque)", "Fluid, pus, blood, tumor, collapse"],
["Bright White", "Bone β ribs, sternum, clavicles"],
["Black hole in White", "Cavity β TB abscess, lung cancer"],
]
story.append(make_table(
["Color on X-Ray", "Matlab (Meaning)"],
density_rows,
[5.5*cm, CW - 5.5*cm - 0.1*cm],
header_color=MED_BLUE
))
story.append(Spacer(1, 0.35*cm))
# Normal CXR + Normal anatomy table side by side
story.append(section_banner("NORMAL CHEST X-RAY β Baseline", ACCENT_GRN))
story.append(Spacer(1, 0.25*cm))
normal_img = img_with_caption(
"normal_cxr.png",
"Normal PA (left) + Lateral (right) CXR\n(Harrison's IM 22E)",
width=9.5*cm
)
normal_tbl = make_table(
["Structure", "Normal Finding"],
[
["Trachea", "Midline, visible above carina"],
["Carina angle", "<70Β° (LA enlargement mein splayed)"],
["Lung fields", "Bilateral equal black (aerated)"],
["Heart (CTR)", "<0.5 (50%) on PA erect film"],
["Right border", "Right atrium (RA)"],
["Left border (up)","Aortic knuckle + Pulm trunk"],
["Left border (dn)","Left ventricle (LV)"],
["Diaphragm", "Right > Left (liver neeche)"],
["CP angles", "Sharp, pointed β blunt = effusion"],
["Hila", "Symmetric, left slightly higher"],
],
[3.2*cm, CW - 9.8*cm - 0.2*cm],
header_color=ACCENT_GRN
)
row = Table(
[[normal_img, normal_tbl]],
colWidths=[9.8*cm, CW - 9.8*cm]
)
row.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 4),
]))
story.append(row)
story.append(Spacer(1, 0.35*cm))
# ABCDE systematic approach
story.append(section_banner("SYSTEMATIC APPROACH β ABCDE Method", DARK_BLUE))
story.append(Spacer(1, 0.2*cm))
abcde_rows = [
["A β Airway", "Trachea midline? ETT position? Carina angle?"],
["B β Bones", "Rib fractures? Clavicles? Scapulae? Soft tissue?"],
["C β Cardiac", "CTR <0.5? Heart shape? Borders clear?"],
["D β Diaphragm", "Right > Left? CP angles sharp? Air under = perforation!"],
["E β Everything", "Lung fields? Hila? Mediastinum? Pleural space? Tubes/lines?"],
]
story.append(make_table(
["Step", "Check Karo"],
abcde_rows,
[3.2*cm, CW - 3.2*cm - 0.1*cm],
header_color=DARK_BLUE
))
story.append(PageBreak())
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# PAGE 2 β PNEUMOTHORAX + PLEURAL EFFUSION
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_banner("1. PNEUMOTHORAX", DARK_BLUE))
story.append(Spacer(1, 0.25*cm))
ptx_img = img_with_caption(
"pneumothorax.png",
"Spontaneous PTX β visceral pleural\nline clearly visible (Fishman's Pulmonary)",
width=8.5*cm
)
ptx_tbl = make_table(
["Feature", "Finding"],
[
["Main sign", "Visceral pleural line β thin white line, NO lung markings beyond"],
["Color", "Hyperlucent (extra black) on affected side"],
["CP angle", "Sharp (air, not fluid)"],
["Trachea", "Midline in simple; OPPOSITE shift in Tension PTX"],
["Diaphragm", "Depressed in Tension PTX"],
["Erect film", "Air at APEX (best seen)"],
["Supine film", "\"Deep sulcus sign\" β very deep lateral CP angle"],
["Causes", "Trauma, tall young males (spontaneous), COPD bullae"],
],
[2.8*cm, CW - 8.8*cm - 0.2*cm],
header_color=DARK_BLUE
)
ptx_row = Table([[ptx_img, ptx_tbl]], colWidths=[8.8*cm, CW - 8.8*cm])
ptx_row.setStyle(TableStyle([("VALIGN",(0,0),(-1,-1),"TOP"),("LEFTPADDING",(0,0),(-1,-1),0),("RIGHTPADDING",(0,0),(-1,-1),4)]))
story.append(ptx_row)
story.append(Spacer(1, 0.15*cm))
story.append(Table([[Paragraph(
"β TENSION PNEUMOTHORAX EMERGENCY: Trachea shifts AWAY + depressed diaphragm + hemodynamic collapse β immediate needle decompression!",
emergency_style
)]], colWidths=[CW], style=[
("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#FDECEA")),
("BOX",(0,0),(-1,-1),1,ACCENT_RED),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),8),
]))
story.append(Spacer(1, 0.4*cm))
# Pleural effusion
story.append(section_banner("2. PLEURAL EFFUSION", MED_BLUE))
story.append(Spacer(1, 0.25*cm))
eff_img = img_with_caption(
"pleural_effusion.png",
"Right pleural effusion (A) β after\ndrainage catheter placement (B)\n(Murray & Nadel's Respiratory Medicine)",
width=9*cm
)
eff_tbl = make_table(
["Feature", "Finding"],
[
["Main sign", "BLUNTED CP angle β pehla aur sabse important sign"],
["Shape", "Meniscus sign β concave upper border (hallmark)"],
["Color", "White opacity at base, affected side"],
["Trachea", "Massive effusion mein OPPOSITE side shift"],
["Threshold", ">200-300 mL PA film mein dikh ta hai"],
["Supine film", "Diffuse haze entire side β diagnose karna mushkil"],
["Lat decubitus","Freely mobile fluid = effusion confirmed"],
["Causes", "CHF, TB, malignancy, parapneumonic, hypoalbuminemia"],
],
[2.8*cm, CW - 9.3*cm - 0.2*cm],
header_color=MED_BLUE
)
eff_row = Table([[eff_img, eff_tbl]], colWidths=[9.3*cm, CW - 9.3*cm])
eff_row.setStyle(TableStyle([("VALIGN",(0,0),(-1,-1),"TOP"),("LEFTPADDING",(0,0),(-1,-1),0),("RIGHTPADDING",(0,0),(-1,-1),4)]))
story.append(eff_row)
story.append(PageBreak())
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# PAGE 3 β PULMONARY EDEMA + CARDIOMEGALY
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_banner("3. PULMONARY EDEMA / CHF", MED_BLUE))
story.append(Spacer(1, 0.25*cm))
ped_img = img_with_caption(
"pulm_edema_kerleyb.png",
"CHF β Kerley B lines (arrows at periphery),\ncardiomegaly, bilateral lower zone haze\n(Fuster & Hurst's Heart 15E)",
width=7.5*cm
)
ped_tbl = make_table(
["Stage/Sign", "CXR Finding"],
[
["Cephalization", "Upper lobe veins MORE prominent than lower (gravity reversed)"],
["Kerley B lines", "Short horizontal white lines at lung BASE/periphery (dilated lymphatics) β
"],
["Bat-wing/Butterfly","Bilateral PERIHILAR white haze β severe edema"],
["Bronchial cuffing","Bronchial walls appear THICKENED (peribronchial edema)"],
["Heart size", "Cardiomegaly β CTR >0.5"],
["Pleural effusion", "Bilateral, RIGHT > LEFT"],
["Distribution", "Bilateral, symmetric, perihilar"],
["vs Pneumonia", "CHF = bilateral + cardiomegaly; Pneumonia = unilateral + fever"],
],
[3.5*cm, CW - 7.8*cm - 0.2*cm],
header_color=MED_BLUE
)
ped_row = Table([[ped_img, ped_tbl]], colWidths=[7.8*cm, CW - 7.8*cm])
ped_row.setStyle(TableStyle([("VALIGN",(0,0),(-1,-1),"TOP"),("LEFTPADDING",(0,0),(-1,-1),0),("RIGHTPADDING",(0,0),(-1,-1),4)]))
story.append(ped_row)
story.append(Spacer(1, 0.4*cm))
story.append(section_banner("4. CARDIOMEGALY & CARDIAC ENLARGEMENT", colors.HexColor("#6B2D8B")))
story.append(Spacer(1, 0.25*cm))
card_img = img_with_caption(
"cardiomegaly_chf.png",
"Gross cardiomegaly β mitral & tricuspid\nregurgitation. Carina splayed (lines).\nKerley B lines (arrows). LA = double density.\n(Fuster & Hurst's Heart 15E)",
width=7.5*cm
)
card_tbl = make_table(
["Chamber/Condition", "CXR Sign"],
[
["CTR >0.5", "CARDIOMEGALY (PA erect film mein measure karo)"],
["Left Ventricle β", "Left lower border ROUNDED, apex down-and-out"],
["Left Atrium β", "Double density right border, carina splaying >70Β°, LA appendage"],
["Right Ventricle β", "Lateral view: retrosternal space fill ho jaata hai"],
["Pericardial effusion", "FLASK / WATER-BOTTLE heart β all borders globally rounded"],
["Aortic stenosis", "Post-stenotic dilatation β ascending aorta right upper border"],
["Pulmonary hypertension", "Bilateral enlarged hila + peripheral pruning"],
],
[4.5*cm, CW - 7.8*cm - 0.2*cm],
header_color=colors.HexColor("#6B2D8B")
)
card_row = Table([[card_img, card_tbl]], colWidths=[7.8*cm, CW - 7.8*cm])
card_row.setStyle(TableStyle([("VALIGN",(0,0),(-1,-1),"TOP"),("LEFTPADDING",(0,0),(-1,-1),0),("RIGHTPADDING",(0,0),(-1,-1),4)]))
story.append(card_row)
story.append(PageBreak())
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# PAGE 4 β TUBERCULOSIS (Cavitary + Miliary + HIV)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_banner("5. CAVITARY TUBERCULOSIS", ACCENT_RED))
story.append(Spacer(1, 0.25*cm))
ctb_img = img_with_caption(
"cavitary_tb.png",
"Cavitary TB β extensive right upper\nlobe cavitation (Fishman's Pulmonary)",
width=7.2*cm
)
ctb_tbl = make_table(
["Feature", "Finding"],
[
["Location", "UPPER LOBE dominant (right > left) + superior segment lower lobe"],
["Primary TB", "Lower/mid zone infiltrate + hilar adenopathy + pleural effusion"],
["Reactivation TB", "APICAL CONSOLIDATION + CAVITY (hallmark) β
"],
["Cavity appearance", "Thick-walled, irregular β kaali hole white opacity ke andar"],
["Bronchogenic spread","Centrilobular nodules β tree-in-bud pattern"],
["Healed TB", "Calcified foci + volume loss + fibrosis upper lobes"],
["Pleural effusion", "TB pleuritis mein β lymphocyte exudate"],
],
[3.5*cm, CW - 7.5*cm - 0.2*cm],
header_color=ACCENT_RED
)
ctb_row = Table([[ctb_img, ctb_tbl]], colWidths=[7.5*cm, CW - 7.5*cm])
ctb_row.setStyle(TableStyle([("VALIGN",(0,0),(-1,-1),"TOP"),("LEFTPADDING",(0,0),(-1,-1),0),("RIGHTPADDING",(0,0),(-1,-1),4)]))
story.append(ctb_row)
story.append(Spacer(1, 0.4*cm))
# Miliary TB + TB-HIV side by side
story.append(section_banner("6. MILIARY TB & 7. TB with HIV β Hilar Adenopathy", colors.HexColor("#C0711A")))
story.append(Spacer(1, 0.25*cm))
mil_img = img_with_caption(
"miliary_tb.png",
"Miliary TB β 1-3 mm random\nbilateral nodules (Murray & Nadel)",
width=7*cm
)
hiv_img = img_with_caption(
"tb_hiv_hilar.png",
"TB + HIV β bilateral hilar adenopathy\n(arrows) + parenchymal nodules\n(Murray & Nadel)",
width=7*cm
)
mil_tbl = make_table(
["Feature", "Finding"],
[
["Size", "1-3 mm uniform nodules"],
["Distribution", "Random, bilateral, uniform"],
["Pattern", "Millet seeds β fine stippling"],
["Cause", "TB (most common), metastasis,\nhistoplasmosis, sarcoidosis"],
],
[2.8*cm, 3.8*cm],
header_color=colors.HexColor("#C0711A")
)
hiv_tbl = make_table(
["Feature", "Finding"],
[
["Hilar", "Bilateral enlarged, lobular"],
["Pattern", "Lower zone infiltrates (atypical)"],
["Sarcoidosis", "\"1-2-3 sign\" bilateral hilar adenopathy"],
["Eggshell calc","Sarcoidosis / silicosis nodes"],
],
[2.8*cm, 3.8*cm],
header_color=colors.HexColor("#C0711A")
)
dual = Table([
[mil_img, Spacer(0.2*cm,1), mil_tbl, Spacer(0.4*cm,1), hiv_img, Spacer(0.2*cm,1), hiv_tbl]
], colWidths=[7.2*cm, 0.2*cm, 6.8*cm, 0.4*cm, 7.2*cm, 0.2*cm, 6.8*cm])
# recalculate β total CW=17cm approx, split each pair
# simpler 4-col
dual2 = Table([
[mil_img, mil_tbl, hiv_img, hiv_tbl]
], colWidths=[7.2*cm, 6.8*cm - 0.2*cm, 7.2*cm, 6.5*cm - 0.2*cm])
dual2.setStyle(TableStyle([
("VALIGN",(0,0),(-1,-1),"TOP"),
("LEFTPADDING",(0,0),(-1,-1),2),
("RIGHTPADDING",(0,0),(-1,-1),4),
]))
story.append(dual2)
story.append(PageBreak())
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# PAGE 5 β MASTER COMPARISON TABLE + TRACHEA RULE
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_banner("MASTER COMPARISON TABLE β Ek Nazar Mein Sabkuch", DARK_BLUE))
story.append(Spacer(1, 0.25*cm))
master_rows = [
["Normal", "Bilateral equal black", "Bilateral", "Midline", "Sharp", "<0.5", "β"],
["Pneumothorax", "Hyperlucent (extra black)", "Unilateral apex", "Normal / Away*","Sharp", "Normal", "Visceral pleural line, no lung markings beyond"],
["Pleural Effusion", "White base opacity", "Unilateral/bilateral","Away* (massive)","BLUNTED β
", "Normal", "Meniscus sign"],
["Pneumonia", "White lobar/segmental patch", "Lobar/segmental", "Normal", "Normal", "Normal", "Air bronchogram β
"],
["Pulmonary Edema", "Bilateral perihilar haze", "Perihilar bilateral", "Normal", "Blunted (bil)","ENLARGED","Kerley B + cephalization β
"],
["Collapse/Atelectasis","White β volume loss", "Affected lobe", "TOWARD β
", "Elevated hemi","Normal", "Fissure displacement, ipsilateral shift"],
["Miliary TB", "Tiny white dots 1-3mm", "Random bilateral", "Normal", "Normal", "Normal", "Uniform millet-seed nodules β
"],
["Cavitary TB", "White + black hole", "Upper lobe β
", "Normal", "Normal", "Normal", "Thick-walled cavity"],
["Cardiomegaly", "Normal lungs", "β", "Normal", "Normal", ">0.5 β
", "Rounded cardiac borders"],
["Pericardial Effusion","Normal lungs", "β", "Normal", "Normal", ">0.5", "FLASK / water-bottle heart β
"],
]
master_hdrs = ["Condition", "Color/Opacity", "Location", "Trachea", "CP Angle", "CTR", "Key Sign β
"]
master_cws = [3.2*cm, 3.5*cm, 3.0*cm, 2.2*cm, 2.0*cm, 1.5*cm, CW-15.4*cm]
mt = Table([
[Paragraph(h, table_header_style) for h in master_hdrs]
] + [
[Paragraph(str(c), table_cell_style) for c in row]
for row in master_rows
], colWidths=master_cws, repeatRows=1)
mt.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_BLUE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7.2),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("GRID", (0,0), (-1,-1), 0.4, MID_GREY),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LIGHT_GREY]),
("VALIGN", (0,0), (-1,-1), "TOP"),
# Highlight emergency rows
("BACKGROUND", (0,1), (-1,1), colors.HexColor("#EBF5FF")), # Normal
("BACKGROUND", (0,2), (-1,2), colors.HexColor("#FFF8E1")), # PTX
("BACKGROUND", (0,5), (-1,5), colors.HexColor("#FFF3CD")), # PNA
("BACKGROUND", (0,6), (-1,6), colors.HexColor("#F3E5F5")), # Edema
]))
story.append(mt)
story.append(Spacer(1, 0.4*cm))
# Trachea Rule box
story.append(section_banner("TRACHEA RULE β Yaad Karo", colors.HexColor("#1A6B3A")))
story.append(Spacer(1, 0.15*cm))
trachea_data = [
[
Paragraph("TRACHEA shifts AWAY from opacity", ParagraphStyle("tr1",fontSize=9,fontName="Helvetica-Bold",textColor=ACCENT_RED,leading=12)),
Paragraph("TRACHEA shifts TOWARD opacity", ParagraphStyle("tr2",fontSize=9,fontName="Helvetica-Bold",textColor=MED_BLUE,leading=12)),
],
[
Paragraph("= SPACE-OCCUPYING lesion\n(Effusion, Tension PTX, Large Tumor)", body_style),
Paragraph("= VOLUME-LOSING lesion\n(Collapse, Fibrosis, Post-pneumonectomy)", body_style),
]
]
trachea_tbl = Table(trachea_data, colWidths=[CW/2 - 0.2*cm, CW/2 - 0.2*cm])
trachea_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), colors.HexColor("#FDECEA")),
("BACKGROUND", (1,0), (1,-1), colors.HexColor("#E3F0FF")),
("BOX", (0,0), (-1,-1), 1, MID_GREY),
("INNERGRID", (0,0), (-1,-1), 0.5, MID_GREY),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(trachea_tbl)
story.append(Spacer(1, 0.35*cm))
# Silhouette sign
story.append(section_banner("SILHOUETTE SIGN β Location Pehchano", colors.HexColor("#5D4037")))
story.append(Spacer(1, 0.15*cm))
sil_rows = [
["Right heart border BLUR", "Right Middle Lobe pathology"],
["Left heart border BLUR", "Lingula (Left Upper Lobe) pathology"],
["Diaphragm border BLUR", "Lower Lobe pathology (adjacent to diaphragm)"],
["Aortic knuckle BLUR", "Left Upper Lobe or adjacent pleural pathology"],
["Descending aorta BLUR", "Left Lower Lobe pathology"],
]
story.append(make_table(
["Border Blur", "Location Indicate Karta Hai"],
sil_rows,
[5.5*cm, CW - 5.5*cm - 0.1*cm],
header_color=colors.HexColor("#5D4037")
))
# ββ Build ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.build(story, onFirstPage=add_page_deco, onLaterPages=add_page_deco)
print(f"PDF created: {OUT_PATH}")
if __name__ == "__main__":
build_pdf()