Create pdf showing the difference between a normal lung alveolus and one affected by pneumonia, side by side with labeled inflammatory infiltrate.

Reading File
Reading File
Checking Date & Time
Finding Sources
Finding Sources
Searching Images

normal alveolus vs pneumonia inflammatory infiltrate histology

Loading Image
Loading Image
Searching Images

normal lung alveolus histology type I type II pneumocytes capillary

Reading File
Loading Image
Loading Image
Running Command

ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or missing"

Running Command

mkdir -p /home/daytona/workspace/pneumonia-alveolus-pdf

Writing File

~/pneumonia-alveolus-pdf/build_pdf.py

#!/usr/bin/env python3
"""
Build a side-by-side comparison PDF:
  Normal Lung Alveolus  vs  Alveolus Affected by Pneumonia
with labeled inflammatory infiltrate.
"""

import urllib.request
import os
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    Image, HRFlowable, KeepTogether
)
from reportlab.pdfgen import canvas
from reportlab.platypus.flowables import Flowable

OUT_DIR = "/home/daytona/workspace/pneumonia-alveolus-pdf"
PDF_PATH = os.path.join(OUT_DIR, "Normal_vs_Pneumonia_Alveolus.pdf")

# ── Image URLs ───────────────────────────────────────────────────────────────
NORMAL_HISTO_URL   = "https://cdn.orris.care/cdss_images/Pathology_1760052631924_4e2d174f-ecec-49e8-aaa2-21103e931ec2.jpg"
PNEUMONIA_HISTO_URL = "https://cdn.orris.care/cdss_images/Pathology_1760050132399_6b1df8c8-24ae-4ac3-8833-7b27d592089f.jpg"
DIAGRAM_URL        = "https://cdn.orris.care/cdss_images/HARRISON_1763032917069_bc607f3c-f0d1-4b09-9c55-5061e1e5e897.png"

def download(url, dest):
    if not os.path.exists(dest):
        print(f"Downloading {url} ...")
        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
        with urllib.request.urlopen(req) as r, open(dest, "wb") as f:
            f.write(r.read())
    return dest

normal_img   = download(NORMAL_HISTO_URL,   os.path.join(OUT_DIR, "normal_histo.jpg"))
pneumonia_img = download(PNEUMONIA_HISTO_URL, os.path.join(OUT_DIR, "pneumonia_histo.jpg"))
diagram_img  = download(DIAGRAM_URL,         os.path.join(OUT_DIR, "diagram.png"))

# ── Colour Palette ────────────────────────────────────────────────────────────
C_NAVY   = colors.HexColor("#1a2d5a")
C_BLUE   = colors.HexColor("#2962a8")
C_NORMAL = colors.HexColor("#2e7d32")   # green header for normal side
C_PNEU   = colors.HexColor("#b71c1c")   # red header for pneumonia side
C_LIGHT_GREEN = colors.HexColor("#e8f5e9")
C_LIGHT_RED   = colors.HexColor("#ffebee")
C_GOLD   = colors.HexColor("#f9a825")
C_GRAY   = colors.HexColor("#546e7a")
C_WHITE  = colors.white
C_LIGHT_BLUE = colors.HexColor("#e3f2fd")

# ── Page Setup ────────────────────────────────────────────────────────────────
PAGE = landscape(A4)
W, H = PAGE

doc = SimpleDocTemplate(
    PDF_PATH,
    pagesize=PAGE,
    leftMargin=1.5*cm, rightMargin=1.5*cm,
    topMargin=1.5*cm,  bottomMargin=1.5*cm,
    title="Normal vs Pneumonia Alveolus",
    author="Orris Medical Education"
)

styles = getSampleStyleSheet()

def sty(name, **kw):
    return ParagraphStyle(name, **kw)

TITLE_STYLE = sty("Title",
    fontName="Helvetica-Bold", fontSize=18,
    textColor=C_NAVY, alignment=TA_CENTER, spaceAfter=4)

SUB_STYLE = sty("Sub",
    fontName="Helvetica", fontSize=10,
    textColor=C_GRAY, alignment=TA_CENTER, spaceAfter=2)

SECTION_HDR = sty("SectionHdr",
    fontName="Helvetica-Bold", fontSize=12,
    textColor=C_WHITE, alignment=TA_CENTER,
    spaceAfter=0, leading=16)

