Ihc studies in lymphoma
| Marker | Lineage/Role | Key Notes |
|---|---|---|
| CD45 (LCA) | Pan-leukocyte | Positive in NHL; negative in classic HL (Reed-Sternberg cells) |
| CD20 | B-cell marker | All mature B-cell lymphomas; therapeutic target for rituximab |
| CD79a | B-cell marker | More sensitive than CD20 in some B-cell lymphomas |
| PAX5 | B-cell transcription factor | Present on Reed-Sternberg cells in classic HL (weak) |
| CD3 | Pan-T-cell marker | T-cell lymphomas |
| CD5 | T-cell/subset B-cell | Also positive in CLL/SLL and mantle cell lymphoma |
| CD10 | Follicle center marker | Follicular lymphoma, DLBCL (GCB type), Burkitt |
| Ki-67 | Proliferation index | Very high (>95%) in Burkitt lymphoma; helps grade |
NLPHL vs. Classic HL distinction by IHC is critical, as NLPHL has a different clinical course and treatment.
| Step | Marker | Interpretation |
|---|---|---|
| 1 | CD10 | If >30% positive → GCB type |
| 2 | BCL6 | If CD10-, BCL6- → non-GCB (ABC) |
| 3 | MUM1 | If BCL6+, MUM1+ → non-GCB (ABC); MUM1- → GCB |
| CD10 | BCL6 | MUM1 | Phenotype |
|---|---|---|---|
| + | + | - | GCB |
| + | - | - | GCB |
| - | + | - | GCB |
| - | + | + | non-GCB (ABC) |
| - | - | + | non-GCB (ABC) |

| Lymphoma | Key IHC Profile |
|---|---|
| Peripheral T-cell lymphoma NOS | CD3+, CD4+ or CD8+, variable loss of CD5/CD7 |
| Anaplastic Large Cell Lymphoma (ALCL) | CD30+, ALK+ (ALK+ has better prognosis), CD3 often negative |
| Angioimmunoblastic T-cell Lymphoma (AITL) | CD3+, CD4+, PD-1+, CXCL13+, CD10+, CD21+ FDC expanded meshwork |
| NK/T-cell Lymphoma, nasal type | CD2+, CD56+, cytoplasmic CD3+, EBV+, granzyme B+ |
| Hepatosplenic T-cell Lymphoma | CD3+, TCRγδ+, CD4-, CD8- (double negative), CD56+ |
| Adult T-cell Leukemia/Lymphoma (ATLL) | CD3+, CD4+, CD25+, CD7 often lost; HTLV-1+ |
Key tip: In T-cell lymphomas, look for aberrant loss of pan-T markers (CD3, CD5, CD7) - this suggests neoplasia. - Henry's Clinical Diagnosis and Management by Laboratory Methods
| Lymphoma | CD20 | CD5 | CD10 | CD23 | Cyclin D1 | CD30 | CD15 | BCL2 | Ki-67 |
|---|---|---|---|---|---|---|---|---|---|
| SLL/CLL | dim+ | + | - | + | - | - | - | + | low |
| MCL | + | + | - | - | + | - | - | + | variable |
| FL | + | - | + | - | - | - | - | + | low-mod |
| DLBCL | + | - | +/- | - | - | - | - | +/- | high |
| Burkitt | + | - | + | - | - | - | - | - | ~100% |
| MALT MZL | + | - | - | - | - | - | - | +/- | low |
| Classic HL (RS) | - | - | - | - | - | + | + | - | - |
| NLPHL (L&H) | + | - | - | - | - | - | - | - | - |
| ALCL | - | - | - | - | - | + | - | - | high |

