~/microbio-pdf/generate_culture_media_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Table, TableStyle, Paragraph,
Spacer, HRFlowable
)
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import KeepTogether
OUTPUT = "/home/daytona/workspace/microbio-pdf/Culture_Media_Quick_Reference.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
topMargin=1.2*cm,
bottomMargin=1.0*cm,
leftMargin=1.2*cm,
rightMargin=1.2*cm,
)
# ββ Colour palette ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
C_HEADER_BG = colors.HexColor("#1a3a5c") # deep navy
C_HEADER_TXT = colors.white
C_TITLE_BG = colors.HexColor("#2e86ab") # steel blue
C_ENRICH_BG = colors.HexColor("#e8f4f8") # light blue
C_SELECT_BG = colors.HexColor("#fff3cd") # amber
C_DIFF_BG = colors.HexColor("#d4edda") # green
C_SPEC_BG = colors.HexColor("#f8d7da") # pink/red
C_TRANS_BG = colors.HexColor("#e2d9f3") # lavender
C_ROW_ALT = colors.HexColor("#f4f9fc")
C_HEMO_BG = colors.HexColor("#fff0e6") # peach
C_BORDER = colors.HexColor("#2e86ab")
C_MNEM_BG = colors.HexColor("#fffbcc") # pale yellow
# ββ Styles βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def S(name, **kw):
defaults = dict(fontName="Helvetica", fontSize=8, leading=10, textColor=colors.black)
defaults.update(kw)
return ParagraphStyle(name, **defaults)
s_maintitle = S("maintitle", fontName="Helvetica-Bold", fontSize=16,
textColor=C_HEADER_TXT, alignment=TA_CENTER, leading=20)
s_subtitle = S("subtitle", fontName="Helvetica", fontSize=9,
textColor=C_HEADER_TXT, alignment=TA_CENTER, leading=12)
s_sec = S("sec", fontName="Helvetica-Bold", fontSize=9,
textColor=C_HEADER_TXT, alignment=TA_CENTER, leading=12)
s_cell = S("cell", fontSize=7.5, leading=10)
s_cellb = S("cellb", fontName="Helvetica-Bold", fontSize=7.5, leading=10)
s_organism = S("organism", fontName="Helvetica-Oblique", fontSize=7.5, leading=10,
textColor=colors.HexColor("#1a3a5c"))
s_mnem = S("mnem", fontName="Helvetica", fontSize=7.5, leading=10,
textColor=colors.HexColor("#5c3a00"))
s_mnemb = S("mnemb", fontName="Helvetica-Bold", fontSize=7.5, leading=10,
textColor=colors.HexColor("#5c3a00"))
s_footer = S("footer", fontSize=7, textColor=colors.grey, alignment=TA_CENTER)
W = A4[0] - 2.4*cm # usable width
def section_header(text, bg=C_TITLE_BG):
tbl = Table([[Paragraph(text, s_sec)]], colWidths=[W])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
return tbl
def make_table(headers, rows, col_widths, row_colors=None):
data = [[Paragraph(h, s_cellb) for h in headers]]
for row in rows:
data.append([Paragraph(str(c), s_cell) for c in row])
t = Table(data, colWidths=col_widths)
style = [
("BACKGROUND", (0,0), (-1,0), C_HEADER_BG),
("TEXTCOLOR", (0,0), (-1,0), C_HEADER_TXT),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7.5),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#bbbbbb")),
("ROWBACKGROUNDS", (0,1), (-1,-1),
row_colors if row_colors else [colors.white, C_ROW_ALT]),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "TOP"),
]
t.setStyle(TableStyle(style))
return t
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story = []
# ββ TITLE BANNER βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
banner_data = [[
Paragraph("π§« MICROBIOLOGY CULTURE MEDIA", s_maintitle),
Paragraph("Quick Reference Card | MBBS Practical Exam", s_subtitle),
]]
banner = Table(banner_data, colWidths=[W])
banner.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_HEADER_BG),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("SPAN", (0,0), (-1,-1)),
]))
story.append(banner)
story.append(Spacer(1, 4))
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 1 β CLASSIFICATION
story.append(section_header("CLASSIFICATION OF CULTURE MEDIA"))
story.append(Spacer(1, 3))
class_headers = ["TYPE", "DEFINITION", "KEY EXAMPLES"]
class_rows = [
["Enriched /\nNon-selective",
"Support growth of MOST organisms; no inhibitory agents",
"Blood Agar, Chocolate Agar, Nutrient Agar, Thioglycolate Broth"],
["Selective",
"Inhibit unwanted organisms; allow target to grow",
"MacConkey, TCBS, Mannitol Salt, LJ, Cetrimide, Wilson-Blair"],
["Differential",
"Distinguish organisms by colony colour/reaction",
"MacConkey (lactose), Blood Agar (hemolysis), XLD, CHROMagar"],
["Enrichment Broth",
"Liquid; enhance growth of target while suppressing others",
"Selenite F (Salmonella), Alkaline Peptone Water (Vibrio)"],
["Transport",
"Maintain viability during transport; no multiplication",
"Cary-Blair, Stuart's, Pike's, Alkaline Peptone Water"],
["Specialized",
"For fastidious / difficult-to-grow organisms",
"BCYE (Legionella), Bordet-Gengou (Bordetella), Cystine-Tellurite"],
]
story.append(make_table(class_headers, class_rows,
[2.8*cm, 6.5*cm, 8.0*cm]))
story.append(Spacer(1, 5))
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 2 β MEDIA vs ORGANISM TABLE (the core of the card)
story.append(section_header("CULTURE MEDIA β ORGANISMS QUICK LOOKUP"))
story.append(Spacer(1, 3))
mo_headers = ["MEDIUM", "TYPE", "ORGANISM(S) GROWN", "COLONY APPEARANCE / KEY FEATURE"]
mo_rows = [
["Blood Agar (BA)",
"Enriched",
"Most bacteria & fungi",
"Ξ±-hemolysis=green; Ξ²-hemolysis=clear; Ξ³=no lysis"],
["Chocolate Agar",
"Enriched",
"Haemophilus influenzae\nNeisseria gonorrhoeae",
"Releases factors X & V on heating; grey-brown colour"],
["Nutrient Agar (NA)",
"Basic/Universal",
"Non-fastidious bacteria",
"White/cream colonies; no blood or special additions"],
["Mueller-Hinton Agar",
"Enriched",
"Antibiotic sensitivity testing",
"Standard medium per CLSI/EUCAST for disk diffusion"],
["Sabouraud's Dextrose\nAgar (SDA)",
"Selective (fungi)",
"Candida, Aspergillus,\ndermatophytes",
"pH 5.6; creamy/fluffy colonies; cycloheximide added"],
["Thioglycolate Broth",
"Enriched (anaerobic)",
"Anaerobes + aerobes",
"Oxygen gradient; pink dye at top = aerobic zone"],
["MacConkey Agar",
"Selective + Differential",
"Gram-negative bacteria",
"Pink=lactose+ve (E. coli); Pale/colorless=lactose-ve (Salmonella, Shigella)"],
["Mannitol Salt Agar\n(MSA)",
"Selective + Differential",
"Staphylococci",
"7.5% NaCl; Yellow=S. aureus (mannitol+); Pink/Red=CONS"],
["XLD Agar",
"Selective + Differential",
"Salmonella & Shigella",
"Black+halo=Salmonella (HβS); Red=Shigella; Yellow=coliforms"],
["TCBS Agar",
"Selective + Differential",
"Vibrio species",
"Yellow=V. cholerae (sucrose+); Blue-green=V. parahaemolyticus"],
["Wilson-Blair / BSA",
"Selective + Differential",
"Salmonella typhi",
"Black metallic sheen colonies (HβS + FeΒ²βΊ β FeS)"],
["CLED Agar",
"Selective + Differential",
"Urinary pathogens",
"Inhibits Proteus swarming; lactose fermenters = yellow"],
["Cetrimide Agar",
"Selective",
"Pseudomonas aeruginosa",
"Fluorescent green pigment (pyocyanin); fruity smell"],
["LJ Medium\n(Lowenstein-Jensen)",
"Selective",
"Mycobacterium tuberculosis",
"Rough, buff/cream colonies; 4-8 weeks growth"],
["Middlebrook 7H10/11",
"Selective",
"Mycobacteria (faster)",
"Agar-based; faster than LJ; used with OADC supplement"],
["Bordet-Gengou Agar",
"Specialized",
"Bordetella pertussis",
"Mercury-drop colonies; 50% blood content"],
["Regan-Lowe Agar",
"Specialized",
"Bordetella pertussis",
"Improved variant; charcoal replaces blood"],
["BCYE Agar",
"Specialized",
"Legionella pneumophila\nNocardia",
"Requires L-cysteine + iron; ground-glass colonies"],
["Cystine-Tellurite Agar",
"Specialized",
"Corynebacterium diphtheriae",
"Black/grey colonies due to tellurite reduction"],
["Selenite F Broth",
"Enrichment",
"Salmonella (in stool)",
"Liquid; used as primary enrichment before XLD plating"],
["Alkaline Peptone Water\n(APW)",
"Enrichment / Transport",
"Vibrio cholerae",
"pH 8.6 favours Vibrio; 6-8 hrs incubation before TCBS"],
]
story.append(make_table(mo_headers, mo_rows,
[3.4*cm, 2.5*cm, 3.6*cm, 7.8*cm]))
story.append(Spacer(1, 5))
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 3 β BLOOD AGAR HEMOLYSIS + ORGANISMS (side by side)
story.append(section_header("BLOOD AGAR HEMOLYSIS PATTERNS"))
story.append(Spacer(1, 3))
hemo_headers = ["TYPE", "APPEARANCE", "ZONE", "ORGANISMS"]
hemo_rows = [
["Alpha (Ξ±)", "Greenish partial lysis", "Incomplete, green", "S. viridans, S. pneumoniae, H. influenzae"],
["Beta (Ξ²)", "Complete clear zone", "Complete, colourless", "S. pyogenes (Grp A), S. aureus, S. agalactiae (Grp B)"],
["Gamma (Ξ³)", "No hemolysis", "None", "Enterococcus faecalis, S. bovis"],
]
story.append(make_table(hemo_headers, hemo_rows,
[2.0*cm, 4.5*cm, 3.5*cm, 7.3*cm],
row_colors=[C_ENRICH_BG, C_DIFF_BG, C_SELECT_BG]))
story.append(Spacer(1, 5))
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 4 β MNEMONICS + VIVA TIPS (2-column layout)
story.append(section_header("MNEMONICS & HIGH-YIELD VIVA POINTS"))
story.append(Spacer(1, 3))
mnem_data = [
["π¬ MNEMONICS", "β‘ VIVA QUICK ANSWERS"],
[
Paragraph(
"<b>\"Chocolate for the Hungry Neisseria\"</b><br/>"
"β Chocolate agar for <i>H. influenzae</i> & <i>Neisseria</i> (need X & V factors)<br/><br/>"
"<b>\"Mac is Negative, Pink is Positive\"</b><br/>"
"β MacConkey: pink = lactose fermenter (E. coli)<br/><br/>"
"<b>\"Salmonella goes Black on XLD\"</b><br/>"
"β Black due to HβS + ferric ammonium citrate β FeS<br/><br/>"
"<b>\"Salt loves Staph\"</b><br/>"
"β 7.5% NaCl in MSA selects Staphylococcus only<br/><br/>"
"<b>\"LJ = Lengthy Journey\"</b><br/>"
"β M. tuberculosis takes 4β8 weeks on LJ medium<br/><br/>"
"<b>\"BCYE = Bacteria Crave Your Extra nutrition\"</b><br/>"
"β Legionella needs L-cysteine + iron to grow<br/><br/>"
"<b>\"Yellow Vibrio on TCBS\"</b><br/>"
"β V. cholerae = yellow (sucrose+); V. parahaemolyticus = blue-green",
s_mnem),
Paragraph(
"<b>Q: Medium for antibiotic sensitivity testing?</b><br/>"
"β Mueller-Hinton Agar (CLSI standard)<br/><br/>"
"<b>Q: Medium for blood culture (enrichment)?</b><br/>"
"β BHI broth or Tryptic Soy Broth (TSB)<br/><br/>"
"<b>Q: Significant bacteriuria?</b><br/>"
"β β₯10β΅ CFU/mL (Kass criteria), CLED agar used<br/><br/>"
"<b>Q: Why is SDA used for fungi?</b><br/>"
"β Low pH (5.6) + antibiotics suppress bacteria<br/><br/>"
"<b>Q: What is a transport medium?</b><br/>"
"β Maintains viability without allowing growth<br/>"
"β Examples: Cary-Blair (stool), Stuart's (swabs)<br/><br/>"
"<b>Q: Why is MacConkey selective for Gram-negatives?</b><br/>"
"β Bile salts + crystal violet inhibit Gram-positive organisms<br/><br/>"
"<b>Q: Which medium prevents Proteus swarming?</b><br/>"
"β CLED agar (lacks electrolytes needed for swarming)",
s_mnem),
]
]
mnem_tbl = Table(mnem_data, colWidths=[W/2 - 2, W/2 - 2])
mnem_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_HEADER_BG),
("TEXTCOLOR", (0,0), (-1,0), C_HEADER_TXT),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 8),
("ALIGN", (0,0), (-1,0), "CENTER"),
("BACKGROUND", (0,1), (0,1), C_MNEM_BG),
("BACKGROUND", (1,1), (1,1), C_ENRICH_BG),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#cccccc")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
]))
story.append(mnem_tbl)
story.append(Spacer(1, 6))
# ββ FOOTER βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(HRFlowable(width=W, thickness=0.5, color=C_BORDER))
story.append(Spacer(1, 2))
story.append(Paragraph(
"Sources: Medical Microbiology 9e (Murray) Β· Sherris & Ryan's Medical Microbiology 8e Β· Jawetz, Melnick & Adelberg's Medical Microbiology 28e | For MBBS Practical Exam Preparation",
s_footer))
# ββ BUILD βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc.build(story)
print(f"PDF written β {OUTPUT}")