BODY_STYLE = sty("Body",
    fontName="Helvetica", fontSize=8.5,
    textColor=colors.black, leading=12,
    spaceAfter=3, alignment=TA_LEFT)

BULLET_STYLE = sty("Bullet",
    fontName="Helvetica", fontSize=8.5,
    textColor=colors.black, leading=12,
    leftIndent=10, spaceAfter=2, alignment=TA_LEFT)

LABEL_STYLE = sty("Label",
    fontName="Helvetica-Bold", fontSize=7.5,
    textColor=C_NAVY, leading=10, alignment=TA_CENTER)

CAPTION_STYLE = sty("Caption",
    fontName="Helvetica-Oblique", fontSize=7,
    textColor=C_GRAY, alignment=TA_CENTER, spaceAfter=4)

FOOT_STYLE = sty("Footer",
    fontName="Helvetica", fontSize=7,
    textColor=C_GRAY, alignment=TA_CENTER)

# ── Helper: coloured header cell ──────────────────────────────────────────────
def hdr(text, bg):
    return Paragraph(f"<b>{text}</b>", ParagraphStyle(
        "H", fontName="Helvetica-Bold", fontSize=12,
        textColor=C_WHITE, alignment=TA_CENTER, leading=16))

# ── Build content ─────────────────────────────────────────────────────────────
story = []

# PAGE TITLE
story.append(Paragraph("Normal Lung Alveolus vs. Pneumonia", TITLE_STYLE))
story.append(Paragraph(
    "A side-by-side histopathological and cellular comparison with labeled inflammatory infiltrate",
    SUB_STYLE))
story.append(HRFlowable(width="100%", thickness=2, color=C_NAVY, spaceAfter=6))

# ── Section 1: Histology side-by-side ─────────────────────────────────────────
IMG_W = 10.8*cm
IMG_H = 7.2*cm

normal_photo   = Image(normal_img,   width=IMG_W, height=IMG_H)
pneumonia_photo = Image(pneumonia_img, width=IMG_W, height=IMG_H)

# Build two-column histology table
histo_table = Table(
    [
        # Headers
        [
            Paragraph("<b>NORMAL ALVEOLUS</b><br/><font size='8'>H&amp;E Histology</font>",
                      ParagraphStyle("NH", fontName="Helvetica-Bold", fontSize=11,
                                     textColor=C_WHITE, alignment=TA_CENTER, leading=14)),
            Paragraph("<b>PNEUMONIA-AFFECTED ALVEOLUS</b><br/><font size='8'>H&amp;E Histology — Acute Bronchopneumonia</font>",
                      ParagraphStyle("PH", fontName="Helvetica-Bold", fontSize=11,
                                     textColor=C_WHITE, alignment=TA_CENTER, leading=14)),
        ],
        # Images
        [normal_photo, pneumonia_photo],
        # Captions
        [
            Paragraph(
                "Normal pulmonary parenchyma. Thin alveolar septa, patent air spaces, "
                "sparse cellularity. Type I and II pneumocytes visible. "
                "Capillaries within septa contain RBCs. No inflammatory infiltrate.",
                CAPTION_STYLE),
            Paragraph(
                "Acute pneumonia. Alveolar spaces packed with neutrophils and cellular debris. "
                "Septa thickened by inflammatory infiltrate. Vascular congestion. "
                "Fibrinous exudate. Reactive type II pneumocyte hyperplasia.",
                CAPTION_STYLE),
        ],
    ],
    colWidths=[IMG_W + 1.6*cm, IMG_W + 1.6*cm],
    rowHeights=[1.0*cm, IMG_H, 1.4*cm],
)
histo_table.setStyle(TableStyle([
    # Header background
    ("BACKGROUND", (0, 0), (0, 0), C_NORMAL),
    ("BACKGROUND", (1, 0), (1, 0), C_PNEU),
    # Image row background
    ("BACKGROUND", (0, 1), (0, 1), C_LIGHT_GREEN),
    ("BACKGROUND", (1, 1), (1, 1), C_LIGHT_RED),
    # Caption row
    ("BACKGROUND", (0, 2), (0, 2), colors.HexColor("#f1f8e9")),
    ("BACKGROUND", (1, 2), (1, 2), colors.HexColor("#fff3e0")),
    # Alignment & padding
    ("VALIGN",  (0, 0), (-1, -1), "MIDDLE"),
    ("ALIGN",   (0, 0), (-1, -1), "CENTER"),
    ("TOPPADDING",    (0, 0), (-1, 0), 6),
    ("BOTTOMPADDING", (0, 0), (-1, 0), 6),
    ("LEFTPADDING",   (0, 0), (-1, -1), 4),
    ("RIGHTPADDING",  (0, 0), (-1, -1), 4),
    # Borders
    ("BOX",     (0, 0), (0, -1), 1.5, C_NORMAL),
    ("BOX",     (1, 0), (1, -1), 1.5, C_PNEU),
    ("INNERGRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#cccccc")),
    ("COLUMNPADDING", (0, 0), (-1, -1), 6),
]))