Generate a quick reference PDF for lymphoma IHC stain interpretation
ls /home/daytona/workspace/ && python3 -c "import reportlab; print('reportlab ok…
mkdir -p /home/daytona/workspace/lymphoma-ihc
~/lymphoma-ihc/generate_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, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate
from reportlab.lib.colors import HexColor
OUTPUT = "/home/daytona/workspace/lymphoma-ihc/Lymphoma_IHC_Quick_Reference.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
NAVY = HexColor("#1B3A6B")
TEAL = HexColor("#1A7A8A")
AMBER = HexColor("#E67E22")
LIGHT_BG = HexColor("#F0F4F8")
HEADER_BG = HexColor("#1B3A6B")
ROW_ALT = HexColor("#E8F0F7")
WHITE = colors.white
BLACK = colors.black
POS_GREEN = HexColor("#1E7E34")
NEG_RED = HexColor("#C0392B")
VAR_AMBER = HexColor("#D4860B")
# ── Styles ───────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
TITLE_STYLE = ParagraphStyle("Title", fontName="Helvetica-Bold", fontSize=22,
textColor=WHITE, alignment=TA_CENTER, spaceAfter=4)
SUBTITLE_STYLE = ParagraphStyle("Subtitle", fontName="Helvetica", fontSize=11,
textColor=HexColor("#B8D4E8"), alignment=TA_CENTER, spaceAfter=2)
DATE_STYLE = ParagraphStyle("Date", fontName="Helvetica-Oblique", fontSize=9,
textColor=HexColor("#B8D4E8"), alignment=TA_CENTER)
SECTION_STYLE = ParagraphStyle("Section", fontName="Helvetica-Bold", fontSize=13,
textColor=WHITE, alignment=TA_LEFT,
leftIndent=6, spaceAfter=0, spaceBefore=0)
BODY_STYLE = ParagraphStyle("Body", fontName="Helvetica", fontSize=8.5,
textColor=BLACK, spaceAfter=3, leading=12)
BODY_BOLD = ParagraphStyle("BodyBold", fontName="Helvetica-Bold", fontSize=8.5,
textColor=BLACK, spaceAfter=3, leading=12)
CAPTION = ParagraphStyle("Caption", fontName="Helvetica-Oblique", fontSize=7.5,
textColor=HexColor("#555555"), spaceAfter=4)
FOOTNOTE = ParagraphStyle("Footnote", fontName="Helvetica-Oblique", fontSize=7,
textColor=HexColor("#666666"), spaceAfter=2)
# cell styles
TH_STYLE = ParagraphStyle("TH", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_CENTER, leading=10)
TD_STYLE = ParagraphStyle("TD", fontName="Helvetica", fontSize=7.8,
textColor=BLACK, alignment=TA_CENTER, leading=10)
TD_LEFT = ParagraphStyle("TDL", fontName="Helvetica", fontSize=7.8,
textColor=BLACK, alignment=TA_LEFT, leading=10)
TD_BOLD = ParagraphStyle("TDB", fontName="Helvetica-Bold", fontSize=7.8,
textColor=BLACK, alignment=TA_LEFT, leading=10)
def pos(text="+"): return Paragraph(f'<font color="#1E7E34"><b>{text}</b></font>', TD_STYLE)
def neg(text="−"): return Paragraph(f'<font color="#C0392B"><b>{text}</b></font>', TD_STYLE)
def var(text="+/−"): return Paragraph(f'<font color="#D4860B"><b>{text}</b></font>', TD_STYLE)
def th(text): return Paragraph(f"<b>{text}</b>", TH_STYLE)
def td(text): return Paragraph(text, TD_STYLE)
def tdl(text): return Paragraph(text, TD_LEFT)
def tdb(text): return Paragraph(f"<b>{text}</b>", TD_BOLD)
def section_bar(title):
"""Returns a coloured section header bar."""
data = [[Paragraph(f"<b>{title}</b>", SECTION_STYLE)]]
t = Table(data, colWidths=[17.7*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [3]),
]))
return t
def base_table_style(header_rows=1):
return [
("BACKGROUND", (0,0), (-1, header_rows-1), NAVY),
("TEXTCOLOR", (0,0), (-1, header_rows-1), WHITE),
("FONTNAME", (0,0), (-1, header_rows-1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8),
("ROWBACKGROUNDS",(0, header_rows), (-1,-1), [WHITE, ROW_ALT]),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#AAAAAA")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]
# ── Header / footer callbacks ────────────────────────────────────────────────
def on_first_page(canvas, doc):
canvas.saveState()
w, h = A4
# navy banner
canvas.setFillColor(NAVY)
canvas.rect(0, h-3.8*cm, w, 3.8*cm, fill=1, stroke=0)
# teal accent stripe
canvas.setFillColor(TEAL)
canvas.rect(0, h-4.1*cm, w, 0.3*cm, fill=1, stroke=0)
canvas.restoreState()
def on_later_pages(canvas, doc):
canvas.saveState()
w, h = A4
# slim top bar
canvas.setFillColor(NAVY)
canvas.rect(0, h-1.2*cm, w, 1.2*cm, fill=1, stroke=0)
canvas.setFont("Helvetica-Bold", 8)
canvas.setFillColor(WHITE)
canvas.drawString(1.5*cm, h-0.85*cm, "LYMPHOMA IHC — QUICK REFERENCE")
canvas.drawRightString(w-1.5*cm, h-0.85*cm, f"Page {doc.page}")
# footer
canvas.setFillColor(NAVY)
canvas.rect(0, 0, w, 0.8*cm, fill=1, stroke=0)
canvas.setFont("Helvetica-Oblique", 7)
canvas.setFillColor(HexColor("#B8D4E8"))
canvas.drawCentredString(w/2, 0.28*cm,
"Sources: Robbins Pathology · Harrison's Internal Medicine 22E · Quick Compendium of Clinical Pathology 5e · Henry's Lab Methods")
canvas.restoreState()
# ── Build document ────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=1.5*cm, rightMargin=1.5*cm,
topMargin=4.5*cm, bottomMargin=1.5*cm,
title="Lymphoma IHC Quick Reference",
author="Orris Medical AI",
)
story = []
# ── Cover header content ──────────────────────────────────────────────────────
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("LYMPHOMA IHC", TITLE_STYLE))
story.append(Paragraph("Quick Reference Guide for Immunohistochemical Stain Interpretation", SUBTITLE_STYLE))
story.append(Paragraph("Based on WHO Classification · Robbins Pathology · Harrison's 22E · Quick Compendium 5e", DATE_STYLE))
story.append(Spacer(1, 0.6*cm))
# ── LEGEND ────────────────────────────────────────────────────────────────────
legend_data = [
[th("Symbol"), th("Meaning")],
[pos("+"), tdl("Positive / Expressed")],
[neg("−"), tdl("Negative / Not expressed")],
[var("+/−"),tdl("Variable / Inconsistent expression")],
[pos("dim+"),tdl("Weakly / dimly positive")],
[td("~100%"),tdl("Near-100% proliferative index (Ki-67)")],
]
legend_table = Table(legend_data, colWidths=[2.5*cm, 6*cm])
legend_table.setStyle(TableStyle(base_table_style()))
story.append(KeepTogether([
section_bar("LEGEND"),
Spacer(1, 0.15*cm),
legend_table,
Spacer(1, 0.5*cm),
]))
# ── SECTION 1: CORE LINEAGE PANEL ────────────────────────────────────────────
lineage_data = [
[th("Marker"), th("Lineage / Role"), th("Key Diagnostic Use")],
[tdb("CD45 (LCA)"), tdl("Pan-leukocyte"), tdl("ALL lymphomas +; Negative in classic Reed-Sternberg cells (HL)")],
[tdb("CD20"), tdl("B-cell"), tdl("Mature B-cell lymphomas; rituximab therapeutic target")],
[tdb("CD79a"), tdl("B-cell"), tdl("Sensitive B-cell marker; positive when CD20 lost (e.g. post-rituximab)")],
[tdb("PAX5"), tdl("B-cell TF"), tdl("Weak + in Reed-Sternberg cells; confirms B origin in classic HL")],
[tdb("CD3"), tdl("Pan-T-cell"), tdl("T-cell lymphomas; surface loss suggests neoplasia")],
[tdb("CD5"), tdl("T-cell + B subset"), tdl("CLL/SLL and MCL among B-cell lymphomas; absent in FL/DLBCL")],
[tdb("CD10"), tdl("Follicle center"), tdl("FL, DLBCL-GCB, Burkitt, AITL; absent in MCL/MZL/CLL")],
[tdb("BCL6"), tdl("GC B-cell TF"), tdl("FL, DLBCL-GCB; AITL (T-cells); NLPHL L&H cells")],
[tdb("MUM1/IRF4"), tdl("Post-GC/plasma cell"), tdl("DLBCL-ABC subtype; plasma cell myeloma; ALCL")],
[tdb("BCL2"), tdl("Anti-apoptotic"), tdl("FL (t14;18), DLBCL; Negative in Burkitt (key distinction)")],
[tdb("Cyclin D1"), tdl("Cell cycle"), tdl("Hallmark of MCL t(11;14); also hairy cell leukemia")],
[tdb("CD30"), tdl("Activation marker"), tdl("Classic HL RS cells, ALCL, lymphomatoid papulosis")],
[tdb("CD15"), tdl("Granulocyte/RS"), tdl("Classic HL RS cells 75–85%; absent in NLPHL")],
[tdb("Ki-67"), tdl("Proliferation"), tdl("~100% Burkitt; high in DLBCL/ALCL; low in FL/SLL/MCL")],
[tdb("EBV (LMP-1)"),tdl("Viral marker"), tdl("HL mixed-cellularity 70%, lymphocyte-depleted >90%; NK/T lymphoma")],
]
lineage_table = Table(lineage_data, colWidths=[2.6*cm, 3.1*cm, 12*cm])
lineage_table.setStyle(TableStyle(base_table_style()))
story.append(KeepTogether([
section_bar("1 · CORE LINEAGE & DIAGNOSTIC MARKERS"),
Spacer(1, 0.15*cm),
lineage_table,
Spacer(1, 0.5*cm),
]))
# ── SECTION 2: MASTER IHC TABLE ──────────────────────────────────────────────
# Columns: Lymphoma | CD20 | CD5 | CD10 | CD23 | CycD1 | BCL2 | BCL6 | CD30 | CD15 | CD45 | Ki-67
master_headers = [th(x) for x in [
"Lymphoma", "CD20","CD5","CD10","CD23","CyclinD1","BCL2","BCL6","CD30","CD15","CD45","Ki-67"
]]
master_data = [master_headers,
[tdb("SLL / CLL"), pos("dim+"), pos(), neg(), pos(), neg(), pos(), neg(), neg(), neg(), pos(), td("Low")],
[tdb("MCL"), pos(), pos(), neg(), neg(), pos("✓"),pos(), neg(), neg(), neg(), pos(), td("Var")],
[tdb("Follicular"), pos(), neg(), pos(), neg(), neg(), pos("✓"),pos(), neg(), neg(), pos(), td("Low")],
[tdb("MALT/MZL"), pos(), neg(), neg(), neg(), neg(), var(), neg(), neg(), neg(), pos(), td("Low")],
[tdb("DLBCL-GCB"), pos(), neg(), pos(), neg(), neg(), var(), pos(), neg(), neg(), pos(), td("High")],
[tdb("DLBCL-ABC"), pos(), neg(), neg(), neg(), neg(), var(), neg(), neg(), neg(), pos(), td("High")],
[tdb("Burkitt"), pos(), neg(), pos(), neg(), neg(), neg("✓"),pos(), neg(), neg(), pos(), td("~100%")],
[tdb("Classic HL"), neg(), neg(), neg(), neg(), neg(), neg(), neg(), pos("✓"),pos("✓"),neg("✓"),td("Var")],
[tdb("NLPHL"), pos("✓"), neg(), neg(), neg(), neg(), neg(), pos(), neg(), neg(), pos(), td("Low")],
[tdb("ALCL (ALK+)"), neg(), neg(), neg(), neg(), neg(), neg(), neg(), pos("✓"),neg(), pos(), td("High")],
[tdb("PTCL-NOS"), neg(), neg(), neg(), neg(), neg(), neg(), neg(), neg(), neg(), pos(), td("High")],
[tdb("NK/T-cell"), neg(), neg(), neg(), neg(), neg(), neg(), neg(), neg(), neg(), pos(), td("High")],
]
# Highlight special "key" cells
col_widths = [2.8*cm, 1.3*cm, 1.1*cm, 1.1*cm, 1.15*cm, 1.5*cm, 1.15*cm, 1.15*cm, 1.15*cm, 1.15*cm, 1.15*cm, 1.3*cm]
master_table = Table(master_data, colWidths=col_widths)
ts = base_table_style()
# mark key diagnostics with light gold
key_cells = [
(5,2),(5,3),(5,4),(5,5), # MCL cyclin D1
(6,3),(6,7), # FL CD10, BCL2
(9,11), # Burkitt Ki67
(8,9),(8,10),(8,11), # Classic HL CD30 CD15 CD45
(9,8), # NLPHL CD20
(10,9), # ALCL CD30
]
for (row,col) in key_cells:
ts.append(("BACKGROUND", (col,row), (col,row), HexColor("#FFF3CD")))
master_table.setStyle(TableStyle(ts))
story.append(KeepTogether([
section_bar("2 · MASTER IHC PROFILE TABLE"),
Spacer(1, 0.15*cm),
master_table,
Spacer(1, 0.15*cm),
Paragraph("✓ = hallmark/diagnostic marker | Highlighted cells = key distinguishing markers", CAPTION),
Spacer(1, 0.4*cm),
]))
# ── SECTION 3: HODGKIN LYMPHOMA ───────────────────────────────────────────────
hl_data = [
[th("Subtype"), th("% of HL"), th("RS Cell Variant"), th("IHC Profile"), th("EBV"), th("Notes")],
[tdb("Nodular Sclerosis"), td("65–70%"), tdl("Lacunar cells"), tdl("CD30+, CD15+, PAX5+w, CD45−"), td("Rare"), tdl("Young adults; mediastinal disease")],
[tdb("Mixed Cellularity"), td("20–25%"), tdl("Classic RS cells"), tdl("CD30+, CD15+, PAX5+w, CD45−"), td("~70%"), tdl("Older males; advanced stage")],
[tdb("Lymphocyte-Rich"), td("~5%"), tdl("Mononuclear variants"), tdl("CD30+, CD15+, PAX5+w, CD45−"), td("~40%"), tdl("Good prognosis")],
[tdb("Lymphocyte-Depleted"), td("<5%"), tdl("Pleomorphic RS"), tdl("CD30+, CD15+, PAX5+w, CD45−"), td(">90%"), tdl("Poorest prognosis; rule out ALCL")],
[tdb("NLPHL (non-classic)"), td("~5%"), tdl("Popcorn/L&H cells"), tdl("CD20+, BCL6+, CD30−, CD15−, CD45+"), td("Rare"), tdl("Different entity; better prognosis")],
]
hl_table = Table(hl_data, colWidths=[3.4*cm, 1.6*cm, 2.8*cm, 4.8*cm, 1.2*cm, 3.9*cm])
hl_ts = base_table_style()
# NLPHL row highlight in amber background
for col in range(6):
hl_ts.append(("BACKGROUND", (col, 5), (col, 5), HexColor("#FFF3CD")))
hl_table.setStyle(TableStyle(hl_ts))
story.append(KeepTogether([
section_bar("3 · HODGKIN LYMPHOMA — SUBTYPE IHC"),
Spacer(1, 0.15*cm),
hl_table,
Spacer(1, 0.15*cm),
Paragraph("w = weakly positive | NLPHL = Nodular Lymphocyte Predominant HL (highlighted — distinct from classic HL)", CAPTION),
Spacer(1, 0.4*cm),
]))
# ── SECTION 4: DLBCL SUBTYPING — HANS ALGORITHM ─────────────────────────────
hans_data = [
[th("Step"), th("Marker"), th("Threshold"), th("Result")],
[td("1"), tdb("CD10"), tdl("> 30% positive"), tdl("→ GCB type (stop)")],
[td("2"), tdb("BCL6"), tdl("Positive"), tdl("→ Proceed to step 3; if negative → non-GCB (ABC)")],
[td("3"), tdb("MUM1"), tdl("Positive"), tdl("→ non-GCB (ABC); if negative → GCB")],
]
hans_table = Table(hans_data, colWidths=[1.5*cm, 2.5*cm, 4.5*cm, 9.2*cm])
hans_table.setStyle(TableStyle(base_table_style()))
combo_data = [
[th("CD10"), th("BCL6"), th("MUM1"), th("Phenotype"), th("Common Genetics"), th("Prognosis")],
[pos(), pos(), neg(), tdb("GCB"), tdl("t(14;18) BCL2; BCL6 rare"), tdl("Better (R-CHOP sensitive)")],
[pos(), neg(), neg(), tdb("GCB"), tdl("t(14;18) BCL2"), tdl("Better")],
[neg(), pos(), neg(), tdb("GCB"), tdl("BCL6 rearrangement"), tdl("Better")],
[neg(), pos(), pos(), tdb("non-GCB (ABC)"), tdl("BCL6 t(3;X)"), tdl("Worse")],
[neg(), neg(), pos(), tdb("non-GCB (ABC)"), tdl("NF-κB activation"), tdl("Worse")],
]
combo_table = Table(combo_data, colWidths=[1.8*cm, 1.8*cm, 1.8*cm, 3.2*cm, 5.5*cm, 3.6*cm])
combo_table.setStyle(TableStyle(base_table_style()))
story.append(KeepTogether([
section_bar("4 · DLBCL SUBTYPING — HANS ALGORITHM (IHC)"),
Spacer(1, 0.15*cm),
Paragraph("<b>Step-wise evaluation:</b>", BODY_BOLD),
Spacer(1, 0.1*cm),
hans_table,
Spacer(1, 0.25*cm),
Paragraph("<b>Full combination matrix:</b>", BODY_BOLD),
Spacer(1, 0.1*cm),
combo_table,
Spacer(1, 0.15*cm),
Paragraph(
"Double-hit lymphoma: MYC rearrangement + BCL2 and/or BCL6 rearrangement — confirm by FISH. "
"Extremely poor prognosis (median OS 12–18 months). MYC protein >40% + BCL2 >50% by IHC = "
'"double-expressor" — also adverse even without rearrangements.',
BODY_STYLE),
Spacer(1, 0.4*cm),
]))
# ── SECTION 5: T-CELL / NK-CELL LYMPHOMAS ────────────────────────────────────
tcell_data = [
[th("Lymphoma"), th("Key Positive Markers"), th("Key Negative"), th("Special Marker"), th("Notes")],
[tdb("PTCL-NOS"), tdl("CD3, CD4 or CD8"), tdl("No single defining marker"), tdl("Aberrant T-cell Ag loss"), tdl("Diagnosis of exclusion")],
[tdb("ALCL ALK+"), tdl("CD30 (strong), EMA"), tdl("CD3 often −"), tdl("ALK (nuclear+cytoplasmic)"), tdl("Better prognosis; t(2;5) NPM-ALK")],
[tdb("ALCL ALK−"), tdl("CD30 (strong), EMA"), tdl("ALK−"), tdl("DUSP22 or TP63 by FISH"), tdl("Worse prognosis")],
[tdb("AITL"), tdl("CD3, CD4, PD-1, CXCL13, CD10"), tdl("CD8"), tdl("CD21+ expanded FDC meshwork, EBER+"), tdl("TFH cell origin")],
[tdb("NK/T-cell"), tdl("CD56, CD2, cytoCD3, Granzyme B"), tdl("sCD3−, CD5−"), tdl("EBV (EBER) in situ hybridisation +"), tdl("Nasal type; aggressive")],
[tdb("Hepatosplenic"), tdl("CD3, CD56"), tdl("CD4−, CD8−"), tdl("TCRγδ+"), tdl("Young males; sinusoidal")],
[tdb("ATLL"), tdl("CD3, CD4, CD25"), tdl("CD7 (often lost)"), tdl("HTLV-1 serology"), tdl("FoxP3+ (regulatory T-cell)")],
[tdb("MF / Sézary"), tdl("CD3, CD4"), tdl("CD7, CD26 loss"), tdl("CD4:CD8 ratio >10 in blood"), tdl("Epidermotropic; indolent")],
]
tcell_table = Table(tcell_data, colWidths=[2.8*cm, 4.2*cm, 2.5*cm, 4.4*cm, 3.8*cm])
tcell_table.setStyle(TableStyle(base_table_style()))
story.append(KeepTogether([
section_bar("5 · T-CELL & NK-CELL LYMPHOMAS"),
Spacer(1, 0.15*cm),
tcell_table,
Spacer(1, 0.15*cm),
Paragraph("AITL = Angioimmunoblastic T-cell Lymphoma | ALCL = Anaplastic Large Cell Lymphoma | MF = Mycosis Fungoides | TFH = T follicular helper", CAPTION),
Spacer(1, 0.4*cm),
]))
# ── PAGE 2: DIFFERENTIAL DIAGNOSIS PANELS ────────────────────────────────────
story.append(PageBreak())
# ── SECTION 6: KEY DIFFERENTIALS ──────────────────────────────────────────────
diff_data = [
[th("Differential"), th("CD5"), th("CD10"), th("CD23"), th("Cyclin D1"), th("BCL2"), th("SOX11"), th("Key Distinguisher")],
[tdb("CLL/SLL"), pos("dim"), neg(), pos(), neg(), pos(), neg(), tdl("CD23+, CD5+, dim CD20, dim sIg")],
[tdb("MCL"), pos(), neg(), neg(), pos("✓"),pos(),pos("✓"),tdl("Cyclin D1+ (FISH if negative); SOX11+")],
[tdb("FL"), neg(), pos(), neg(), neg(), pos("✓"),neg(),tdl("CD10+, BCL2+, follicular pattern, t(14;18)")],
[tdb("MZL/MALT"), neg(), neg(), neg(), neg(), var(), neg(), tdl("All negative — diagnosis by exclusion + morphology")],
]
diff_table = Table(diff_data, colWidths=[2.6*cm, 1.4*cm, 1.4*cm, 1.5*cm, 2.2*cm, 1.4*cm, 1.5*cm, 6.2*cm])
diff_table.setStyle(TableStyle(base_table_style()))
story.append(KeepTogether([
section_bar("6 · DIFFERENTIAL: SMALL B-CELL LYMPHOMAS"),
Spacer(1, 0.15*cm),
diff_table,
Spacer(1, 0.4*cm),
]))
# ── Burkitt vs DLBCL ──────────────────────────────────────────────────────────
bvd_data = [
[th("Feature"), th("Burkitt Lymphoma"), th("DLBCL")],
[tdb("CD20"), pos(), pos()],
[tdb("CD10"), pos(), var()],
[tdb("BCL6"), pos(), var()],
[tdb("BCL2"), neg("✓ negative"), var()],
[tdb("MUM1"), neg(), var()],
[tdb("Ki-67"), td("~100% ✓"), td("60–90%")],
[tdb("MYC"), tdl("Rearranged (FISH) 100%"), tdl("~10% rearranged")],
[tdb("Morphology"), tdl('"Starry sky" — tingible-body macrophages'), tdl("Large atypical cells, necrosis")],
[tdb("Translocation"), tdl("t(8;14), t(2;8), t(8;22)"), tdl("t(14;18) in GCB; BCL6 in ABC")],
]
bvd_table = Table(bvd_data, colWidths=[3.2*cm, 7.25*cm, 7.25*cm])
bvd_table.setStyle(TableStyle(base_table_style()))
story.append(KeepTogether([
section_bar("7 · DIFFERENTIAL: BURKITT vs DLBCL"),
Spacer(1, 0.15*cm),
bvd_table,
Spacer(1, 0.4*cm),
]))
# ── Classic HL vs NLPHL ────────────────────────────────────────────────────────
hl_diff_data = [
[th("Feature"), th("Classic HL"), th("NLPHL")],
[tdb("RS/Tumor cell"), tdl("Classic RS, lacunar, mononuclear"), tdl("Popcorn/L&H cells")],
[tdb("CD20"), neg(), pos("✓")],
[tdb("CD45"), neg("✓"), pos()],
[tdb("CD15"), pos("✓"), neg("✓")],
[tdb("CD30"), pos("✓"), neg("rare+")],
[tdb("PAX5"), pos("weak"), pos("strong")],
[tdb("BCL6"), neg(), pos("✓")],
[tdb("EBV"), var("+/− (subtype dependent)"), neg("Rare")],
[tdb("EMA"), neg(), pos("+/−")],
[tdb("Background"),tdl("T-cells, eosinophils, plasma cells"), tdl("B-cell-rich nodules, FDC meshwork")],
[tdb("Prognosis"), tdl("Excellent with chemo-RT"), tdl("Excellent; late relapses possible")],
]
hl_diff_table = Table(hl_diff_data, colWidths=[3.2*cm, 7.25*cm, 7.25*cm])
hl_diff_table.setStyle(TableStyle(base_table_style()))
story.append(KeepTogether([
section_bar("8 · DIFFERENTIAL: CLASSIC HL vs NLPHL"),
Spacer(1, 0.15*cm),
hl_diff_table,
Spacer(1, 0.4*cm),
]))
# ── SECTION 9: PRACTICAL TIPS ─────────────────────────────────────────────────
tips = [
("<b>CD45 first:</b> A CD45-negative large cell tumour raises HL, germ cell tumour, carcinoma, or melanoma "
"before NHL. Always confirm with CD30/CD15 (HL) or keratins/PLAP (carcinoma/GCT)."),
("<b>BCL2 in follicular lymphoma:</b> BCL2+ in neoplastic follicles vs. BCL2− in reactive follicles. "
"Confirm t(14;18) by FISH when in doubt."),
("<b>Burkitt BCL2:</b> BCL2 negativity with Ki-67 ~100% virtually excludes DLBCL and is a red flag for Burkitt. "
"Always confirm MYC rearrangement by FISH."),
("<b>Double-expressor DLBCL:</b> MYC >40% + BCL2 >50% by IHC without FISH-confirmed rearrangement still "
"confers adverse prognosis but does not define 'double-hit lymphoma'. FISH is needed for the latter."),
("<b>ALK in ALCL:</b> ALK+ ALCL (t(2;5), NPM-ALK fusion) — nuclear+cytoplasmic staining. "
"ALK− ALCL — identical morphology and CD30 positivity but ALK absent; worse prognosis."),
("<b>CD5+ B-cell lymphoma:</b> MCL (cyclin D1+, SOX11+) vs CLL/SLL (CD23+, dim sIg). "
"Rarely, DLBCL can be CD5+ — aggressive behaviour."),
("<b>T-cell marker loss:</b> In T-cell lymphomas, aberrant loss of CD3, CD5, or CD7 supports neoplasia. "
"Normal T-cells express all three."),
("<b>EBV in NK/T-cell lymphoma:</b> EBER (EBV-encoded RNA) by in situ hybridisation is the preferred method "
"— more sensitive than LMP-1 by IHC."),
("<b>Hairy Cell Leukaemia:</b> CD20+, CD11c+, CD25+, CD103+ (TRAP+). CD5−, CD23−. "
"BRAF V600E IHC (VE1 antibody) positive in >95%."),
]
tips_story = [
section_bar("9 · PRACTICAL DIAGNOSTIC TIPS"),
Spacer(1, 0.15*cm),
]
for i, tip in enumerate(tips, 1):
tips_story.append(Paragraph(f"<b>{i}.</b> {tip}", BODY_STYLE))
story.append(KeepTogether(tips_story))
story.append(Spacer(1, 0.5*cm))
# ── SECTION 10: QUICK ALGORITHM BOXES ─────────────────────────────────────────
algo_data = [
[th("Clinical Scenario"), th("First IHC Panel"), th("Decision")],
[tdl("Undifferentiated large cell"), tdl("CD45, CK, S100, CD30"), tdl("CD45+ → lymphoma panel | CD45− → carcinoma/melanoma/HL")],
[tdl("Small B-cell lymphoma"), tdl("CD5, CD10, CD23, Cyclin D1"), tdl("See Section 6 algorithm above")],
[tdl("Large B-cell lymphoma"), tdl("CD20, CD10, BCL6, MUM1, BCL2, Ki-67"), tdl("Apply Hans algorithm; add MYC IHC if high grade")],
[tdl("Mediastinal mass"), tdl("CD20, CD30, CD15, CD45, PAX5"), tdl("HL vs PMBCL (CD20+ CD23+ BCL6+ MAL+) vs T-LBL")],
[tdl("Suspect Burkitt"), tdl("CD20, CD10, BCL6, BCL2, Ki-67, MYC FISH"), tdl("BCL2−, Ki-67 ~100%, MYC FISH+ = Burkitt")],
[tdl("Suspect T-cell NHL"), tdl("CD3, CD4, CD8, CD5, CD7, CD30, ALK"), tdl("Loss of CD5/CD7 supports neoplasia; CD30+ → ALCL panel")],
[tdl("Skin lymphoma"), tdl("CD3, CD4, CD8, CD20, CD30, CD56"), tdl("CD4+ epidermotropic = MF; CD30+ = lymphomatoid papulosis/pcALCL")],
]
algo_table = Table(algo_data, colWidths=[4.0*cm, 5.5*cm, 8.2*cm])
algo_table.setStyle(TableStyle(base_table_style()))
story.append(KeepTogether([
section_bar("10 · CLINICAL SCENARIO — PANEL SELECTION GUIDE"),
Spacer(1, 0.15*cm),
algo_table,
Spacer(1, 0.4*cm),
]))
# ── FOOTER DISCLAIMER ─────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=0.5, color=NAVY))
story.append(Spacer(1, 0.15*cm))
story.append(Paragraph(
"This quick-reference document is intended for educational and clinical support purposes. "
"IHC interpretation must always be correlated with morphology, clinical context, and molecular findings. "
"Sources: Robbins, Cotran & Kumar Pathologic Basis of Disease; Harrison's Principles of Internal Medicine 22E; "
"Quick Compendium of Clinical Pathology 5th Edition; Henry's Clinical Diagnosis and Management by Laboratory Methods; "
"Murray & Nadel's Textbook of Respiratory Medicine. WHO Classification of Tumours of Haematopoietic and Lymphoid Tissues.",
FOOTNOTE))
# ── Build PDF ─────────────────────────────────────────────────────────────────
doc.build(story,
onFirstPage=on_first_page,
onLaterPages=on_later_pages)
print(f"PDF generated: {OUTPUT}")
python3 /home/daytona/workspace/lymphoma-ihc/generate_pdf.py 2>&1
ls -lh /home/daytona/workspace/lymphoma-ihc/Lymphoma_IHC_Quick_Reference.pdf
Lymphoma IHC Quick Reference
PDF Document · PDF
| Section | Contents |
|---|---|
| Legend | Symbol key: +, −, +/−, dim+, ~100% |
| 1. Core Lineage Markers | CD45, CD20, CD79a, PAX5, CD3, CD5, CD10, BCL6, MUM1, BCL2, Cyclin D1, CD30, CD15, Ki-67, EBV |
| 2. Master IHC Profile Table | 12 lymphoma types across 11 markers - with highlighted hallmark cells |
| 3. Hodgkin Lymphoma Subtypes | All 5 HL subtypes with RS cell variant, IHC profile, EBV association |
| 4. DLBCL Hans Algorithm | Step-wise CD10/BCL6/MUM1 subtyping table + full combination matrix + double-hit note |
| 5. T-cell & NK-cell Lymphomas | PTCL, ALCL ALK+/−, AITL, NK/T, Hepatosplenic, ATLL, MF/Sézary |
| 6. Small B-cell Differential | CLL vs MCL vs FL vs MALT comparison |
| 7. Burkitt vs DLBCL | 9-point IHC + genetics comparison |
| 8. Classic HL vs NLPHL | Side-by-side IHC distinction table |
| 9. Practical Diagnostic Tips | 9 high-yield pearls (BCL2, double-expressor, ALK, CD5, etc.) |
| 10. Panel Selection Guide | Scenario-based first-line panel recommendations |