story.append(histo_table)
story.append(Spacer(1, 0.4*cm))

# ── Section 2: Feature comparison table ──────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=C_BLUE, spaceAfter=4))
story.append(Paragraph("Histopathological Feature Comparison", sty("CompHdr",
    fontName="Helvetica-Bold", fontSize=11, textColor=C_NAVY,
    alignment=TA_CENTER, spaceAfter=4)))

feat_headers = [
    Paragraph("<b>Feature</b>", sty("FH", fontName="Helvetica-Bold", fontSize=8.5,
              textColor=C_WHITE, alignment=TA_CENTER)),
    Paragraph("<b>Normal Alveolus</b>", sty("FH2", fontName="Helvetica-Bold", fontSize=8.5,
              textColor=C_WHITE, alignment=TA_CENTER)),
    Paragraph("<b>Pneumonia-Affected Alveolus</b>", sty("FH3", fontName="Helvetica-Bold",
              fontSize=8.5, textColor=C_WHITE, alignment=TA_CENTER)),
]

def fp(t): return Paragraph(t, BODY_STYLE)

feat_data = [
    feat_headers,
    [fp("Alveolar air space"), fp("Clear, patent, air-filled"), fp("Obliterated by exudate, neutrophils & debris")],
    [fp("Alveolar septa"), fp("Thin, delicate, 1–2 cell layers"), fp("Thickened by oedema & inflammatory infiltrate")],
    [fp("Inflammatory cells"), fp("Rare alveolar macrophages only"), fp("Abundant neutrophils, macrophages, lymphocytes")],
    [fp("Intra-alveolar exudate"), fp("None"), fp("Fibrinous/proteinaceous exudate, RBCs")],
    [fp("Type I pneumocytes"), fp("Flat, intact lining cells"), fp("Necrotic / sloughed from basement membrane")],
    [fp("Type II pneumocytes"), fp("Cuboidal, sparse, produce surfactant"), fp("Reactive hyperplasia (reparative response)")],
    [fp("Capillaries / vessels"), fp("Patent, RBCs within septa"), fp("Congested, endothelial swelling, margination")],
    [fp("Surfactant"), fp("Intact; reduces surface tension"), fp("Inactivated by edema fluid & proteases")],
    [fp("Interstitium"), fp("Sparse fibroblasts, minimal fluid"), fp("Widened; oedematous; collagen disrupted")],
    [fp("Architecture"), fp("Normal lobular architecture"), fp("Partially preserved; risk of consolidation")],
]

COL_W = [(W - 3*cm) * p for p in [0.28, 0.36, 0.36]]

feat_table = Table(feat_data, colWidths=COL_W, rowHeights=None)
feat_table.setStyle(TableStyle([
    ("BACKGROUND", (0, 0), (-1, 0), C_NAVY),
    ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#f5f5f5")]),
    ("BACKGROUND", (1, 1), (1, -1), colors.HexColor("#f1f8e9")),
    ("BACKGROUND", (2, 1), (2, -1), colors.HexColor("#fff8e1")),
    ("ALIGN",    (0, 0), (-1, -1), "LEFT"),
    ("VALIGN",   (0, 0), (-1, -1), "TOP"),
    ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
    ("FONTSIZE", (0, 0), (-1, -1), 8.5),
    ("TOPPADDING",    (0, 0), (-1, -1), 3),
    ("BOTTOMPADDING", (0, 0), (-1, -1), 3),
    ("LEFTPADDING",   (0, 0), (-1, -1), 5),
    ("RIGHTPADDING",  (0, 0), (-1, -1), 5),
    ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#cccccc")),
    ("BOX",  (0, 0), (-1, -1), 1,   C_NAVY),
]))

story.append(feat_table)
story.append(Spacer(1, 0.3*cm))

# ── Footer ─────────────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=C_GRAY, spaceBefore=4, spaceAfter=4))
story.append(Paragraph(
    "Sources: Harrison's Principles of Internal Medicine 21e (diagram) · WebPathology (H&amp;E images) · "
    "Miller's Anesthesia 10e · Orris Medical Education · April 2026",
    FOOT_STYLE))

# ── Page 2: Cellular Mechanism Diagram ────────────────────────────────────────
from reportlab.platypus import PageBreak

story.append(PageBreak())

story.append(Paragraph("Cellular Mechanisms: Normal vs. Injured Alveolus", TITLE_STYLE))
story.append(Paragraph(
    "Diagram showing molecular and cellular events — normal alveolar homeostasis vs. acute lung injury/pneumonia",
    SUB_STYLE))
story.append(HRFlowable(width="100%", thickness=2, color=C_NAVY, spaceAfter=10))

# Display the Harrison diagram large
diag_w = W - 4*cm
diag_h = diag_w * 0.55   # keep approximate aspect ratio

diag_img_flow = Image(diagram_img, width=diag_w, height=diag_h)

# Center it
diag_table = Table([[diag_img_flow]], colWidths=[W - 3*cm])
diag_table.setStyle(TableStyle([
    ("ALIGN",  (0, 0), (-1, -1), "CENTER"),
    ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
    ("BOX",    (0, 0), (-1, -1), 1.5, C_NAVY),
    ("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#f5f9ff")),
    ("TOPPADDING",    (0, 0), (-1, -1), 8),
    ("BOTTOMPADDING", (0, 0), (-1, -1), 8),
]))
story.append(diag_table)
story.append(Spacer(1, 0.4*cm))

story.append(Paragraph(
    "Figure: Harrison's Principles of Internal Medicine 21e. "
    "Left — Normal alveolus with intact type I/II pneumocytes, surfactant layer, "
    "alveolar macrophages, and capillary endothelium. "
    "Right — Acute injured alveolus (pneumonia/ALI) showing neutrophilic infiltrate, "
    "activated cytokines (IL-6, IL-8, TNF-α), fibrin deposition, hyaline membrane formation, "
    "protein-rich oedema fluid, and endothelial gap formation.",
    CAPTION_STYLE))

# ── Labeled Inflammatory Infiltrate Key ───────────────────────────────────────
story.append(Spacer(1, 0.3*cm))
story.append(HRFlowable(width="100%", thickness=1, color=C_BLUE, spaceAfter=6))
story.append(Paragraph("Labeled Inflammatory Infiltrate Components in Pneumonia", sty("IKey",
    fontName="Helvetica-Bold", fontSize=11, textColor=C_NAVY,
    alignment=TA_CENTER, spaceAfter=6)))

def lp(label, color=C_NAVY):
    return Paragraph(f"<font color='#{color.hexval()[2:]}'>●</font> <b>{label}</b>",
                     BULLET_STYLE)

def dp(text):
    return Paragraph(text, BODY_STYLE)

infiltrate_data = [
    [
        Paragraph("<b>Cell / Component</b>", sty("IH", fontName="Helvetica-Bold", fontSize=8.5,
                  textColor=C_WHITE, alignment=TA_CENTER)),
        Paragraph("<b>Role in Pneumonia</b>", sty("IH", fontName="Helvetica-Bold", fontSize=8.5,
                  textColor=C_WHITE, alignment=TA_CENTER)),
        Paragraph("<b>Histologic Appearance</b>", sty("IH", fontName="Helvetica-Bold", fontSize=8.5,
                  textColor=C_WHITE, alignment=TA_CENTER)),
        Paragraph("<b>Mediators Released</b>", sty("IH", fontName="Helvetica-Bold", fontSize=8.5,
                  textColor=C_WHITE, alignment=TA_CENTER)),
    ],
    [dp("Neutrophils (PMNs)"),
     dp("First responders; phagocytose bacteria; dominate acute exudate"),
     dp("Small cells with multi-lobed nuclei; fill alveolar spaces"),
     dp("IL-8, oxidants, elastase, myeloperoxidase, NETs")],
    [dp("Alveolar Macrophages"),
     dp("Clear debris; present antigens; orchestrate resolution"),
     dp("Large cells with pale cytoplasm in alveolar lumen"),
     dp("TNF-α, IL-1β, IL-6, MIF, reactive oxygen species")],
    [dp("Lymphocytes (T/B cells)"),
     dp("Adaptive immunity; support antibody production (B) and cytotoxicity (T)"),
     dp("Small round cells with scant cytoplasm; interstitial location"),
     dp("IFN-γ, perforin, granzymes, B-cell derived antibodies")],
    [dp("Fibrin / Exudate"),
     dp("Coagulation cascade activation; forms scaffold for repair"),
     dp("Pink eosinophilic amorphous material in air spaces"),
     dp("Coagulation factors, fibrinogen → fibrin meshwork")],
    [dp("Edema fluid"),
     dp("Vascular permeability ↑; capillary leak into alveoli"),
     dp("Pale proteinaceous fluid within alveolar spaces"),
     dp("Plasma proteins, complement; inactivates surfactant")],
    [dp("Reactive Type II Pneumocytes"),
     dp("Regenerate epithelial lining; restore alveolar barrier"),
     dp("Cuboidal cells with prominent nucleoli lining septa"),
     dp("Surfactant components, growth factors (KGF, HGF)")],
    [dp("Hyaline Membranes"),
     dp("Deposited in severe/ALI cases; indicate diffuse damage"),
     dp("Dense eosinophilic lining along alveolar walls"),
     dp("Dead cell debris + plasma proteins (fibrin, complement)")],
]

inf_col_w = [(W - 3*cm) * p for p in [0.19, 0.27, 0.28, 0.26]]

inf_table = Table(infiltrate_data, colWidths=inf_col_w)
inf_table.setStyle(TableStyle([
    ("BACKGROUND", (0, 0), (-1, 0), C_PNEU),
    ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#fff5f5")]),
    ("ALIGN",    (0, 0), (-1, -1), "LEFT"),
    ("VALIGN",   (0, 0), (-1, -1), "TOP"),
    ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
    ("FONTSIZE", (0, 0), (-1, -1), 8.5),
    ("TOPPADDING",    (0, 0), (-1, -1), 3),
    ("BOTTOMPADDING", (0, 0), (-1, -1), 3),
    ("LEFTPADDING",   (0, 0), (-1, -1), 5),
    ("RIGHTPADDING",  (0, 0), (-1, -1), 5),
    ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#cccccc")),
    ("BOX",  (0, 0), (-1, -1), 1.5, C_PNEU),
]))

story.append(inf_table)

story.append(Spacer(1, 0.3*cm))
story.append(HRFlowable(width="100%", thickness=1, color=C_GRAY, spaceBefore=4, spaceAfter=4))
story.append(Paragraph(
    "Sources: Harrison's Principles of Internal Medicine 21e · Robbins & Cotran Pathologic Basis of Disease · "
    "Miller's Anesthesia 10e · WebPathology (histology images) · Orris Medical Education — April 2026",
    FOOT_STYLE))

# ── Build ──────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF written to: {PDF_PATH}")
Running Command

cd /home/daytona/workspace/pneumonia-alveolus-pdf && python3 build_pdf.py 2>&1

Running Command

ls -lh /home/daytona/workspace/pneumonia-alveolus-pdf/Normal_vs_Pneumonia_Alveolus.pdf

The PDF built successfully at 2.5 MB. Here's your file:

Normal vs Pneumonia Alveolus

PDF Document · PDF


The PDF is landscape A4, 2 pages, and contains:
Page 1 — Side-by-Side Histology
  • Two H&E photomicrographs placed side-by-side with colour-coded headers (green = normal, red = pneumonia)
  • Captions under each image describing what is seen
  • A 10-row feature comparison table covering: alveolar air space, septa, inflammatory cells, exudate, type I & II pneumocytes, capillaries, surfactant, interstitium, and overall architecture
Page 2 — Cellular Mechanisms & Labeled Infiltrate
  • The full Harrison's Principles diagram showing normal alveolus vs. acutely injured alveolus (labeled: surfactant, type I/II cells, macrophages, capillary, neutrophils, fibrin, hyaline membrane, cytokines IL-6/IL-8/TNF-α, edema fluid, endothelial gap formation)
  • A 7-row inflammatory infiltrate table labeling each component: neutrophils, alveolar macrophages, lymphocytes, fibrin/exudate, edema fluid, reactive type II pneumocytes, and hyaline membranes — with role, histologic appearance, and mediators released
Sources: Harrison's Principles of Internal Medicine 21e · WebPathology (H&E images) · Miller's Anesthesia 10e
This is a shared conversation. Sign in to Orris to start your own chat.