How to read hrctand cect chest

Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
Finding Sources
Reading File
Finding Sources
Reading File
Finding Sources
Finding Sources
Finding Sources
Loading Image
Loading Image
Searching the Web

how to read HRCT CECT chest systematic approach radiology

Reading Web Page

https://radiologyassistant.nl/chest/hrct/basic-interpretatio…

Searching the Web

CECT chest reading approach mediastinum pleura lung vessels systematic

I now have comprehensive information to write a thorough guide. Let me compile everything into a well-structured answer.

How to Read HRCT and CECT Chest


PART 1 - HRCT CHEST (High-Resolution CT)

What is HRCT?

HRCT differs from a standard non-contrast CT by using very thin slices (≤1.5 mm, ideally ~1 mm) combined with a high-spatial-frequency reconstruction algorithm to maximize sharpness and spatial resolution. This makes fine lung parenchymal detail visible - similar to gross pathological examination. IV contrast is not given for HRCT because it adds no value for interstitial lung assessment.
  • Fishman's Pulmonary Diseases and Disorders, 2-Volume Set
  • Murray & Nadel's Textbook of Respiratory Medicine, 2-Volume
Standard HRCT protocol positions:
  • Supine, full inspiration - routine
  • Prone - to distinguish true ILD from dependent atelectasis
  • Expiratory - to detect air trapping (useful in obstructive/small airway disease)

Step 1 - Technical Assessment Before Interpretation

Before looking at pathology, verify:
  • Adequate inspiration (diaphragm at 9th-10th rib posteriorly)
  • Correct window settings used (lung window: W: 1500, L: -700; mediastinal window: W: 350-400, L: 40)
  • No motion artifact

Step 2 - Know the Secondary Pulmonary Lobule (SPL)

This is the foundation of HRCT interpretation. The SPL is the smallest unit of lung structure surrounded by connective tissue septa.
Normal HRCT - secondary lobular anatomy showing centrilobular arteries, interlobular septa, and peripheral pulmonary veins
Normal HRCT at right lower lobe level: (A) axial HRCT showing normal fissures, airways, pulmonary veins (white arrowheads) in interlobular septa, and centrilobular artery (black arrows); (B) diagram of the secondary pulmonary lobule; (C) magnified detail - Murray & Nadel's
Key structures within the SPL:
  • Centrilobular region: bronchiole + centrilobular artery
  • Interlobular septa: contain pulmonary veins + lymphatics
  • Perilobular region: connective tissue septum
Disease distribution within the SPL directs the differential diagnosis:
DistributionPatternClassic Disease
CentrilobularNodules near lobule center, spares pleuraHypersensitivity pneumonitis, respiratory bronchiolitis, endobronchial spread of infection
PerilymphaticAlong septa, fissures, bronchovascular bundlesSarcoidosis, lymphangitic carcinomatosis, pulmonary edema
RandomNo lobular preferenceHematogenous metastases, miliary TB/fungal

Step 3 - Identify the Dominant Pattern

Ask: "What is the main HRCT pattern?"

A. Increased Attenuation Patterns

1. Reticular / Linear opacities Thickening of the interstitial fiber network. Subtypes:
  • Interlobular septal thickening (smooth): pulmonary edema, lymphangitic carcinomatosis
  • Interlobular septal thickening (nodular/irregular): sarcoidosis, lymphangitic spread
  • Intralobular lines (fine netlike): fibrotic ILDs (UIP, NSIP, asbestosis)
  • Honeycombing: clustered cystic spaces 3-10 mm, subpleural - hallmark of end-stage fibrosis/UIP
2. Nodular opacities Characterize by:
  • Size: micronodules (<3 mm) vs macronodules (3-10 mm) vs large nodules (>10 mm)
  • Distribution: centrilobular, perilymphatic, or random (see table above)
  • Borders: well-defined (metastases) vs ill-defined/ground glass halo (infection, eosinophilic)
  • Calcification: old granuloma, hamartoma
3. Ground Glass Opacity (GGO) Hazy increased attenuation, does NOT obscure underlying vessels. Indicates partial filling/thickening of alveoli or interstitium. Causes:
  • Active inflammation/infection (COVID-19 pneumonia, PCP)
  • Pulmonary edema
  • Early ILD (NSIP)
  • Alveolar hemorrhage
4. Consolidation Dense opacity that obscures vessels. Complete airspace filling. Causes: pneumonia, organizing pneumonia (COP), pulmonary infarction, adenocarcinoma (formerly BAC)
5. "Crazy paving" GGO + superimposed interlobular septal thickening = classic for alveolar proteinosis, also COVID-19

B. Decreased Attenuation Patterns

1. Emphysema - low attenuation areas without walls
  • Centrilobular: smoking-related, upper lobe predominant, spares periphery
  • Panlobular: alpha-1 antitrypsin deficiency, lower lobe predominant
  • Paraseptal: along septa, subpleural, associated with bullae and spontaneous pneumothorax
2. Cystic lung diseases - air-filled spaces with thin but definable walls
  • LAM (lymphangioleiomyomatosis): uniform thin-walled cysts throughout both lungs in young women
  • LCH (Langerhans cell histiocytosis): bizarre-shaped thick-walled cysts + nodules, upper lobe, smoker
  • LIP: cysts + GGO, usually immune disorder
3. Bronchiectasis: abnormally dilated bronchi. "Signet ring sign" = bronchus > adjacent artery. Causes: CF, post-infective, allergic bronchopulmonary aspergillosis (ABPA)
4. Air trapping (on expiratory scans): focal hypodense lobules that fail to decrease in attenuation on expiration - indicates small airway disease

Step 4 - Determine Distribution

Zonal predominance:
LocationThink of
Upper lobe predominantSarcoidosis, silicosis, LCH, PLCH, hypersensitivity pneumonitis (some)
Lower lobe predominantUIP/IPF, NSIP, aspiration, pulmonary edema
DiffuseNSIP, DIP, infections
Central vs. peripheral:
  • Central (peribronchovascular): sarcoidosis, lymphangitic carcinomatosis, bronchitis
  • Peripheral/subpleural: UIP, COP (organizing pneumonia), chronic eosinophilic pneumonia
Symmetry:
  • Bilateral symmetric: UIP, NSIP, edema, sarcoidosis
  • Unilateral/asymmetric: lymphangitic carcinomatosis (~50% unilateral), post-obstructive changes, aspiration

Step 5 - UIP/ILD Assessment (Fleischner Society Criteria)

This is a major application of HRCT. The four Fleischner categories for UIP:
Fleischner Society 4 CT patterns for UIP - Typical UIP, Probable UIP, Indeterminate for UIP, and CT features most consistent with non-IPF diagnosis
Fleischner Society CT patterns for UIP/IPF diagnosis - Murray & Nadel's
PatternCT FeaturesBiopsy Needed?
Typical UIPSubpleural + basal reticulation + honeycombing + traction bronchiectasisNo
Probable UIPSubpleural + basal reticulation + traction bronchiectasis, NO honeycombingUsually no
IndeterminateVariable/diffuse distribution, some features of fibrosis but inconclusiveYes
Non-IPF diagnosisUpper/mid predominant, consolidation, diffuse GGO, extensive mosaic, diffuse nodules/cystsYes (alternate diagnosis)

Step 6 - Additional Features (Don't Miss These)

  • Pleural effusion: transudative (edema, heart failure) vs exudative (lymphangitic, mesothelioma, empyema)
  • Lymphadenopathy: sarcoidosis shows classic "1-2-3 sign" (right paratracheal + bilateral hilar); lymphoma is infiltrative, encircles vessels; lung carcinoma usually unilateral
  • Traction bronchiectasis: dilated bronchi within areas of fibrosis - sign of established fibrotic ILD
  • Pleural plaques: asbestos exposure
  • Pneumothorax: spontaneous (paraseptal emphysema, LAM, LCH)

PART 2 - CECT CHEST (Contrast-Enhanced CT)

When to Use CECT vs. HRCT

IndicationHRCT (no contrast)CECT (with contrast)
Interstitial lung diseaseYesNo
Pulmonary embolism (CTPA)NoYes (arterial phase)
Mediastinal massesLess usefulYes
Lung cancer stagingLess usefulYes
Hilar/mediastinal nodesLess usefulYes
Pleural vs. parenchymal diseaseLess usefulYes
Aortic aneurysm/dissectionNoYes

Contrast Phases Used

PhaseTiming after contrastWhat it shows
Arterial (CT Pulmonary Angiography - CTPA)15-20 secPulmonary arteries - PE, AVM
Venous/portal60-70 secMediastinal structures, lymph nodes, liver
Delayed3-5 minVascular tumors, infection

Systematic Approach to Reading CECT Chest

Use windows correctly:

  • Lung window (W:1500, L:-700): parenchyma, airways, emphysema
  • Mediastinal/soft tissue window (W:350-400, L:40): nodes, vessels, masses, pleura
  • Bone window (W:2000, L:400): ribs, vertebrae, sternum

Go in a fixed order (prevents omission):

1. Trachea and main bronchi
  • Central position? Any displacement, stenosis, compression?
  • Tracheal shape: saber sheath trachea (COPD), tracheomalacia
2. Mediastinum Divide into compartments (ITMIG classification):
  • Prevascular/Anterior: thymus, thymic tumors, germ cell tumors, thyroid
  • Visceral/Middle: trachea, heart, great vessels, esophagus, lymph nodes
  • Paravertebral/Posterior: neural tumors (schwannoma, neurofibroma), vertebral disease
Key: "Typical anterior mediastinal masses" = 4 T's - Thymoma, Teratoma/germ cell, Terrible lymphoma, Thyroid
3. Hilar structures
  • Normal pulmonary arteries vs. enlarged hila (lymph nodes, mass)
  • Sarcoidosis: bilateral symmetric hilar enlargement
  • Primary lung cancer: unilateral hilar mass
4. Heart and pericardium
  • Heart size, pericardial effusion (density: transudative vs hemorrhagic)
  • Cardiac masses, thrombus
5. Aorta and great vessels
  • Aneurysm (>4 cm thoracic), intimal flap (dissection), mural thrombus
  • CTPA: filling defects in pulmonary arteries = PE (saddle embolus at bifurcation is classic)
6. Lung parenchyma (using lung windows)
  • Nodules: size, density (solid, part-solid, GGO), margins (spiculated = malignant, smooth = benign)
  • Masses (>3 cm), consolidation, GGO, atelectasis
  • Fleischner 2017 guidelines for nodule follow-up (<6 mm = no routine follow-up in low-risk patients)
7. Pleura
  • Effusion: measure and note density (HU <20 = transudative; >35 = exudative/hemorrhagic)
  • Pleural thickening: uniform (reactive) vs. nodular (malignant mesothelioma)
  • Pneumothorax
8. Chest wall and bony structures
  • Rib fractures (acute = sharp margins; healing = callus formation)
  • Lytic vs. sclerotic bone lesions
  • Soft tissue masses
9. Subdiaphragmatic (scan to include upper abdomen)
  • Adrenal glands (common site of lung cancer metastasis)
  • Liver, upper kidneys

CECT Chest: Common Clinical Applications

Pulmonary Embolism (CTPA)

  • Scan in arterial phase (~15-20 seconds after contrast injection)
  • Look for: intraluminal filling defect in opacified pulmonary arteries
  • Saddle embolus straddles the main pulmonary artery bifurcation
  • Right heart strain signs: RV:LV ratio >0.9, straightening of interventricular septum
  • Peripheral wedge-shaped infarct (Hampton's hump)

Lung Cancer Staging (TNM)

  • T: Tumor size, invasion of mediastinum/chest wall/diaphragm
  • N: Mediastinal node involvement (short axis >1 cm considered enlarged)
  • M: Distant metastases (liver, adrenals, brain with CT head)

Mediastinal Masses with CECT

  • Thymoma: anterior mediastinal, heterogeneous enhancement, pleural nodules in invasive type
  • Lymphoma: multiple nodes, infiltrative, soft tissue, "encases" vessels without invading
  • Germ cell tumor: anterior, may contain fat/calcification (teratoma)
  • Neurogenic tumor: posterior mediastinum, may extend through neural foramen (dumbbell)

Summary Mnemonic for Reading Chest CT

"TALE-P-V-B" (systematic order):
  • T - Trachea & main bronchi
  • A - Airspaces (lung parenchyma - lung window)
  • L - Lymph nodes (hilar + mediastinal)
  • E - Effusions (pleural and pericardial)
  • P - Pleura and chest wall
  • V - Vessels (aorta, pulmonary arteries, CTPA filling defects)
  • B - Bones (ribs, vertebrae, sternum)

Key References:

Create a visual reference guide with labeled HRCT patterns and pathology

Asking for Preferences
Reading File
Running Command

mkdir -p /home/daytona/workspace/hrct-guide && ls /home/daytona/workspace/hrct-g…

Writing File

~/hrct-guide/generate_hrct_guide.py

"""
HRCT & CECT Chest Visual Reference Guide
Multi-page PDF using reportlab with visual pattern diagrams drawn programmatically
"""
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, TA_RIGHT
from reportlab.graphics.shapes import (Drawing, Rect, Circle, Line, String,
                                        Ellipse, Polygon, PolyLine, Group)
from reportlab.graphics import renderPDF
from reportlab.platypus import Flowable
from reportlab.lib.colors import (HexColor, white, black, grey, lightgrey,
                                   darkgrey, Color)
import math

# ── Colour palette ─────────────────────────────────────────────────────────────
NAVY      = HexColor('#0D2B45')
TEAL      = HexColor('#1B6CA8')
CYAN      = HexColor('#3ABFD4')
GOLD      = HexColor('#F5A623')
ORANGE    = HexColor('#E07B3A')
RED       = HexColor('#C0392B')
GREEN     = HexColor('#27AE60')
PURPLE    = HexColor('#8E44AD')
LBLUE     = HexColor('#D6EAF8')
LGOLD     = HexColor('#FEF9E7')
LGREEN    = HexColor('#E9F7EF')
LRED      = HexColor('#FDEDEC')
LPURPLE   = HexColor('#F5EEF8')
LGREY     = HexColor('#F2F3F4')
DARKGREY  = HexColor('#2C3E50')
MIDGREY   = HexColor('#7F8C8D')
PAGEBG    = HexColor('#FAFBFC')

# ── Lung grey scale (CT appearance) ────────────────────────────────────────────
CT_AIRSPACE = HexColor('#0A0A0A')      # almost black – normal aerated lung
CT_GGO      = HexColor('#505050')      # dark grey – ground glass
CT_CONSOL   = HexColor('#C8C8C8')      # light grey – consolidation / soft tissue
CT_VESSEL   = HexColor('#FFFFFF')      # white – vessels / enhanced structures
CT_HONEY    = HexColor('#1A1A1A')      # very dark – honeycombing cysts
CT_FIBROSIS = HexColor('#888888')      # medium grey – reticular fibrosis

W, H = A4  # 595 x 842 pt

# ── Styles ─────────────────────────────────────────────────────────────────────
def make_styles():
    base = getSampleStyleSheet()

    def s(name, parent='Normal', **kw):
        return ParagraphStyle(name, parent=base[parent], **kw)

    styles = {
        'cover_title': s('CoverTitle',
            fontName='Helvetica-Bold', fontSize=28, textColor=white,
            leading=34, alignment=TA_CENTER, spaceAfter=8),
        'cover_sub': s('CoverSub',
            fontName='Helvetica', fontSize=14, textColor=HexColor('#D6EAF8'),
            leading=18, alignment=TA_CENTER, spaceAfter=4),
        'cover_tag': s('CoverTag',
            fontName='Helvetica-Oblique', fontSize=10, textColor=HexColor('#AED6F1'),
            leading=14, alignment=TA_CENTER),
        'section_h': s('SectionH',
            fontName='Helvetica-Bold', fontSize=16, textColor=white,
            leading=20, alignment=TA_LEFT, spaceAfter=0, spaceBefore=0),
        'page_title': s('PageTitle',
            fontName='Helvetica-Bold', fontSize=18, textColor=NAVY,
            leading=22, alignment=TA_LEFT, spaceBefore=4, spaceAfter=6),
        'sub_h': s('SubH',
            fontName='Helvetica-Bold', fontSize=12, textColor=TEAL,
            leading=15, spaceBefore=8, spaceAfter=3),
        'body': s('Body',
            fontName='Helvetica', fontSize=8.5, textColor=DARKGREY,
            leading=13, spaceBefore=1, spaceAfter=1),
        'small': s('Small',
            fontName='Helvetica', fontSize=7.5, textColor=MIDGREY,
            leading=11, spaceBefore=0, spaceAfter=0),
        'label': s('Label',
            fontName='Helvetica-Bold', fontSize=7.5, textColor=NAVY,
            leading=10, alignment=TA_CENTER),
        'caption': s('Caption',
            fontName='Helvetica-Oblique', fontSize=7.5, textColor=MIDGREY,
            leading=10, alignment=TA_CENTER, spaceAfter=4),
        'table_h': s('TableH',
            fontName='Helvetica-Bold', fontSize=8, textColor=white,
            leading=11, alignment=TA_CENTER),
        'table_cell': s('TableCell',
            fontName='Helvetica', fontSize=8, textColor=DARKGREY,
            leading=11, alignment=TA_LEFT),
        'bullet': s('Bullet',
            fontName='Helvetica', fontSize=8.5, textColor=DARKGREY,
            leading=13, leftIndent=12, firstLineIndent=-8, spaceAfter=2),
        'footer': s('Footer',
            fontName='Helvetica', fontSize=7, textColor=MIDGREY,
            alignment=TA_CENTER),
        'tip_text': s('TipText',
            fontName='Helvetica', fontSize=8, textColor=HexColor('#1A5276'),
            leading=12),
    }
    return styles

ST = make_styles()

# ── Helper Flowables ────────────────────────────────────────────────────────────
class ColorBand(Flowable):
    """Full-width coloured header band"""
    def __init__(self, text, bg=TEAL, fg=white, height=28, fs=13, bold=True):
        super().__init__()
        self.text = text
        self.bg = bg
        self.fg = fg
        self.height = height
        self.fs = fs
        self.bold = bold
        self.width = W - 3*cm

    def wrap(self, aw, ah):
        self.width = aw
        return aw, self.height

    def draw(self):
        c = self.canv
        c.setFillColor(self.bg)
        c.roundRect(0, 0, self.width, self.height, 4, fill=1, stroke=0)
        fn = 'Helvetica-Bold' if self.bold else 'Helvetica'
        c.setFont(fn, self.fs)
        c.setFillColor(self.fg)
        c.drawString(10, self.height/2 - self.fs*0.35, self.text)


class TipBox(Flowable):
    def __init__(self, text, bg=LBLUE, border=TEAL, label='KEY POINT', width=None):
        super().__init__()
        self.text = text
        self.bg = bg
        self.border = border
        self.label = label
        self._width = width or (W - 3*cm)
        self.height = 40

    def wrap(self, aw, ah):
        self._width = aw
        return aw, self.height

    def draw(self):
        c = self.canv
        c.setFillColor(self.bg)
        c.roundRect(0, 2, self._width, self.height-4, 5, fill=1, stroke=0)
        c.setStrokeColor(self.border)
        c.setLineWidth(1.5)
        c.roundRect(0, 2, self._width, self.height-4, 5, fill=0, stroke=1)
        # label
        c.setFont('Helvetica-Bold', 7)
        c.setFillColor(self.border)
        c.drawString(8, self.height - 12, self.label)
        c.setFont('Helvetica', 8)
        c.setFillColor(DARKGREY)
        c.drawString(8, 8, self.text)


# ── CT Pattern Drawing Functions ───────────────────────────────────────────────
def draw_lung_outline(d, x, y, w, h, fill_color=CT_AIRSPACE, label=None,
                      border_color=HexColor('#444444'), label_color=white):
    """Draw a simplified lung circle with label"""
    d.add(Circle(x + w/2, y + h/2, min(w, h)/2 - 2,
                 fillColor=fill_color, strokeColor=border_color, strokeWidth=1))
    if label:
        d.add(String(x + w/2, y + h/2 - 4, label,
                     fontSize=7, fillColor=label_color,
                     textAnchor='middle', fontName='Helvetica-Bold'))


def draw_vessel(d, x1, y1, x2, y2, color=CT_VESSEL, width=2):
    d.add(Line(x1, y1, x2, y2, strokeColor=color, strokeWidth=width))


def pattern_normal(w=120, h=100):
    """Normal HRCT - dark lung with thin branching vessels"""
    d = Drawing(w, h)
    # Background circle
    d.add(Circle(w/2, h/2, min(w,h)/2-3,
                 fillColor=CT_AIRSPACE, strokeColor=HexColor('#333333'), strokeWidth=1))
    # Main bronchus/artery
    d.add(Line(w/2, h*0.9, w/2, h*0.55, strokeColor=CT_VESSEL, strokeWidth=2.5))
    # Branching vessels
    d.add(Line(w/2, h*0.75, w*0.3, h*0.5, strokeColor=CT_VESSEL, strokeWidth=1.5))
    d.add(Line(w/2, h*0.75, w*0.7, h*0.5, strokeColor=CT_VESSEL, strokeWidth=1.5))
    d.add(Line(w*0.3, h*0.5, w*0.2, h*0.3, strokeColor=CT_VESSEL, strokeWidth=1))
    d.add(Line(w*0.3, h*0.5, w*0.4, h*0.3, strokeColor=CT_VESSEL, strokeWidth=1))
    d.add(Line(w*0.7, h*0.5, w*0.6, h*0.3, strokeColor=CT_VESSEL, strokeWidth=1))
    d.add(Line(w*0.7, h*0.5, w*0.8, h*0.3, strokeColor=CT_VESSEL, strokeWidth=1))
    # Label
    d.add(String(w/2, 6, 'NORMAL', fontSize=7, fillColor=CT_VESSEL,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    return d


def pattern_ggo(w=120, h=100):
    """Ground Glass Opacity - hazy grey overlay"""
    d = Drawing(w, h)
    d.add(Circle(w/2, h/2, min(w,h)/2-3,
                 fillColor=CT_AIRSPACE, strokeColor=HexColor('#333333'), strokeWidth=1))
    # GGO patches
    for cx, cy, r in [(w*0.45, h*0.6, 22), (w*0.55, h*0.45, 18), (w*0.35, h*0.4, 15)]:
        d.add(Circle(cx, cy, r, fillColor=CT_GGO, strokeColor=None, strokeWidth=0))
    # Vessels visible through GGO
    d.add(Line(w/2, h*0.85, w/2, h*0.5, strokeColor=CT_VESSEL, strokeWidth=2))
    d.add(Line(w/2, h*0.7, w*0.35, h*0.5, strokeColor=CT_VESSEL, strokeWidth=1.5))
    d.add(String(w/2, 6, 'GROUND GLASS', fontSize=7, fillColor=CT_VESSEL,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    return d


def pattern_consolidation(w=120, h=100):
    """Consolidation - dense opacification"""
    d = Drawing(w, h)
    d.add(Circle(w/2, h/2, min(w,h)/2-3,
                 fillColor=CT_AIRSPACE, strokeColor=HexColor('#333333'), strokeWidth=1))
    # Consolidated area (lower/peripheral)
    pts = [w*0.2, h*0.25, w*0.8, h*0.25, w*0.85, h*0.65, w*0.15, h*0.65]
    d.add(Polygon(pts, fillColor=CT_CONSOL, strokeColor=None))
    # Air bronchogram - dark bronchi within consolidation
    d.add(Line(w/2, h*0.6, w/2, h*0.3, strokeColor=CT_AIRSPACE, strokeWidth=2))
    d.add(Line(w/2, h*0.45, w*0.35, h*0.3, strokeColor=CT_AIRSPACE, strokeWidth=1.5))
    d.add(Line(w/2, h*0.45, w*0.65, h*0.3, strokeColor=CT_AIRSPACE, strokeWidth=1.5))
    d.add(String(w/2, 6, 'CONSOLIDATION', fontSize=7, fillColor=CT_VESSEL,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    return d


def pattern_reticulation(w=120, h=100):
    """Reticular pattern - septal thickening / fibrosis"""
    d = Drawing(w, h)
    d.add(Circle(w/2, h/2, min(w,h)/2-3,
                 fillColor=CT_AIRSPACE, strokeColor=HexColor('#333333'), strokeWidth=1))
    # Reticular lines (grid-like)
    line_c = HexColor('#AAAAAA')
    for i in range(5):
        y = h*0.25 + i*h*0.12
        d.add(Line(w*0.15, y, w*0.85, y, strokeColor=line_c, strokeWidth=0.8))
    for i in range(5):
        x = w*0.2 + i*w*0.14
        d.add(Line(x, h*0.2, x, h*0.8, strokeColor=line_c, strokeWidth=0.8))
    # Subpleural accentuation
    d.add(Circle(w/2, h/2, min(w,h)/2-8,
                 fillColor=None, strokeColor=HexColor('#888888'), strokeWidth=1.5))
    d.add(String(w/2, 6, 'RETICULATION', fontSize=7, fillColor=CT_VESSEL,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    return d


def pattern_honeycombing(w=120, h=100):
    """Honeycombing - clustered cystic spaces, subpleural"""
    d = Drawing(w, h)
    d.add(Circle(w/2, h/2, min(w,h)/2-3,
                 fillColor=CT_AIRSPACE, strokeColor=HexColor('#333333'), strokeWidth=1))
    # Subpleural honeycomb cysts
    cyst_c = CT_HONEY
    wall_c = HexColor('#BBBBBB')
    positions = [
        (w*0.65, h*0.28), (w*0.75, h*0.35), (w*0.72, h*0.48),
        (w*0.62, h*0.55), (w*0.52, h*0.50), (w*0.58, h*0.38),
        (w*0.68, h*0.62), (w*0.78, h*0.55),
    ]
    for cx, cy in positions:
        r = 7
        d.add(Circle(cx, cy, r, fillColor=cyst_c, strokeColor=wall_c, strokeWidth=1.5))
    d.add(String(w/2, 6, 'HONEYCOMBING', fontSize=7, fillColor=CT_VESSEL,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    return d


def pattern_nodules(w=120, h=100, nodule_type='random'):
    """Nodular pattern"""
    d = Drawing(w, h)
    d.add(Circle(w/2, h/2, min(w,h)/2-3,
                 fillColor=CT_AIRSPACE, strokeColor=HexColor('#333333'), strokeWidth=1))
    if nodule_type == 'centrilobular':
        positions = [(w*0.3, h*0.65), (w*0.5, h*0.7), (w*0.7, h*0.65),
                     (w*0.25, h*0.45), (w*0.5, h*0.5), (w*0.75, h*0.45),
                     (w*0.35, h*0.3), (w*0.65, h*0.3)]
        nc = HexColor('#DDDDDD')
    elif nodule_type == 'perilymphatic':
        positions = [(w*0.35, h*0.7), (w*0.5, h*0.75), (w*0.65, h*0.7),
                     (w*0.5, h*0.55), (w*0.3, h*0.4), (w*0.7, h*0.4)]
        nc = HexColor('#EEEEEE')
    else:  # random
        positions = [(w*0.25, h*0.7), (w*0.5, h*0.65), (w*0.72, h*0.7),
                     (w*0.3, h*0.45), (w*0.6, h*0.5), (w*0.75, h*0.35),
                     (w*0.4, h*0.28), (w*0.55, h*0.35)]
        nc = HexColor('#FFFFFF')
    for cx, cy in positions:
        d.add(Circle(cx, cy, 5, fillColor=nc, strokeColor=None))
    label = {'centrilobular': 'CENTRILOBULAR', 'perilymphatic': 'PERILYMPHATIC',
             'random': 'RANDOM'}.get(nodule_type, 'NODULES')
    d.add(String(w/2, 6, label, fontSize=7, fillColor=CT_VESSEL,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    return d


def pattern_emphysema(w=120, h=100):
    """Centrilobular emphysema - bullous low attenuation areas"""
    d = Drawing(w, h)
    d.add(Circle(w/2, h/2, min(w,h)/2-3,
                 fillColor=CT_AIRSPACE, strokeColor=HexColor('#333333'), strokeWidth=1))
    # Emphysematous spaces (slightly darker than background, no walls)
    emp_c = HexColor('#050505')
    for cx, cy, r in [(w*0.35, h*0.65, 12), (w*0.55, h*0.6, 10),
                      (w*0.45, h*0.45, 14), (w*0.65, h*0.5, 11),
                      (w*0.3, h*0.45, 9)]:
        d.add(Circle(cx, cy, r, fillColor=emp_c, strokeColor=HexColor('#1A1A1A'), strokeWidth=0.3))
    # Centrilobular artery dot in center of some
    for cx, cy in [(w*0.35, h*0.65), (w*0.45, h*0.45)]:
        d.add(Circle(cx, cy, 2, fillColor=HexColor('#CCCCCC'), strokeColor=None))
    d.add(String(w/2, 6, 'EMPHYSEMA', fontSize=7, fillColor=CT_VESSEL,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    return d


def pattern_bronchiectasis(w=120, h=100):
    """Bronchiectasis - signet ring sign"""
    d = Drawing(w, h)
    d.add(Circle(w/2, h/2, min(w,h)/2-3,
                 fillColor=CT_AIRSPACE, strokeColor=HexColor('#333333'), strokeWidth=1))
    # Multiple signet ring signs
    for cx, cy, br, ar in [(w*0.35, h*0.65, 10, 6),
                            (w*0.6, h*0.6, 9, 5),
                            (w*0.45, h*0.45, 11, 7)]:
        # Dilated bronchus (ring)
        d.add(Circle(cx, cy, br, fillColor=CT_AIRSPACE,
                     strokeColor=HexColor('#BBBBBB'), strokeWidth=2))
        # Adjacent artery (solid)
        d.add(Circle(cx + br + ar + 1, cy, ar,
                     fillColor=HexColor('#BBBBBB'), strokeColor=None))
    d.add(String(w/2, 6, 'BRONCHIECTASIS', fontSize=7, fillColor=CT_VESSEL,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    return d


def pattern_crazy_paving(w=120, h=100):
    """Crazy paving - GGO + septal thickening"""
    d = Drawing(w, h)
    d.add(Circle(w/2, h/2, min(w,h)/2-3,
                 fillColor=CT_GGO, strokeColor=HexColor('#333333'), strokeWidth=1))
    # Superimposed lines (septal thickening)
    line_c = HexColor('#CCCCCC')
    for i in range(6):
        y = h*0.2 + i*h*0.11
        d.add(Line(w*0.15, y, w*0.85, y, strokeColor=line_c, strokeWidth=1.2))
    for i in range(5):
        x = w*0.2 + i*w*0.15
        d.add(Line(x, h*0.15, x, h*0.85, strokeColor=line_c, strokeWidth=1.2))
    d.add(String(w/2, 6, 'CRAZY PAVING', fontSize=7, fillColor=CT_VESSEL,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    return d


def pattern_mosaic(w=120, h=100):
    """Mosaic attenuation - air trapping"""
    d = Drawing(w, h)
    d.add(Circle(w/2, h/2, min(w,h)/2-3,
                 fillColor=CT_AIRSPACE, strokeColor=HexColor('#333333'), strokeWidth=1))
    # Alternating dark/grey lobules
    lobule_positions = [
        (w*0.35, h*0.65, HexColor('#050505')),   # very dark = air trapped
        (w*0.55, h*0.65, HexColor('#383838')),   # slightly brighter
        (w*0.25, h*0.50, HexColor('#383838')),
        (w*0.45, h*0.50, HexColor('#050505')),
        (w*0.65, h*0.50, HexColor('#383838')),
        (w*0.35, h*0.35, HexColor('#050505')),
        (w*0.55, h*0.35, HexColor('#383838')),
    ]
    for cx, cy, fc in lobule_positions:
        d.add(Rect(cx-12, cy-8, 22, 16, fillColor=fc, strokeColor=HexColor('#2A2A2A'), strokeWidth=0.5))
    d.add(String(w/2, 6, 'MOSAIC/AIR TRAP', fontSize=7, fillColor=CT_VESSEL,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    return d


def pattern_mass(w=120, h=100):
    """Pulmonary mass with spiculation"""
    d = Drawing(w, h)
    d.add(Circle(w/2, h/2, min(w,h)/2-3,
                 fillColor=CT_AIRSPACE, strokeColor=HexColor('#333333'), strokeWidth=1))
    cx, cy = w*0.5, h*0.55
    r = 18
    # Spicules
    for angle_deg in range(0, 360, 30):
        angle = math.radians(angle_deg)
        d.add(Line(cx + r*math.cos(angle), cy + r*math.sin(angle),
                   cx + (r+8)*math.cos(angle), cy + (r+8)*math.sin(angle),
                   strokeColor=HexColor('#AAAAAA'), strokeWidth=0.8))
    # Mass
    d.add(Circle(cx, cy, r, fillColor=HexColor('#AAAAAA'), strokeColor=HexColor('#DDDDDD'), strokeWidth=0.5))
    d.add(String(w/2, 6, 'SPICULATED MASS', fontSize=7, fillColor=CT_VESSEL,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    return d


# ── Secondary Lobule Diagram ────────────────────────────────────────────────────
def draw_secondary_lobule(w=300, h=220):
    d = Drawing(w, h)
    # Background
    d.add(Rect(0, 0, w, h, fillColor=LGREY, strokeColor=None))
    # Hexagonal lobule outline
    cx, cy = w*0.4, h*0.52
    side = 60
    pts = []
    for i in range(6):
        angle = math.radians(60*i - 30)
        pts.extend([cx + side*math.cos(angle), cy + side*math.sin(angle)])
    d.add(Polygon(pts, fillColor=HexColor('#E8F4FD'), strokeColor=TEAL, strokeWidth=2))
    # Centrilobular artery (center)
    d.add(Circle(cx, cy, 5, fillColor=RED, strokeColor=None))
    # Centrilobular bronchiole
    d.add(Circle(cx+8, cy+3, 4, fillColor=CT_AIRSPACE, strokeColor=HexColor('#888888'), strokeWidth=1.5))
    # Interlobular septa with veins
    for i in range(6):
        angle = math.radians(60*i - 30)
        x1 = cx + 5*math.cos(angle)
        y1 = cy + 5*math.sin(angle)
        x2 = cx + side*math.cos(angle)
        y2 = cy + side*math.sin(angle)
        d.add(Line(x1, y1, x2, y2, strokeColor=PURPLE, strokeWidth=2))
        # Vein along septa
        d.add(Circle(cx + (side*0.6)*math.cos(angle),
                     cy + (side*0.6)*math.sin(angle),
                     3, fillColor=PURPLE, strokeColor=None))
    # Alveolar clusters
    for i in range(6):
        angle = math.radians(60*i)
        ax = cx + 30*math.cos(angle)
        ay = cy + 30*math.sin(angle)
        d.add(Circle(ax, ay, 7, fillColor=HexColor('#BDE3F0'),
                     strokeColor=HexColor('#7FB3C8'), strokeWidth=0.8))
    # Labels
    d.add(String(cx+12, cy+8, 'Centrilobular', fontSize=6.5, fillColor=RED,
                 fontName='Helvetica-Bold'))
    d.add(String(cx+12, cy+1, 'artery + bronchiole', fontSize=6, fillColor=RED))

    # Legend panel on right
    lx = w*0.72
    items = [
        (RED, 'Centrilobular artery'),
        (HexColor('#0A0A0A'), 'Centrilobular bronchiole'),
        (PURPLE, 'Interlobular septum / vein'),
        (HexColor('#7FB3C8'), 'Alveolar sacs'),
    ]
    legend_y = h*0.85
    for fc, lbl in items:
        d.add(Circle(lx, legend_y, 5, fillColor=fc, strokeColor=MIDGREY, strokeWidth=0.5))
        d.add(String(lx+10, legend_y-4, lbl, fontSize=6.5, fillColor=DARKGREY))
        legend_y -= 16
    d.add(String(lx-5, h*0.93, 'Legend:', fontSize=7, fillColor=DARKGREY, fontName='Helvetica-Bold'))
    d.add(String(cx - 20, 8, 'Secondary Pulmonary Lobule', fontSize=8,
                 fillColor=TEAL, fontName='Helvetica-Bold', textAnchor='middle'))
    return d


# ── UIP Pattern Diagram ────────────────────────────────────────────────────────
def draw_uip_comparison(w=500, h=130):
    d = Drawing(w, h)
    d.add(Rect(0, 0, w, h, fillColor=LGREY, strokeColor=None))
    labels = ['Typical UIP', 'Probable UIP', 'Indeterminate', 'Non-UIP/Alt Dx']
    colors_ = [GREEN, TEAL, GOLD, RED]
    x_positions = [w*0.12, w*0.37, w*0.62, w*0.87]
    findings = [
        ['Subpleural+basal', 'Reticulation', 'Traction bronchiect.', 'HONEYCOMBING'],
        ['Subpleural+basal', 'Reticulation', 'Traction bronchiect.', 'No honeycombing'],
        ['Variable/diffuse', 'Some fibrosis', 'Inconclusive', 'features'],
        ['Upper/mid lung', 'Consolidation', 'Diffuse GGO', 'Diffuse cysts'],
    ]
    for i, (lbl, col, xc, flist) in enumerate(zip(labels, colors_, x_positions, findings)):
        # Header circle
        d.add(Circle(xc, h*0.82, 14, fillColor=col, strokeColor=None))
        d.add(String(xc, h*0.78, str(i+1), fontSize=10, fillColor=white,
                     textAnchor='middle', fontName='Helvetica-Bold'))
        d.add(String(xc, h*0.64, lbl, fontSize=7.5, fillColor=col,
                     textAnchor='middle', fontName='Helvetica-Bold'))
        for j, f in enumerate(flist):
            bullet = u'\u2022 '
            d.add(String(xc - 38, h*0.50 - j*13, bullet + f, fontSize=6.5,
                         fillColor=DARKGREY))
    # Biopsy indicator
    d.add(String(w*0.62, 12, 'Biopsy needed', fontSize=7, fillColor=RED,
                 fontName='Helvetica-Bold', textAnchor='middle'))
    d.add(String(w*0.87, 12, 'Biopsy needed', fontSize=7, fillColor=RED,
                 fontName='Helvetica-Bold', textAnchor='middle'))
    d.add(String(w*0.12, 12, 'No biopsy', fontSize=7, fillColor=GREEN,
                 fontName='Helvetica-Bold', textAnchor='middle'))
    d.add(String(w*0.37, 12, 'Usually no biopsy', fontSize=7, fillColor=GREEN,
                 fontName='Helvetica-Bold', textAnchor='middle'))
    return d


# ── Window Settings Diagram ────────────────────────────────────────────────────
def draw_window_comparison(w=400, h=100):
    d = Drawing(w, h)
    d.add(Rect(0, 0, w, h, fillColor=LGREY, strokeColor=None))
    windows = [
        ('Lung Window', 'W:1500 / L:-700',
         [CT_AIRSPACE, HexColor('#505050'), HexColor('#888888'), HexColor('#CCCCCC')],
         ['Airspace', 'GGO', 'Vessels', 'Consol.']),
        ('Mediastinal', 'W:400 / L:40',
         [HexColor('#888888'), HexColor('#AAAAAA'), HexColor('#FFFFFF'), HexColor('#DDDDDD')],
         ['Tissue', 'Fat', 'Bone', 'Vessels']),
        ('Bone Window', 'W:2000 / L:400',
         [HexColor('#000000'), HexColor('#666666'), HexColor('#BBBBBB'), HexColor('#FFFFFF')],
         ['Soft Tiss.', 'Cortex', 'Medulla', 'Dense']),
    ]
    wx = [w*0.18, w*0.5, w*0.82]
    for (wname, wset, wcolors, wlabels), xc in zip(windows, wx):
        d.add(String(xc, h*0.93, wname, fontSize=8, fillColor=TEAL,
                     textAnchor='middle', fontName='Helvetica-Bold'))
        d.add(String(xc, h*0.82, wset, fontSize=7, fillColor=MIDGREY,
                     textAnchor='middle'))
        for k, (wc, wl) in enumerate(zip(wcolors, wlabels)):
            bx = xc - 30 + k*16
            d.add(Rect(bx, h*0.45, 14, 22, fillColor=wc,
                       strokeColor=HexColor('#999999'), strokeWidth=0.5))
            d.add(String(bx+7, h*0.35, wl, fontSize=5.5, fillColor=DARKGREY,
                         textAnchor='middle'))
    return d


# ── CECT Applications Diagram ──────────────────────────────────────────────────
def draw_cect_phases(w=450, h=80):
    d = Drawing(w, h)
    d.add(Rect(0, 0, w, h, fillColor=LGREY, strokeColor=None))
    phases = [
        ('Non-contrast', '0 sec', DARKGREY, 'Calcification\nHemorrhage'),
        ('Arterial/CTPA', '15-20 sec', RED, 'Pulm. arteries\nAorta\nEmbolism'),
        ('Venous', '60-70 sec', TEAL, 'Mediastinum\nLymph nodes\nMasses'),
        ('Delayed', '3-5 min', PURPLE, 'Fibrous tumors\nInfection'),
    ]
    pw = w / len(phases)
    for i, (name, timing, col, desc) in enumerate(phases):
        xc = (i + 0.5) * pw
        # Arrow connector
        if i < len(phases) - 1:
            d.add(Line((i+1)*pw - 5, h*0.65, (i+1)*pw + 5, h*0.65,
                       strokeColor=MIDGREY, strokeWidth=1))
        d.add(Rect(xc-30, h*0.5, 60, 22, fillColor=col, strokeColor=None,
                   rx=4, ry=4))
        d.add(String(xc, h*0.56, name, fontSize=7, fillColor=white,
                     textAnchor='middle', fontName='Helvetica-Bold'))
        d.add(String(xc, h*0.44, timing, fontSize=6.5, fillColor=col,
                     textAnchor='middle', fontName='Helvetica-Bold'))
        for j, line in enumerate(desc.split('\n')):
            d.add(String(xc, h*0.28 - j*10, line, fontSize=6, fillColor=DARKGREY,
                         textAnchor='middle'))
    return d


# ── Distribution Map ───────────────────────────────────────────────────────────
def draw_distribution_map(w=460, h=150):
    d = Drawing(w, h)
    d.add(Rect(0, 0, w, h, fillColor=LGREY, strokeColor=None))

    def lung_shape(d, xc, yc, wr, hr, fill, label_text):
        # Simplified lung as ellipse
        d.add(Ellipse(xc, yc, wr, hr, fillColor=fill,
                      strokeColor=HexColor('#555555'), strokeWidth=1))
        d.add(String(xc, yc - 4, label_text, fontSize=6, fillColor=white,
                     textAnchor='middle', fontName='Helvetica-Bold'))

    # Coronal view pair
    cx = w*0.25
    # Right lung
    lung_shape(d, cx - 22, h*0.5, 18, 50, HexColor('#1B6CA8'), 'R')
    # Left lung
    lung_shape(d, cx + 22, h*0.5, 16, 48, HexColor('#1B6CA8'), 'L')
    # Upper zone highlight (e.g. sarcoidosis)
    d.add(Ellipse(cx - 22, h*0.75, 16, 15,
                  fillColor=HexColor('#F5A623'), strokeColor=None))
    d.add(Ellipse(cx + 22, h*0.75, 14, 14,
                  fillColor=HexColor('#F5A623'), strokeColor=None))
    d.add(String(cx, h*0.93, 'Upper Zone', fontSize=7, fillColor=GOLD,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    d.add(String(cx, h*0.85, 'Sarcoid, Silicosis,', fontSize=6.5, fillColor=DARKGREY,
                 textAnchor='middle'))
    d.add(String(cx, h*0.77, 'LCH, Smoking-ILD', fontSize=6.5, fillColor=DARKGREY,
                 textAnchor='middle'))
    d.add(String(cx, h*0.10, 'UPPER ZONE', fontSize=7.5, fillColor=GOLD,
                 textAnchor='middle', fontName='Helvetica-Bold'))

    # Lower zone
    cx2 = w*0.5
    lung_shape(d, cx2 - 22, h*0.5, 18, 50, HexColor('#1B6CA8'), 'R')
    lung_shape(d, cx2 + 22, h*0.5, 16, 48, HexColor('#1B6CA8'), 'L')
    d.add(Ellipse(cx2 - 22, h*0.28, 16, 18,
                  fillColor=HexColor('#E74C3C'), strokeColor=None))
    d.add(Ellipse(cx2 + 22, h*0.28, 14, 17,
                  fillColor=HexColor('#E74C3C'), strokeColor=None))
    d.add(String(cx2, h*0.93, 'Lower Zone', fontSize=7, fillColor=RED,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    d.add(String(cx2, h*0.85, 'IPF/UIP, NSIP,', fontSize=6.5, fillColor=DARKGREY,
                 textAnchor='middle'))
    d.add(String(cx2, h*0.77, 'Aspiration, Edema', fontSize=6.5, fillColor=DARKGREY,
                 textAnchor='middle'))
    d.add(String(cx2, h*0.10, 'LOWER ZONE', fontSize=7.5, fillColor=RED,
                 textAnchor='middle', fontName='Helvetica-Bold'))

    # Peripheral / Central
    cx3 = w*0.76
    lung_shape(d, cx3 - 22, h*0.5, 18, 50, HexColor('#1B6CA8'), 'R')
    lung_shape(d, cx3 + 22, h*0.5, 16, 48, HexColor('#1B6CA8'), 'L')
    # Peripheral ring
    d.add(Ellipse(cx3 - 22, h*0.5, 18, 50,
                  fillColor=None, strokeColor=GREEN, strokeWidth=3))
    d.add(Ellipse(cx3 + 22, h*0.5, 16, 48,
                  fillColor=None, strokeColor=GREEN, strokeWidth=3))
    d.add(String(cx3, h*0.93, 'Peripheral', fontSize=7, fillColor=GREEN,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    d.add(String(cx3, h*0.85, 'UIP, COP,', fontSize=6.5, fillColor=DARKGREY,
                 textAnchor='middle'))
    d.add(String(cx3, h*0.77, 'Eosinophilic PNA', fontSize=6.5, fillColor=DARKGREY,
                 textAnchor='middle'))
    d.add(String(cx3, h*0.10, 'PERIPH/CENTRAL', fontSize=7.5, fillColor=GREEN,
                 textAnchor='middle', fontName='Helvetica-Bold'))
    return d


# ── Page header/footer callbacks ────────────────────────────────────────────────
def page_header_footer(canvas, doc):
    canvas.saveState()
    # Header bar
    canvas.setFillColor(NAVY)
    canvas.rect(0, H - 28, W, 28, fill=1, stroke=0)
    canvas.setFont('Helvetica-Bold', 9)
    canvas.setFillColor(white)
    canvas.drawString(1.5*cm, H - 18, 'HRCT & CECT CHEST  |  Visual Reference Guide')
    canvas.setFont('Helvetica', 8)
    canvas.setFillColor(CYAN)
    canvas.drawRightString(W - 1.5*cm, H - 18, 'Orris Medical  2026')
    # Footer
    canvas.setFillColor(LGREY)
    canvas.rect(0, 0, W, 18, fill=1, stroke=0)
    canvas.setFont('Helvetica', 7)
    canvas.setFillColor(MIDGREY)
    canvas.drawCentredString(W/2, 5, f'Page {doc.page}  |  For educational use only')
    canvas.restoreState()


def cover_page(canvas, doc):
    canvas.saveState()
    # Full gradient-like background
    canvas.setFillColor(NAVY)
    canvas.rect(0, 0, W, H, fill=1, stroke=0)
    # Accent band top
    canvas.setFillColor(TEAL)
    canvas.rect(0, H - 8, W, 8, fill=1, stroke=0)
    # Bottom accent
    canvas.setFillColor(TEAL)
    canvas.rect(0, 0, W, 8, fill=1, stroke=0)
    # Decorative circles
    canvas.setFillColor(HexColor('#1A3A5C'))
    canvas.circle(W*0.85, H*0.75, 120, fill=1, stroke=0)
    canvas.setFillColor(HexColor('#153250'))
    canvas.circle(W*0.1, H*0.2, 80, fill=1, stroke=0)
    canvas.restoreState()


# ── Build PDF ──────────────────────────────────────────────────────────────────
def build_pdf(path):
    doc = SimpleDocTemplate(
        path,
        pagesize=A4,
        leftMargin=1.5*cm,
        rightMargin=1.5*cm,
        topMargin=1.8*cm,
        bottomMargin=1.2*cm,
    )

    story = []

    # ── COVER PAGE ───────────────────────────────────────────────────────────
    story.append(Spacer(1, 4*cm))
    story.append(Paragraph('HRCT & CECT CHEST', ST['cover_title']))
    story.append(Paragraph('Visual Reference Guide', ST['cover_sub']))
    story.append(Spacer(1, 0.4*cm))
    story.append(Paragraph('Patterns · Pathology · Systematic Approach', ST['cover_tag']))
    story.append(Spacer(1, 0.6*cm))
    story.append(Paragraph('Fleischner Society Classification  ·  CT Windows  ·  CECT Phases',
                            ST['cover_tag']))
    story.append(Spacer(1, 2*cm))

    # Mini contents list
    toc_data = [
        [Paragraph('Section', ST['table_h']), Paragraph('Page', ST['table_h'])],
        [Paragraph('1. Technical Essentials & CT Windows', ST['table_cell']), Paragraph('2', ST['table_cell'])],
        [Paragraph('2. Secondary Pulmonary Lobule Anatomy', ST['table_cell']), Paragraph('3', ST['table_cell'])],
        [Paragraph('3. HRCT Patterns Gallery', ST['table_cell']), Paragraph('4', ST['table_cell'])],
        [Paragraph('4. Distribution & Zonal Patterns', ST['table_cell']), Paragraph('5', ST['table_cell'])],
        [Paragraph('5. Nodule Distribution: Centrilobular vs Perilymphatic vs Random', ST['table_cell']), Paragraph('6', ST['table_cell'])],
        [Paragraph('6. Fleischner UIP Classification', ST['table_cell']), Paragraph('7', ST['table_cell'])],
        [Paragraph('7. CECT Chest: Phases & Applications', ST['table_cell']), Paragraph('8', ST['table_cell'])],
        [Paragraph('8. Systematic Reading Checklist', ST['table_cell']), Paragraph('9', ST['table_cell'])],
        [Paragraph('9. Quick Pattern-Disease Lookup Table', ST['table_cell']), Paragraph('10', ST['table_cell'])],
    ]
    toc_style = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), TEAL),
        ('TEXTCOLOR', (0, 0), (-1, 0), white),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [HexColor('#D6EAF8'), HexColor('#EBF5FB')]),
        ('GRID', (0, 0), (-1, -1), 0.5, HexColor('#AED6F1')),
        ('FONTSIZE', (0, 1), (-1, -1), 9),
        ('LEFTPADDING', (0, 0), (-1, -1), 8),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
    ])
    toc = Table(toc_data, colWidths=[12*cm, 2*cm])
    toc.setStyle(toc_style)
    story.append(toc)

    story.append(Spacer(1, 1.5*cm))
    story.append(Paragraph('Based on Murray & Nadel\'s Respiratory Medicine, Fishman\'s Pulmonary Diseases, '
                            'Fleischner Society 2018 UIP Criteria, and Radiological Society guidelines.',
                            ST['cover_tag']))
    story.append(PageBreak())

    # ── PAGE 2 – Technical Essentials ────────────────────────────────────────
    story.append(ColorBand('  SECTION 1 – TECHNICAL ESSENTIALS & CT WINDOWS', bg=NAVY, height=30))
    story.append(Spacer(1, 6))

    # Window settings
    story.append(Paragraph('CT Window Settings', ST['sub_h']))
    win_data = [
        [Paragraph('Window', ST['table_h']),
         Paragraph('Width (W)', ST['table_h']),
         Paragraph('Level (L)', ST['table_h']),
         Paragraph('Best For', ST['table_h'])],
        [Paragraph('Lung', ST['table_cell']),
         Paragraph('1500 HU', ST['table_cell']),
         Paragraph('-700 HU', ST['table_cell']),
         Paragraph('Parenchyma, airways, emphysema, ILD', ST['table_cell'])],
        [Paragraph('Mediastinal / Soft Tissue', ST['table_cell']),
         Paragraph('350-400 HU', ST['table_cell']),
         Paragraph('40 HU', ST['table_cell']),
         Paragraph('Lymph nodes, masses, vessels, pleura', ST['table_cell'])],
        [Paragraph('Bone', ST['table_cell']),
         Paragraph('2000 HU', ST['table_cell']),
         Paragraph('400 HU', ST['table_cell']),
         Paragraph('Ribs, vertebrae, sternum, cortex', ST['table_cell'])],
        [Paragraph('High-Res Lung (HRCT)', ST['table_cell']),
         Paragraph('1500-2000 HU', ST['table_cell']),
         Paragraph('-650 HU', ST['table_cell']),
         Paragraph('Fine interstitial detail, GGO, fibrosis', ST['table_cell'])],
    ]
    win_ts = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), TEAL),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [white, LGREEN]),
        ('GRID', (0, 0), (-1, -1), 0.5, HexColor('#BDC3C7')),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, -1), 8),
        ('LEFTPADDING', (0, 0), (-1, -1), 6),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
    ])
    win_t = Table(win_data, colWidths=[4.5*cm, 2.8*cm, 2.5*cm, 6.2*cm])
    win_t.setStyle(win_ts)
    story.append(win_t)

    story.append(Spacer(1, 6))
    story.append(Paragraph('Window Grayscale Visual', ST['sub_h']))
    story.append(draw_window_comparison(460, 90))
    story.append(Paragraph('Each window reveals different structures. Always view chest CT in at least lung AND mediastinal windows.',
                            ST['caption']))
    story.append(Spacer(1, 6))

    # HRCT vs Standard CT
    story.append(Paragraph('HRCT vs. Standard (Non-contrast) CT Chest', ST['sub_h']))
    diff_data = [
        [Paragraph('Feature', ST['table_h']),
         Paragraph('Standard CT', ST['table_h']),
         Paragraph('HRCT', ST['table_h'])],
        [Paragraph('Slice thickness', ST['table_cell']),
         Paragraph('3-5 mm', ST['table_cell']),
         Paragraph('\u22641.5 mm (ideally 0.625-1 mm)', ST['table_cell'])],
        [Paragraph('Algorithm', ST['table_cell']),
         Paragraph('Standard / smooth', ST['table_cell']),
         Paragraph('High spatial-frequency (sharp)', ST['table_cell'])],
        [Paragraph('Contrast used?', ST['table_cell']),
         Paragraph('Optional', ST['table_cell']),
         Paragraph('NO (not needed for ILD)', ST['table_cell'])],
        [Paragraph('Best for', ST['table_cell']),
         Paragraph('Mediastinum, staging, PE', ST['table_cell']),
         Paragraph('ILD, emphysema, bronchiectasis, fine parenchyma', ST['table_cell'])],
        [Paragraph('Extra sequences', ST['table_cell']),
         Paragraph('None routine', ST['table_cell']),
         Paragraph('Prone (atelectasis vs ILD), Expiratory (air trapping)', ST['table_cell'])],
    ]
    diff_ts = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), NAVY),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [white, LBLUE]),
        ('GRID', (0, 0), (-1, -1), 0.5, HexColor('#BDC3C7')),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, -1), 8),
        ('LEFTPADDING', (0, 0), (-1, -1), 6),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
    ])
    diff_t = Table(diff_data, colWidths=[4.5*cm, 4.5*cm, 7*cm])
    diff_t.setStyle(diff_ts)
    story.append(diff_t)

    story.append(Spacer(1, 8))
    story.append(TipBox(
        'Tip: Prone images distinguish true lower-lobe ILD from dependent atelectasis. '
        'Expiratory scans unmask air trapping (lobular hyperlucency) in small airway disease.',
        bg=LGREEN, border=GREEN, label='CLINICAL PEARL'))
    story.append(PageBreak())

    # ── PAGE 3 – Secondary Lobule ─────────────────────────────────────────────
    story.append(ColorBand('  SECTION 2 – SECONDARY PULMONARY LOBULE ANATOMY', bg=NAVY, height=30))
    story.append(Spacer(1, 6))
    story.append(Paragraph(
        'The secondary pulmonary lobule (SPL) is the basic anatomical unit of HRCT interpretation. '
        'It is a polyhedral unit (~1-2.5 cm diameter) bounded by interlobular septa containing pulmonary veins and lymphatics. '
        'The centrilobular region in the center contains the pulmonary arteriole and bronchiole. '
        'Disease localization within the SPL forms the basis of HRCT differential diagnosis.',
        ST['body']))
    story.append(Spacer(1, 6))
    story.append(draw_secondary_lobule(480, 220))
    story.append(Spacer(1, 6))

    # Distribution within SPL
    story.append(Paragraph('Disease Distribution Within the Secondary Lobule', ST['sub_h']))
    spl_data = [
        [Paragraph('Distribution', ST['table_h']),
         Paragraph('Anatomic Site', ST['table_h']),
         Paragraph('HRCT Appearance', ST['table_h']),
         Paragraph('Classic Diseases', ST['table_h'])],
        [Paragraph('Centrilobular', ST['table_cell']),
         Paragraph('Center of lobule\n(artery + bronchiole)', ST['table_cell']),
         Paragraph('Nodules/GGO sparing\nthe pleural surface\n(2-3 mm from pleura)', ST['table_cell']),
         Paragraph('Hypersensitivity pneumonitis,\nRespiratory bronchiolitis,\nEndobronchial TB/infection,\nSubacute HP', ST['table_cell'])],
        [Paragraph('Perilymphatic', ST['table_cell']),
         Paragraph('Along septa, fissures,\nbronchovascular bundles', ST['table_cell']),
         Paragraph('Nodules at pleura,\nfissures and along\nbronchi', ST['table_cell']),
         Paragraph('Sarcoidosis (classic),\nLymphangitic carcinomatosis,\nSilicosis, Coal workers PD', ST['table_cell'])],
        [Paragraph('Random', ST['table_cell']),
         Paragraph('No lobular predilection\n(hematogenous spread)', ST['table_cell']),
         Paragraph('Uniform distribution,\nno zonal preference,\npleura involved', ST['table_cell']),
         Paragraph('Miliary TB, Fungal\n(histoplasma), Haematogenous\nmetastases, Varicella PNA', ST['table_cell'])],
    ]
    spl_ts = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), PURPLE),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [white, LPURPLE]),
        ('GRID', (0, 0), (-1, -1), 0.5, HexColor('#BDC3C7')),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, -1), 8),
        ('LEFTPADDING', (0, 0), (-1, -1), 6),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
    ])
    spl_t = Table(spl_data, colWidths=[3*cm, 3.5*cm, 4*cm, 5.5*cm])
    spl_t.setStyle(spl_ts)
    story.append(spl_t)
    story.append(PageBreak())

    # ── PAGE 4 – HRCT Patterns Gallery ───────────────────────────────────────
    story.append(ColorBand('  SECTION 3 – HRCT PATTERNS GALLERY', bg=NAVY, height=30))
    story.append(Spacer(1, 6))
    story.append(Paragraph(
        'Four dominant HRCT patterns are recognized. Each circle below simulates an axial CT view of the lung '
        '(black = normal air density). Identify the dominant pattern first, then refine by distribution and additional features.',
        ST['body']))
    story.append(Spacer(1, 8))

    # Row 1: Normal, GGO, Consolidation, Reticulation
    pw = 120
    ph = 100

    patterns_row1 = [
        (pattern_normal(pw, ph),     'NORMAL',          'Fine branching vessels on dark background', HexColor('#D6EAF8')),
        (pattern_ggo(pw, ph),        'GROUND GLASS (GGO)', 'Hazy opacity; vessels still visible through it', HexColor('#EAF7EA')),
        (pattern_consolidation(pw, ph), 'CONSOLIDATION', 'Dense opacity obscuring vessels; air bronchogram', HexColor('#FDEDEC')),
        (pattern_reticulation(pw, ph),  'RETICULATION',  'Intersecting lines; subpleural accentuation', HexColor('#FEF9E7')),
    ]

    patterns_row2 = [
        (pattern_honeycombing(pw, ph), 'HONEYCOMBING',   'Clustered subpleural cysts 3-10 mm; fibrosis', LRED),
        (pattern_emphysema(pw, ph),    'EMPHYSEMA',      'Lucent areas WITHOUT walls; centrilobular artery dot', LGREY),
        (pattern_bronchiectasis(pw, ph), 'BRONCHIECTASIS', 'Signet ring sign: dilated bronchus > adjacent artery', HexColor('#EAF7EA')),
        (pattern_crazy_paving(pw, ph), 'CRAZY PAVING',   'GGO + superimposed septal lines; classic: alveolar proteinosis', LPURPLE),
    ]

    for row_patterns in [patterns_row1, patterns_row2]:
        row_cells = []
        for drw, title, desc, bg in row_patterns:
            cell_content = [
                Spacer(1, 3),
                drw,
                Spacer(1, 3),
                Paragraph(f'<b>{title}</b>', ParagraphStyle('pc', fontName='Helvetica-Bold',
                    fontSize=8, textColor=NAVY, alignment=TA_CENTER)),
                Paragraph(desc, ParagraphStyle('pd', fontName='Helvetica', fontSize=7,
                    textColor=DARKGREY, alignment=TA_CENTER, leading=10)),
                Spacer(1, 4),
            ]
            row_cells.append(cell_content)
        pat_t = Table([row_cells],
                      colWidths=[3.8*cm, 3.8*cm, 3.8*cm, 3.8*cm])
        pat_t.setStyle(TableStyle([
            ('BACKGROUND', (0, 0), (0, 0), patterns_row1[0][3] if row_patterns == patterns_row1 else patterns_row2[0][3]),
            ('BACKGROUND', (1, 0), (1, 0), patterns_row1[1][3] if row_patterns == patterns_row1 else patterns_row2[1][3]),
            ('BACKGROUND', (2, 0), (2, 0), patterns_row1[2][3] if row_patterns == patterns_row1 else patterns_row2[2][3]),
            ('BACKGROUND', (3, 0), (3, 0), patterns_row1[3][3] if row_patterns == patterns_row1 else patterns_row2[3][3]),
            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
            ('VALIGN', (0, 0), (-1, -1), 'TOP'),
            ('BOX', (0, 0), (-1, -1), 0.5, HexColor('#AEB6BF')),
            ('INNERGRID', (0, 0), (-1, -1), 0.5, HexColor('#AEB6BF')),
            ('TOPPADDING', (0, 0), (-1, -1), 4),
            ('BOTTOMPADDING', (0, 0), (-1, -1), 4),
        ]))
        story.append(pat_t)
        story.append(Spacer(1, 6))

    # Additional pattern: Mosaic & Mass
    story.append(Paragraph('Additional Important Patterns', ST['sub_h']))
    extra_row = [
        (pattern_mosaic(pw, ph), 'MOSAIC ATTENUATION', 'Lobular patchwork; darker lobules = air trapping', LBLUE),
        (pattern_mass(pw, ph), 'SPICULATED MASS', 'Radiating spicules = malignancy until proven otherwise', LRED),
    ]
    extra_cells = []
    for drw, title, desc, bg in extra_row:
        extra_cells.append([
            Spacer(1, 3), drw, Spacer(1, 3),
            Paragraph(f'<b>{title}</b>', ParagraphStyle('pc2', fontName='Helvetica-Bold',
                fontSize=8, textColor=NAVY, alignment=TA_CENTER)),
            Paragraph(desc, ParagraphStyle('pd2', fontName='Helvetica', fontSize=7,
                textColor=DARKGREY, alignment=TA_CENTER, leading=10)),
            Spacer(1, 4),
        ])
    extra_t = Table([extra_cells + [[''] * 6, [''] * 6]],
                    colWidths=[3.8*cm, 3.8*cm, 3.8*cm, 3.8*cm])
    extra_t2 = Table([extra_cells],
                     colWidths=[7.6*cm, 7.6*cm])
    extra_t2.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (0, 0), LBLUE),
        ('BACKGROUND', (1, 0), (1, 0), LRED),
        ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('BOX', (0, 0), (-1, -1), 0.5, HexColor('#AEB6BF')),
        ('INNERGRID', (0, 0), (-1, -1), 0.5, HexColor('#AEB6BF')),
        ('TOPPADDING', (0, 0), (-1, -1), 4),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 4),
    ]))
    story.append(extra_t2)
    story.append(PageBreak())

    # ── PAGE 5 – Distribution ────────────────────────────────────────────────
    story.append(ColorBand('  SECTION 4 – DISTRIBUTION & ZONAL PATTERNS', bg=NAVY, height=30))
    story.append(Spacer(1, 6))
    story.append(Paragraph(
        'After identifying the dominant pattern, determine WHERE it is located. '
        'Zonal and central-peripheral distribution narrows the differential significantly.',
        ST['body']))
    story.append(Spacer(1, 8))
    story.append(draw_distribution_map(480, 155))
    story.append(Spacer(1, 8))

    dist_data = [
        [Paragraph('Distribution', ST['table_h']),
         Paragraph('Diseases to Consider', ST['table_h']),
         Paragraph('Key Differentiating Feature', ST['table_h'])],
        [Paragraph('Upper zone\n(upper lobes)', ST['table_cell']),
         Paragraph('Sarcoidosis, Silicosis, Coal workers PD, LCH,\nHypersensitivity pneumonitis (some), Old TB', ST['table_cell']),
         Paragraph('Sarcoid: perilymphatic nodules + hilar nodes\nLCH: bizarre cysts + history of smoking', ST['table_cell'])],
        [Paragraph('Lower zone\n(basal)', ST['table_cell']),
         Paragraph('UIP/IPF, NSIP, Aspiration pneumonia,\nPulmonary edema, BOOP/COP', ST['table_cell']),
         Paragraph('UIP: subpleural honeycombing\nNSIP: diffuse GGO, less honeycombing\nEdema: septal lines, effusion', ST['table_cell'])],
        [Paragraph('Perihilar / Central', ST['table_cell']),
         Paragraph('Sarcoidosis, Lymphangitic carcinomatosis,\nBronchitis, Cardiogenic pulmonary edema', ST['table_cell']),
         Paragraph('Sarcoid: peribronchial thickening\nLymphangitic: irregular septal lines + nodes', ST['table_cell'])],
        [Paragraph('Peripheral / Subpleural', ST['table_cell']),
         Paragraph('UIP/IPF (classic), COP,\nChronic eosinophilic pneumonia, NSIP', ST['table_cell']),
         Paragraph('COP: peripheral consolidation, "reverse halo"\nIPF: honeycombing + traction bronchiectasis', ST['table_cell'])],
        [Paragraph('Diffuse / Random', ST['table_cell']),
         Paragraph('Pulmonary edema (bilateral), Miliary TB,\nHematogenous metastases, NSIP', ST['table_cell']),
         Paragraph('Miliary: uniform tiny nodules in all zones\nEdema: basal GGO + bilateral effusions', ST['table_cell'])],
    ]
    dist_ts = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), TEAL),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [white, LBLUE]),
        ('GRID', (0, 0), (-1, -1), 0.5, HexColor('#BDC3C7')),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, -1), 8),
        ('LEFTPADDING', (0, 0), (-1, -1), 6),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
    ])
    dist_t = Table(dist_data, colWidths=[3*cm, 6*cm, 7*cm])
    dist_t.setStyle(dist_ts)
    story.append(dist_t)
    story.append(PageBreak())

    # ── PAGE 6 – Nodule Types ────────────────────────────────────────────────
    story.append(ColorBand('  SECTION 5 – NODULE DISTRIBUTION: CENTRILOBULAR vs PERILYMPHATIC vs RANDOM',
                           bg=NAVY, height=30))
    story.append(Spacer(1, 6))
    story.append(Paragraph(
        'Nodule distribution within the secondary pulmonary lobule is one of the most powerful differentiators in HRCT. '
        'The three patterns have fundamentally different pathological mechanisms and disease associations.',
        ST['body']))
    story.append(Spacer(1, 8))

    # Three nodule pattern circles side by side
    nod_cells = [
        [Spacer(1, 3),
         pattern_nodules(120, 100, 'centrilobular'),
         Spacer(1, 3),
         Paragraph('<b>CENTRILOBULAR</b>', ParagraphStyle('nc', fontName='Helvetica-Bold',
             fontSize=9, textColor=TEAL, alignment=TA_CENTER)),
         Paragraph('Spares the pleura (2-3 mm gap)\nCentrilobular artery at center', ParagraphStyle('nd',
             fontName='Helvetica', fontSize=7.5, textColor=DARKGREY, alignment=TA_CENTER, leading=11)),
         Spacer(1, 4)],
        [Spacer(1, 3),
         pattern_nodules(120, 100, 'perilymphatic'),
         Spacer(1, 3),
         Paragraph('<b>PERILYMPHATIC</b>', ParagraphStyle('nc2', fontName='Helvetica-Bold',
             fontSize=9, textColor=PURPLE, alignment=TA_CENTER)),
         Paragraph('Along pleura, fissures &\nbronchovascular bundles', ParagraphStyle('nd2',
             fontName='Helvetica', fontSize=7.5, textColor=DARKGREY, alignment=TA_CENTER, leading=11)),
         Spacer(1, 4)],
        [Spacer(1, 3),
         pattern_nodules(120, 100, 'random'),
         Spacer(1, 3),
         Paragraph('<b>RANDOM</b>', ParagraphStyle('nc3', fontName='Helvetica-Bold',
             fontSize=9, textColor=RED, alignment=TA_CENTER)),
         Paragraph('No lobular preference\nPleural surface also involved', ParagraphStyle('nd3',
             fontName='Helvetica', fontSize=7.5, textColor=DARKGREY, alignment=TA_CENTER, leading=11)),
         Spacer(1, 4)],
    ]
    nod_t = Table([nod_cells], colWidths=[5.3*cm, 5.3*cm, 5.3*cm])
    nod_t.setStyle(TableStyle([
        ('BACKGROUND', (0, 0), (0, 0), LBLUE),
        ('BACKGROUND', (1, 0), (1, 0), LPURPLE),
        ('BACKGROUND', (2, 0), (2, 0), LRED),
        ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('BOX', (0, 0), (-1, -1), 1, TEAL),
        ('INNERGRID', (0, 0), (-1, -1), 0.5, HexColor('#BDC3C7')),
        ('TOPPADDING', (0, 0), (-1, -1), 6),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 6),
    ]))
    story.append(nod_t)
    story.append(Spacer(1, 8))

    nod2_data = [
        [Paragraph('Feature', ST['table_h']),
         Paragraph('Centrilobular', ST['table_h']),
         Paragraph('Perilymphatic', ST['table_h']),
         Paragraph('Random', ST['table_h'])],
        [Paragraph('Pleura spared?', ST['table_cell']),
         Paragraph('YES (classic)', ST['table_cell']),
         Paragraph('NO (nodules at pleura)', ST['table_cell']),
         Paragraph('NO (random)', ST['table_cell'])],
        [Paragraph('Fissure nodules?', ST['table_cell']),
         Paragraph('No', ST['table_cell']),
         Paragraph('YES (classic)', ST['table_cell']),
         Paragraph('Sometimes', ST['table_cell'])],
        [Paragraph('Zonal predominance', ST['table_cell']),
         Paragraph('Upper/mid (HP)\nUpper (smoking-RB)', ST['table_cell']),
         Paragraph('Upper (sarcoid)\nBases (lymphangitic)', ST['table_cell']),
         Paragraph('None (uniform)', ST['table_cell'])],
        [Paragraph('Key diseases', ST['table_cell']),
         Paragraph('Hypersensitivity pneumonitis\nRespiratory bronchiolitis\nEndobronchial TB spread\nSubacute HP', ST['table_cell']),
         Paragraph('Sarcoidosis (1-2-3 sign)\nLymphangitic carcinomatosis\nSilicosis / Coal workers PD', ST['table_cell']),
         Paragraph('Miliary TB / Fungal\nHematogenous metastases\nVaricella pneumonia', ST['table_cell'])],
        [Paragraph('Associated features', ST['table_cell']),
         Paragraph('"Tree-in-bud" = endobronchial\ninfection', ST['table_cell']),
         Paragraph('Bilateral hilar nodes (sarcoid)\nKnown malignancy (lymphangitic)', ST['table_cell']),
         Paragraph('Very uniform, tiny (<3 mm)\nNo lymphadenopathy', ST['table_cell'])],
    ]
    nod2_ts = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), PURPLE),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [white, LPURPLE]),
        ('GRID', (0, 0), (-1, -1), 0.5, HexColor('#BDC3C7')),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, -1), 8),
        ('LEFTPADDING', (0, 0), (-1, -1), 6),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
    ])
    nod2_t = Table(nod2_data, colWidths=[3.5*cm, 4.5*cm, 4.5*cm, 4.5*cm])
    nod2_t.setStyle(nod2_ts)
    story.append(nod2_t)
    story.append(PageBreak())

    # ── PAGE 7 – UIP / Fleischner ────────────────────────────────────────────
    story.append(ColorBand('  SECTION 6 – FLEISCHNER SOCIETY UIP/IPF CLASSIFICATION', bg=NAVY, height=30))
    story.append(Spacer(1, 6))
    story.append(Paragraph(
        'The Fleischner Society 2018 classification divides HRCT findings for suspected UIP/IPF into four categories '
        'with direct implications for biopsy decisions and management.',
        ST['body']))
    story.append(Spacer(1, 8))
    story.append(draw_uip_comparison(480, 135))
    story.append(Spacer(1, 8))

    uip_data = [
        [Paragraph('Pattern', ST['table_h']),
         Paragraph('Distribution', ST['table_h']),
         Paragraph('CT Features', ST['table_h']),
         Paragraph('Biopsy?', ST['table_h']),
         Paragraph('PPV for UIP histology', ST['table_h'])],
        [Paragraph('Typical UIP', ParagraphStyle('u1', fontName='Helvetica-Bold',
             fontSize=8, textColor=GREEN)),
         Paragraph('Subpleural + basal\npredominant', ST['table_cell']),
         Paragraph('Reticulation + traction bronchiectasis\n+ HONEYCOMBING\n(no alternative diagnosis features)', ST['table_cell']),
         Paragraph('NO', ParagraphStyle('nb', fontName='Helvetica-Bold',
             fontSize=9, textColor=GREEN)),
         Paragraph('>90%', ST['table_cell'])],
        [Paragraph('Probable UIP', ParagraphStyle('u2', fontName='Helvetica-Bold',
             fontSize=8, textColor=TEAL)),
         Paragraph('Subpleural + basal\npredominant', ST['table_cell']),
         Paragraph('Reticulation + traction bronchiectasis\nNO honeycombing', ST['table_cell']),
         Paragraph('Usually NO', ParagraphStyle('unb', fontName='Helvetica-Bold',
             fontSize=9, textColor=TEAL)),
         Paragraph('>70%', ST['table_cell'])],
        [Paragraph('Indeterminate', ParagraphStyle('u3', fontName='Helvetica-Bold',
             fontSize=8, textColor=GOLD)),
         Paragraph('Variable/diffuse\nor inconclusive', ST['table_cell']),
         Paragraph('Fibrotic features but inconclusive;\nsome non-UIP features present', ST['table_cell']),
         Paragraph('YES', ParagraphStyle('yb', fontName='Helvetica-Bold',
             fontSize=9, textColor=GOLD)),
         Paragraph('~50%', ST['table_cell'])],
        [Paragraph('Non-IPF / Alt Dx', ParagraphStyle('u4', fontName='Helvetica-Bold',
             fontSize=8, textColor=RED)),
         Paragraph('Upper/mid lung OR\ncentral/peribronchovasc.', ST['table_cell']),
         Paragraph('Consolidation, diffuse pure GGO,\nextensive mosaic, diffuse cysts/nodules', ST['table_cell']),
         Paragraph('YES\n(different Dx)', ParagraphStyle('yb2', fontName='Helvetica-Bold',
             fontSize=9, textColor=RED)),
         Paragraph('Low', ST['table_cell'])],
    ]
    uip_ts = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), NAVY),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [LGREEN, HexColor('#EBF5FB'), LGOLD, LRED]),
        ('GRID', (0, 0), (-1, -1), 0.5, HexColor('#BDC3C7')),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, -1), 8),
        ('LEFTPADDING', (0, 0), (-1, -1), 6),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
    ])
    uip_t = Table(uip_data, colWidths=[2.8*cm, 3*cm, 5.5*cm, 2.2*cm, 2.5*cm])
    uip_t.setStyle(uip_ts)
    story.append(uip_t)
    story.append(Spacer(1, 8))

    # Additional ILD patterns
    story.append(Paragraph('Common ILD Patterns on HRCT', ST['sub_h']))
    ild_data = [
        [Paragraph('ILD Type', ST['table_h']),
         Paragraph('Predominant Zone', ST['table_h']),
         Paragraph('Key HRCT Features', ST['table_h']),
         Paragraph('Distinguishing Feature', ST['table_h'])],
        [Paragraph('UIP (IPF)', ST['table_cell']),
         Paragraph('Lower + subpleural', ST['table_cell']),
         Paragraph('Reticulation, honeycombing, traction bronchiectasis', ST['table_cell']),
         Paragraph('Honeycombing is hallmark', ST['table_cell'])],
        [Paragraph('NSIP', ST['table_cell']),
         Paragraph('Lower + diffuse', ST['table_cell']),
         Paragraph('Diffuse GGO, reticulation, NO honeycombing (or rare)', ST['table_cell']),
         Paragraph('Subpleural sparing in 50%', ST['table_cell'])],
        [Paragraph('COP (Organizing PNA)', ST['table_cell']),
         Paragraph('Peripheral, lower', ST['table_cell']),
         Paragraph('Peripheral consolidation, "reverse halo sign"', ST['table_cell']),
         Paragraph('Migrates over time', ST['table_cell'])],
        [Paragraph('HP (Hypersensitivity P.)', ST['table_cell']),
         Paragraph('Upper/mid, centrilobular', ST['table_cell']),
         Paragraph('Centrilobular nodules, GGO, air trapping', ST['table_cell']),
         Paragraph('Spares bases; exposure history', ST['table_cell'])],
        [Paragraph('DIP', ST['table_cell']),
         Paragraph('Lower, diffuse', ST['table_cell']),
         Paragraph('Diffuse GGO (macrophage filling)', ST['table_cell']),
         Paragraph('Heavy smoker, under 40', ST['table_cell'])],
        [Paragraph('Sarcoidosis', ST['table_cell']),
         Paragraph('Upper + perilymphatic', ST['table_cell']),
         Paragraph('Perilymphatic nodules, bilateral hilar adenopathy', ST['table_cell']),
         Paragraph('"1-2-3 sign"; perihilar', ST['table_cell'])],
    ]
    ild_ts = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), TEAL),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [white, LBLUE]),
        ('GRID', (0, 0), (-1, -1), 0.5, HexColor('#BDC3C7')),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, -1), 7.5),
        ('LEFTPADDING', (0, 0), (-1, -1), 5),
        ('TOPPADDING', (0, 0), (-1, -1), 4),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 4),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
    ])
    ild_t = Table(ild_data, colWidths=[3*cm, 3*cm, 5.5*cm, 4.5*cm])
    ild_t.setStyle(ild_ts)
    story.append(ild_t)
    story.append(PageBreak())

    # ── PAGE 8 – CECT Chest ──────────────────────────────────────────────────
    story.append(ColorBand('  SECTION 7 – CECT CHEST: PHASES & APPLICATIONS', bg=NAVY, height=30))
    story.append(Spacer(1, 6))
    story.append(Paragraph(
        'Contrast-enhanced CT (CECT) uses IV iodinated contrast to evaluate vessels, mediastinum, lymph nodes, '
        'and enhancement patterns of masses. Contrast is NOT used for standard ILD/HRCT evaluation.',
        ST['body']))
    story.append(Spacer(1, 8))

    story.append(Paragraph('CT Contrast Phases', ST['sub_h']))
    story.append(draw_cect_phases(480, 85))
    story.append(Spacer(1, 8))

    phase_data = [
        [Paragraph('Phase', ST['table_h']),
         Paragraph('Timing', ST['table_h']),
         Paragraph('Indication / Use', ST['table_h']),
         Paragraph('Key Findings', ST['table_h'])],
        [Paragraph('Non-contrast', ST['table_cell']),
         Paragraph('Before injection', ST['table_cell']),
         Paragraph('Calcification, hemorrhage, baseline HU', ST['table_cell']),
         Paragraph('Dense = calcium/hemorrhage\nFat = lipoid lesion', ST['table_cell'])],
        [Paragraph('Arterial / CTPA', ST['table_cell']),
         Paragraph('15-20 seconds', ST['table_cell']),
         Paragraph('Pulmonary embolism (CTPA), aortic dissection, AVM', ST['table_cell']),
         Paragraph('Filling defect in pulmonary artery = PE\nIntimal flap = dissection', ST['table_cell'])],
        [Paragraph('Venous / Portal', ST['table_cell']),
         Paragraph('60-70 seconds', ST['table_cell']),
         Paragraph('Mediastinal masses, lymph nodes, lung cancer staging', ST['table_cell']),
         Paragraph('Enhancing nodes, mass characterization\nPleural enhancement', ST['table_cell'])],
        [Paragraph('Delayed', ST['table_cell']),
         Paragraph('3-5 minutes', ST['table_cell']),
         Paragraph('Fibrous tumors, infection, vascular anomalies', ST['table_cell']),
         Paragraph('Progressive enhancement = carcinoid\nWash-out = HCC', ST['table_cell'])],
    ]
    phase_ts = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), DARKGREY),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [white, LBLUE]),
        ('GRID', (0, 0), (-1, -1), 0.5, HexColor('#BDC3C7')),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, -1), 8),
        ('LEFTPADDING', (0, 0), (-1, -1), 6),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
    ])
    phase_t = Table(phase_data, colWidths=[2.8*cm, 2.5*cm, 5.5*cm, 5.2*cm])
    phase_t.setStyle(phase_ts)
    story.append(phase_t)
    story.append(Spacer(1, 8))

    # Mediastinal compartments
    story.append(Paragraph('Mediastinal Compartments & Masses (ITMIG Classification)', ST['sub_h']))
    med_data = [
        [Paragraph('Compartment', ST['table_h']),
         Paragraph('Contents', ST['table_h']),
         Paragraph('Masses (4 T\'s)', ST['table_h']),
         Paragraph('CT Features', ST['table_h'])],
        [Paragraph('Prevascular\n(Anterior)', ParagraphStyle('ac', fontName='Helvetica-Bold',
             fontSize=8, textColor=RED)),
         Paragraph('Thymus, lymph nodes\nthyroid extension', ST['table_cell']),
         Paragraph('Thymoma, Teratoma,\nTerrible (lymphoma),\nThyroid mass', ST['table_cell']),
         Paragraph('Thymoma: heterogeneous, may invade pleura\nLymphoma: infiltrates vessels', ST['table_cell'])],
        [Paragraph('Visceral\n(Middle)', ParagraphStyle('vc', fontName='Helvetica-Bold',
             fontSize=8, textColor=TEAL)),
         Paragraph('Trachea, heart, great vessels,\nesophagus, lymph nodes', ST['table_cell']),
         Paragraph('Lymphoma, mediastinal\ncysts, vascular tumors', ST['table_cell']),
         Paragraph('Pericardial cyst: homogeneous, no enhancement\nLymph node >1 cm SAX = abnormal', ST['table_cell'])],
        [Paragraph('Paravertebral\n(Posterior)', ParagraphStyle('pc3', fontName='Helvetica-Bold',
             fontSize=8, textColor=PURPLE)),
         Paragraph('Sympathetic chain,\nneural structures\nalong spine', ST['table_cell']),
         Paragraph('Neurogenic tumors\n(Schwannoma, NF,\nNeuroblastoma)', ST['table_cell']),
         Paragraph('Dumbbell sign: foraminal extension\nSmooth margins, well-defined', ST['table_cell'])],
    ]
    med_ts = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), NAVY),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [LRED, HexColor('#EBF5FB'), LPURPLE]),
        ('GRID', (0, 0), (-1, -1), 0.5, HexColor('#BDC3C7')),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, -1), 8),
        ('LEFTPADDING', (0, 0), (-1, -1), 6),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
    ])
    med_t = Table(med_data, colWidths=[2.8*cm, 3.5*cm, 4*cm, 5.7*cm])
    med_t.setStyle(med_ts)
    story.append(med_t)
    story.append(PageBreak())

    # ── PAGE 9 – Systematic Checklist ────────────────────────────────────────
    story.append(ColorBand('  SECTION 8 – SYSTEMATIC READING CHECKLIST', bg=NAVY, height=30))
    story.append(Spacer(1, 6))
    story.append(Paragraph(
        'Use a fixed order every time. A systematic approach prevents errors of omission. '
        'Read each CT in BOTH lung AND mediastinal windows before reporting.',
        ST['body']))
    story.append(Spacer(1, 8))

    checklist = [
        ('1', 'TRACHEA & MAIN BRONCHI', TEAL,
         ['Position: central? Deviated?',
          'Calibre: normal / narrowed / widened?',
          'Wall thickening, intraluminal lesion?',
          'Tracheal shape: saber-sheath (COPD)',
          'Carina angle (normal <70 degrees)']),
        ('2', 'LUNG PARENCHYMA', GREEN,
         ['Use LUNG WINDOW (W:1500, L:-700)',
          'Identify dominant pattern: GGO / Consolidation / Reticulation / Nodules / Low attenuation',
          'Determine distribution: upper vs lower, central vs peripheral',
          'Note: honeycombing, traction bronchiectasis, air trapping (expiratory scan)',
          'Nodule: size, density, margins, distribution (centrilobular/perilymphatic/random)']),
        ('3', 'LYMPH NODES', PURPLE,
         ['Hilar nodes: normal <1 cm short axis',
          'Bilateral hilar -> sarcoidosis, lymphoma',
          'Unilateral hilar -> carcinoma, metastasis',
          'Calcified nodes -> old granulomatous disease (TB, histoplasma)',
          'Mediastinal nodes (short axis >1 cm = abnormal)']),
        ('4', 'PLEURA', ORANGE,
         ['Effusion: size, laterality, HU (transudate < 20, exudate > 35)',
          'Pleural thickening: smooth vs nodular (nodular = malignant)',
          'Pleural plaques (asbestos exposure)',
          'Pneumothorax: apical and lateral recesses',
          'Empyema: loculated, thickened enhancing pleura (split pleura sign)']),
        ('5', 'MEDIASTINUM & HEART', NAVY,
         ['Use MEDIASTINAL WINDOW (W:400, L:40)',
          'Compartment of any mass: anterior / visceral / posterior',
          'Pericardial effusion / thickening',
          'Heart size, chamber enlargement',
          'Aorta: diameter (aneurysm >4 cm), intimal flap (dissection)']),
        ('6', 'VESSELS (CECT only)', RED,
         ['CTPA: look for filling defects in pulmonary arteries (PE)',
          'Saddle embolus at bifurcation of main pulmonary artery',
          'RV:LV ratio >0.9 = right heart strain from PE',
          'Aortic arch and great vessels: anomalies, dilatation',
          'Pulmonary artery pressure signs: PA >3 cm = pulmonary hypertension']),
        ('7', 'BONES', MIDGREY,
         ['Use BONE WINDOW (W:2000, L:400)',
          'Rib fractures: count each rib (acute vs healing)',
          'Lytic lesion: myeloma, metastasis, infection',
          'Sclerotic lesion: prostate metastasis, lymphoma, Paget\'s',
          'Vertebral height: compression fracture, malignancy']),
        ('8', 'SUBDIAPHRAGMATIC', GOLD,
         ['Adrenal glands: enlargement / nodule (lung cancer staging)',
          'Upper liver: mass, lesion',
          'Stomach / upper kidney if included in scan',
          'Free fluid / ascites']),
    ]

    for num, title, col, items in checklist:
        # Section header
        hdr = Table([[
            Paragraph(num, ParagraphStyle('cn', fontName='Helvetica-Bold', fontSize=11,
                textColor=white, alignment=TA_CENTER)),
            Paragraph(title, ParagraphStyle('ct', fontName='Helvetica-Bold', fontSize=9,
                textColor=white, alignment=TA_LEFT)),
        ]], colWidths=[0.7*cm, 15.3*cm])
        hdr.setStyle(TableStyle([
            ('BACKGROUND', (0, 0), (-1, -1), col),
            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
            ('LEFTPADDING', (0, 0), (0, 0), 4),
            ('LEFTPADDING', (1, 0), (1, 0), 8),
            ('TOPPADDING', (0, 0), (-1, -1), 4),
            ('BOTTOMPADDING', (0, 0), (-1, -1), 4),
        ]))
        story.append(hdr)
        # Items in 2 columns
        mid = (len(items) + 1) // 2
        left = items[:mid]
        right = items[mid:]
        while len(right) < len(left):
            right.append('')
        item_rows = []
        for l, r in zip(left, right):
            item_rows.append([
                Paragraph(u'\u2610 ' + l, ST['bullet']) if l else Paragraph('', ST['small']),
                Paragraph(u'\u2610 ' + r, ST['bullet']) if r else Paragraph('', ST['small']),
            ])
        item_t = Table(item_rows, colWidths=[8*cm, 8*cm])
        item_t.setStyle(TableStyle([
            ('BACKGROUND', (0, 0), (-1, -1), LGREY),
            ('GRID', (0, 0), (-1, -1), 0.3, HexColor('#D5D8DC')),
            ('LEFTPADDING', (0, 0), (-1, -1), 6),
            ('TOPPADDING', (0, 0), (-1, -1), 2),
            ('BOTTOMPADDING', (0, 0), (-1, -1), 2),
        ]))
        story.append(item_t)
        story.append(Spacer(1, 4))

    story.append(TipBox(
        'Mnemonic: T-A-L-E-P-V-B  (Trachea, Airspaces, Lymph nodes, Effusion, Pleura, Vessels, Bones)',
        bg=LGOLD, border=GOLD, label='MEMORY AID'))
    story.append(PageBreak())

    # ── PAGE 10 – Quick Lookup Table ─────────────────────────────────────────
    story.append(ColorBand('  SECTION 9 – QUICK PATTERN-DISEASE LOOKUP TABLE', bg=NAVY, height=30))
    story.append(Spacer(1, 6))
    story.append(Paragraph(
        'Use this table to correlate HRCT findings with the most likely diagnosis. '
        'Always correlate with clinical history, PFTs, and serological data.',
        ST['body']))
    story.append(Spacer(1, 8))

    lookup_data = [
        [Paragraph('HRCT Pattern', ST['table_h']),
         Paragraph('Distribution', ST['table_h']),
         Paragraph('Additional Feature', ST['table_h']),
         Paragraph('TOP DIAGNOSIS', ST['table_h']),
         Paragraph('Other Dx', ST['table_h'])],
        # GGO group
        [Paragraph('GGO (diffuse)', ST['table_cell']),
         Paragraph('Bilateral basal', ST['table_cell']),
         Paragraph('Crazy paving', ST['table_cell']),
         Paragraph('Pulmonary alveolar proteinosis', ParagraphStyle('td', fontName='Helvetica-Bold', fontSize=8, textColor=TEAL)),
         Paragraph('COVID-19 PNA, edema', ST['small'])],
        [Paragraph('GGO', ST['table_cell']),
         Paragraph('Bilateral perihilar', ST['table_cell']),
         Paragraph('Bilateral effusions', ST['table_cell']),
         Paragraph('Cardiogenic pulmonary edema', ParagraphStyle('td2', fontName='Helvetica-Bold', fontSize=8, textColor=TEAL)),
         Paragraph('ARDS', ST['small'])],
        [Paragraph('GGO + centrilobular\nnodules', ST['table_cell']),
         Paragraph('Upper/mid, bilateral', ST['table_cell']),
         Paragraph('Air trapping, spares\nbases', ST['table_cell']),
         Paragraph('Hypersensitivity Pneumonitis', ParagraphStyle('td3', fontName='Helvetica-Bold', fontSize=8, textColor=TEAL)),
         Paragraph('DIP, RB-ILD', ST['small'])],
        # Reticulation group
        [Paragraph('Reticulation +\nhoneycombing', ST['table_cell']),
         Paragraph('Bilateral basal\nsubpleural', ST['table_cell']),
         Paragraph('Traction bronchiectasis', ST['table_cell']),
         Paragraph('UIP/IPF (Typical UIP)', ParagraphStyle('td4', fontName='Helvetica-Bold', fontSize=8, textColor=GREEN)),
         Paragraph('Asbestosis, CTD-ILD', ST['small'])],
        [Paragraph('Reticulation, GGO\n(no honeycomb)', ST['table_cell']),
         Paragraph('Bilateral basal\n+ subpleural sparing', ST['table_cell']),
         Paragraph('Subpleural sparing\nin 50%', ST['table_cell']),
         Paragraph('NSIP', ParagraphStyle('td5', fontName='Helvetica-Bold', fontSize=8, textColor=GREEN)),
         Paragraph('CTD, drug toxicity', ST['small'])],
        # Consolidation group
        [Paragraph('Peripheral\nconsolidation', ST['table_cell']),
         Paragraph('Bilateral, lower\nmigrating', ST['table_cell']),
         Paragraph('Reverse halo sign\n(atoll sign)', ST['table_cell']),
         Paragraph('COP / BOOP', ParagraphStyle('td6', fontName='Helvetica-Bold', fontSize=8, textColor=ORANGE)),
         Paragraph('Eosinophilic PNA', ST['small'])],
        [Paragraph('Wedge consolidation\n(pleural based)', ST['table_cell']),
         Paragraph('Peripheral', ST['table_cell']),
         Paragraph("Hampton's hump;\nCTPA: filling defect", ST['table_cell']),
         Paragraph('Pulmonary infarction (PE)', ParagraphStyle('td7', fontName='Helvetica-Bold', fontSize=8, textColor=RED)),
         Paragraph('Pneumonia', ST['small'])],
        # Nodule group
        [Paragraph('Perilymphatic nodules', ST['table_cell']),
         Paragraph('Upper zone, bilateral', ST['table_cell']),
         Paragraph('Bilateral hilar nodes\n(1-2-3 sign)', ST['table_cell']),
         Paragraph('Sarcoidosis', ParagraphStyle('td8', fontName='Helvetica-Bold', fontSize=8, textColor=PURPLE)),
         Paragraph('Lymphangitic met.', ST['small'])],
        [Paragraph('Random micronodules\n(<3 mm, uniform)', ST['table_cell']),
         Paragraph('Diffuse, all zones', ST['table_cell']),
         Paragraph('No zonal preference,\nvery uniform', ST['table_cell']),
         Paragraph('Miliary TB / Miliary fungal', ParagraphStyle('td9', fontName='Helvetica-Bold', fontSize=8, textColor=PURPLE)),
         Paragraph('Hematogenous mets', ST['small'])],
        [Paragraph('Tree-in-bud +\ncentrilobular nodules', ST['table_cell']),
         Paragraph('Any, often focal', ST['table_cell']),
         Paragraph('Lobular consolidation', ST['table_cell']),
         Paragraph('Endobronchial infection\n(TB, NTM, bacterial)', ParagraphStyle('td10', fontName='Helvetica-Bold', fontSize=8, textColor=ORANGE)),
         Paragraph('Aspiration', ST['small'])],
        # Cystic/low attenuation
        [Paragraph('Emphysema (NO walls)\ncentrilobular', ST['table_cell']),
         Paragraph('Upper lobe\nperiphery spared', ST['table_cell']),
         Paragraph('Central dot (artery)\nSmoking history', ST['table_cell']),
         Paragraph('Centrilobular emphysema', ParagraphStyle('td11', fontName='Helvetica-Bold', fontSize=8, textColor=MIDGREY)),
         Paragraph('Paraseptal emphysema', ST['small'])],
        [Paragraph('Multiple thin-walled\ncysts, uniform', ST['table_cell']),
         Paragraph('Diffuse both lungs', ST['table_cell']),
         Paragraph('Young woman,\npneumothorax, effusion', ST['table_cell']),
         Paragraph('LAM', ParagraphStyle('td12', fontName='Helvetica-Bold', fontSize=8, textColor=MIDGREY)),
         Paragraph('LIP, BHD syndrome', ST['small'])],
        [Paragraph('Thick + thin cysts,\nbizarre shapes', ST['table_cell']),
         Paragraph('Upper zone\nlung spare bases', ST['table_cell']),
         Paragraph('Smoker, nodules early\nthen cavitate', ST['table_cell']),
         Paragraph('LCH (Langerhans Cell Histiocytosis)', ParagraphStyle('td13', fontName='Helvetica-Bold', fontSize=8, textColor=MIDGREY)),
         Paragraph('LAM, lymphoma', ST['small'])],
        [Paragraph('Signet ring sign\n(dilated bronchi)', ST['table_cell']),
         Paragraph('Any, often bilateral', ST['table_cell']),
         Paragraph('Mucus plugging,\ncentral > cylindrical', ST['table_cell']),
         Paragraph('Bronchiectasis\n(CF, ABPA, post-infective)', ParagraphStyle('td14', fontName='Helvetica-Bold', fontSize=8, textColor=NAVY)),
         Paragraph('Traction bronchiect.', ST['small'])],
    ]
    lookup_ts = TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), DARKGREY),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [white, LGREY]),
        ('GRID', (0, 0), (-1, -1), 0.5, HexColor('#BDC3C7')),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, -1), 7.5),
        ('LEFTPADDING', (0, 0), (-1, -1), 5),
        ('TOPPADDING', (0, 0), (-1, -1), 4),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 4),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
    ])
    lookup_t = Table(lookup_data, colWidths=[3*cm, 2.8*cm, 3.2*cm, 4.5*cm, 2.5*cm])
    lookup_t.setStyle(lookup_ts)
    story.append(lookup_t)

    story.append(Spacer(1, 8))
    story.append(TipBox(
        'Remember: HRCT diagnosis is ALWAYS clinical + radiological + PFT correlation. '
        'A multidisciplinary team discussion (MDD) is the gold standard for ILD diagnosis.',
        bg=LRED, border=RED, label='IMPORTANT'))

    # ── Build ─────────────────────────────────────────────────────────────────
    def make_template_callback(is_cover):
        def callback(canvas, doc):
            if is_cover:
                cover_page(canvas, doc)
            else:
                page_header_footer(canvas, doc)
        return callback

    from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame
    from reportlab.platypus import NextPageTemplate

    doc.build(
        story,
        onFirstPage=cover_page,
        onLaterPages=page_header_footer,
    )
    print(f"PDF generated: {path}")


if __name__ == '__main__':
    build_pdf('/home/daytona/workspace/hrct-guide/HRCT_CECT_Visual_Reference_Guide.pdf')
Running Command

cd /home/daytona/workspace/hrct-guide && python generate_hrct_guide.py 2>&1

Running Command

ls -lh /home/daytona/workspace/hrct-guide/HRCT_CECT_Visual_Reference_Guide.pdf

Running Command

python3 -c "from pypdf import PdfReader; r=PdfReader('/home/daytona/workspace/hr…

Your 11-page visual reference PDF is ready:

HRCT CECT Visual Reference Guide

PDF Document · PDF


What's inside (11 pages, A4):
SectionContent
CoverTitle page with table of contents
1 - Technical EssentialsCT window settings table (Lung/Mediastinal/Bone/HRCT), visual greyscale comparison, HRCT vs Standard CT differences
2 - Secondary LobuleLabeled hexagonal SPL diagram with centrilobular artery, interlobular septa, pulmonary veins, alveoli + disease distribution table
3 - HRCT Patterns Gallery10 programmatically drawn CT pattern circles: Normal, GGO, Consolidation, Reticulation, Honeycombing, Emphysema, Bronchiectasis, Crazy Paving, Mosaic, Spiculated Mass
4 - Distribution & ZonalLung distribution diagrams (upper/lower/peripheral) with disease associations
5 - Nodule TypesCentrilobular vs Perilymphatic vs Random nodule visual comparison with full comparison table
6 - Fleischner UIPColour-coded 4-tier UIP classification diagram + biopsy decision table + ILD comparison table (UIP, NSIP, COP, HP, DIP, Sarcoid)
7 - CECT PhasesContrast phase timeline diagram, phase table, mediastinal compartments (ITMIG) with masses (4 T's)
8 - Systematic Checklist8-step reading checklist with checkboxes: T-A-L-E-P-V-B mnemonic
9 - Pattern-Disease Lookup14-row quick reference table: pattern + distribution + feature → top diagnosis

Search for clinical case examples with HRCT images and findings

Finding Sources
Finding Sources
Searching the Web

HRCT chest clinical cases with images findings radiopaedia site:radiopaedia.org

Finding Sources
Finding Sources
Searching the Web

HRCT chest case examples IPF sarcoidosis hypersensitivity pneumonitis findings images 2024

Finding Sources
Finding Sources
Reading Web Page

https://radiologyassistant.nl/chest/hrct/common-diseases

Finding Sources
Finding Sources
Reading File
Finding Sources
Reading File
Reading File
Loading Image
Loading Image
Loading Image
Reading File
Loading Image
Loading Image
Reading File
Reading File
Finding Sources
Loading Image
Loading Image
Loading Image
Searching the Web

HRCT chest clinical case IPF honeycombing sarcoidosis NSIP hypersensitivity pneumonitis teaching cases open access 2024 2025

I now have excellent textbook images and case descriptions. Let me compile a comprehensive clinical case presentation:

HRCT Chest - Clinical Case Examples with Findings

All images sourced from Murray & Nadel's Textbook of Respiratory Medicine (2-Volume), Courtesy Michael B. Gotway, MD.

CASE 1 - Idiopathic Pulmonary Fibrosis (UIP Pattern): Early Stage

Clinical Scenario: 68-year-old man, non-smoker, 2 years of progressive exertional dyspnoea and dry cough. Bibasal fine end-inspiratory crackles ("Velcro" crackles). PFTs: restrictive pattern with reduced DLCO.
HRCT (axial, lung bases):
Early UIP/IPF - axial HRCT through lung bases shows subpleural reticulation and architectural distortion with traction bronchiectasis, but no discrete honeycombing
Findings:
  • Bilateral subpleural reticulation (increased fine lines in peripheral lung)
  • Architectural distortion
  • Traction bronchiectasis - dilated, irregular bronchi within fibrotic tissue
  • No discrete honeycombing at this stage
Interpretation: This is a Probable UIP pattern (Fleischner Society). No biopsy mandatory if clinical context fits IPF (age >60, male, no CTD, no exposure history).

CASE 2 - IPF: Coronal View Demonstrating Basal Predominance

HRCT (coronal reconstruction):
Coronal HRCT of IPF - subpleural reticulation with clear basal predominance demonstrating the classic lower-zone distribution of UIP/IPF
Findings:
  • Coronal view dramatically demonstrates basal predominant distribution
  • Subpleural reticulation most dense at lung bases
  • The upper lobes show much less disease - this basal gradient is pathognomonic of UIP
  • Upper lung zones are relatively spared
Teaching Point: Always reconstruct coronal images in suspected ILD - axial slices alone can miss the zonal distribution that is so discriminating. The basal predominance here immediately pushes the differential strongly toward UIP/IPF, away from HP (which spares the bases) or sarcoidosis (upper zone predominant).

CASE 3 - IPF: Classic Honeycombing on Prone HRCT

HRCT (axial prone, lung bases):
Honeycombing on HRCT in UIP/IPF - prone axial CT through lung bases shows extensive subpleural clustered cystic spaces with visible, mildly thickened walls that share walls with one another
Findings:
  • Extensive subpleural cystic change = honeycombing
  • Cyst walls are visible and mildly thickened (1-2 mm)
  • Cysts share walls with one another - the "clustered" arrangement is key
  • Prone position confirms these are not dependent atelectasis
Interpretation: This is a Typical UIP pattern - honeycombing + subpleural/basal distribution. Biopsy is NOT required.
Honeycombing vs other cysts:
FeatureHoneycombingLAM cystsLCH cysts
WallVisible, ~1-2 mm, sharedVery thin, uniformThick, bizarre shaped
LocationSubpleural, basalDiffuse, uniformUpper lobes
DistributionLayered/stackedRandomSpares bases
PatientOlder adult, M>FYoung womanSmoker

CASE 4 - Combined Pulmonary Fibrosis and Emphysema (CPFE)

HRCT (axial, lung bases):
Combined pulmonary fibrosis and emphysema - axial CT at lung bases showing centrilobular and paraseptal emphysema in upper lobes with basal predominant reticulation, honeycombing (arrow) consistent with UIP/IPF
Clinical Scenario: 72-year-old heavy smoker with disproportionate dyspnoea and relatively preserved spirometry (emphysema "masks" restriction).
Findings:
  • Upper lobes: centrilobular (single arrowhead) and paraseptal (double arrowhead) emphysema
  • Lower lobes (this image): basal reticulation + architectural distortion + honeycombing (arrow)
  • The two processes occupy different zones
Teaching Point: CPFE is an important syndrome where spirometry can be near-normal despite severe disease because emphysema (reduces elastic recoil, improves FVC) counterbalances fibrosis (reduces lung volumes). DLCO is disproportionately reduced. High risk of pulmonary hypertension and lung carcinoma.

CASE 5 - Sarcoidosis: Perilymphatic Nodule Distribution

HRCT (axial) with secondary lobule diagrams:
Sarcoidosis HRCT showing perilymphatic nodules - axial CT shows numerous subpleural nodules along costal and fissural pleural surfaces (single arrowheads) and nodules along bronchovascular bundles (double arrowheads), with diagrams showing the perilymphatic distribution pattern in the secondary pulmonary lobule
Clinical Scenario: 35-year-old woman, bilateral hilar lymphadenopathy on CXR. Mildly elevated ACE. Asymptomatic.
Findings (Panel A - axial HRCT):
  • Numerous subpleural nodules along the costal and fissural pleural surfaces (single arrowheads)
  • Nodules along bronchovascular bundles (double arrowheads)
  • Upper zone predominance (not shown but typical)
Panel B & C (diagrams): Show the perilymphatic distribution within the secondary pulmonary lobule - nodules hug the lymphatic pathways.
Interpretation: This is a perilymphatic pattern - highly specific for sarcoidosis in the appropriate clinical context. Other perilymphatic conditions include lymphangitic carcinomatosis and silicosis, but sarcoidosis is diagnosed here by the additional bilateral hilar adenopathy ("1-2-3 sign").
Sarcoidosis HRCT Features Summary:
  • Perilymphatic micronodules (1-5 mm), bilateral, upper zone
  • Bilateral hilar + right paratracheal lymphadenopathy
  • Perihilar distribution ("galaxy sign" in advanced disease)
  • Upper lobe fibrosis in stage IV (end-stage)

CASE 6 - Miliary TB: Random Nodule Distribution

HRCT (axial) with secondary lobule diagrams:
Miliary tuberculosis HRCT - axial CT shows numerous small circumscribed nodules equally distributed throughout both lungs bilaterally, with diagrams demonstrating the random distribution pattern involving all compartments of the secondary pulmonary lobule including pleural surfaces and septa
Clinical Scenario: 28-year-old immunocompromised patient from an endemic area with 3 weeks of fever, night sweats, weight loss.
Findings (Panel A - axial HRCT):
  • Numerous small circumscribed nodules equally distributed throughout both lungs bilaterally
  • Nodules seen in contact with fissural pleural surfaces as well as sparing fissural surfaces (random)
  • No zonal preference - upper, mid and lower zones equally affected
Panel B & C (diagrams): Random distribution = nodules in both perilymphatic AND centrilobular locations equally.
Interpretation: Random pattern = hematogenous spread. Top differentials:
  1. Miliary TB (fever + endemic area + immunocompromise)
  2. Miliary fungal infection (histoplasmosis, coccidioidomycosis)
  3. Hematogenous metastases (known primary malignancy)
  4. Miliary sarcoidosis (rare)

CASE 7 - Diffuse Pulmonary Haemorrhage: Centrilobular Nodules

HRCT (axial) with secondary lobule diagrams:
Centrilobular nodules in diffuse pulmonary haemorrhage - axial HRCT shows bilateral poorly defined ground-glass opacity nodules that approach but do not touch the costal and fissural pleural surfaces, with diagrams showing centrilobular distribution in the secondary lobule
Clinical Scenario: Patient with haemoptysis and falling haemoglobin. ANCA positive.
Findings (Panel A):
  • Numerous bilateral poorly defined GGO nodules (arrows)
  • Nodules approach but do not touch the pleura - note the "gap" between nodules and the major fissure (left side)
  • Nodules spared from the interlobular septa and small pulmonary veins (arrowheads)
  • This "sparing" around the pleural surfaces is the hallmark of centrilobular distribution
Panel B & C (diagrams): Centrilobular distribution - nodules clustered around the centrilobular arteries and bronchioles, with a margin separating them from the lobule periphery.
Interpretation: Centrilobular poorly defined GGO nodules in a patient with haemoptysis and ANCA = diffuse alveolar haemorrhage (DAH). Other causes of centrilobular nodules:
  • Hypersensitivity pneumonitis (subacute phase, bird/mold exposure)
  • Endobronchial spread of infection (TB, NTM, bacterial: "tree-in-bud")
  • Respiratory bronchiolitis-ILD (smoker, GGO centrilobular nodules)
  • Pulmonary oedema

Key Teaching Points from These Cases

CaseKey FindingLesson
1 - Early IPFSubpleural reticulation + traction bronchiect., no honeycombingProbable UIP - no biopsy needed
2 - IPF coronalBasal gradient on coronal viewAlways view coronal for zonal distribution
3 - IPF HoneycombingClustered subpleural cysts, shared wallsTypical UIP - honeycombing cements diagnosis
4 - CPFEUpper emphysema + lower honeycombingSpirometry can be normal; DLCO is key
5 - SarcoidosisPerilymphatic nodules + hilar nodesPleural + bronchovascular = perilymphatic
6 - Miliary TBEqual nodules all zones, randomRandom = hematogenous spread
7 - DAHCentrilobular GGO nodules, pleura sparedSparing of pleura = centrilobular

For online interactive cases with actual scroll-through CT images:
All CT images from Murray & Nadel's Textbook of Respiratory Medicine, courtesy Michael B. Gotway, MD.

Please give images disease wise

Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Reading File
Reading File
Loading Image
Loading Image
Loading Image
Loading Image
Loading Image
Loading Image
Reading File
Reading File
Loading Image
Loading Image
Excellent collection of images. Now I have everything needed for a comprehensive disease-wise presentation:

HRCT Chest - Images by Disease

All images sourced from Murray & Nadel's Textbook of Respiratory Medicine, courtesy Michael B. Gotway, MD.

1. EMPHYSEMA

HRCT appearance of the three types of emphysema: (A) Centrilobular - upper lobe punched-out cystic spaces WITHOUT walls with a centrilobular artery dot (arrow) in the center; (B) Panlobular - diffuse decrease in attenuation and simplification of architecture in left lung contrasted with normal transplanted right lung; (C) Paraseptal - single row of thin-walled subpleural cysts in upper lobes along the pleural surface. Bottom row shows gross pathology correlates of each type.
Figure 20.24 - Three types of emphysema on HRCT with gross pathology correlates.
TypeHRCT FeatureDistributionCause
Centrilobular (A)"Punched-out" lucencies WITHOUT walls; central artery dotUpper lobe, centrilobularSmoking
Panlobular (B)Diffuse low attenuation, simplified architecture, fewer vesselsLower lobe predominantAlpha-1 antitrypsin deficiency
Paraseptal (C)Single row of subpleural thin-walled cystsUpper lobe, subpleuralSpontaneous pneumothorax risk
Key: Emphysema has NO visible wall - the defining feature distinguishing it from honeycombing or LAM cysts.

2. CYSTIC LUNG DISEASES (Multiple in One Panel)

Multiple cystic lung diseases on HRCT: (A-B) LAM - multiple thin-walled round cysts of varying number throughout both lungs; (C) LCH - upper lobe bizarre-shaped thick-walled cysts and nodules (arrow) in 19-year-old; (D) Follicular bronchiolitis - uniform subpleural thin-walled cysts; (E) LIP in Sjogren syndrome - uniform midlung cysts; (F-G) Birt-Hogg-Dube - lower lobe subpleural paradiastinal oblong septated cysts; (H) Amyloidosis cysts with calcified nodule within one cyst; (I) Light chain deposition disease; (J-K) Constrictive bronchiolitis - thin-walled cysts with mosaic perfusion (arrow)
Figure 20.25 - Differential diagnosis of cystic lung diseases on HRCT.
DiseaseCyst ShapeDistributionClue
LAMRound, uniform, thin wallsDiffuse throughout both lungsYoung woman, pneumothorax
LCHBizarre shapes, thick wallsUpper lobe, spares basesSmoker, nodules early stage
LIP/SjogrenRound, uniformMidlung / subpleuralAutoimmune, dry eyes/mouth
Birt-Hogg-DubeOblong, septatedLower lobe, subpleural, paramediastinalSkin fibrofolliculomas, renal tumours
AmyloidosisVariableAnyCalcified nodule within cyst (pathognomonic)

3. IDIOPATHIC PULMONARY FIBROSIS / UIP

3a - Early IPF (Probable UIP): Axial View

Early UIP/IPF - axial HRCT through lung bases showing bilateral subpleural reticulation and architectural distortion with traction bronchiectasis, but no discrete honeycombing visible
Findings: Subpleural reticulation + traction bronchiectasis + architectural distortion. No honeycombing = Probable UIP.

3b - IPF: Coronal View Showing Basal Predominance

Coronal HRCT reconstruction of IPF showing the characteristically basal-predominant subpleural reticulation and fibrosis - the upper lungs are relatively spared while the lower zones show dense reticular opacities
Teaching point: Coronal reconstruction immediately reveals the basal-to-apical gradient that separates UIP/IPF from HP (which spares bases) and sarcoidosis (upper zone predominant).

3c - IPF: Honeycombing (Typical UIP)

Prone axial HRCT of lung bases in IPF showing extensive subpleural clustered cystic spaces - honeycombing. The cysts have visible mildly thickened walls measuring 1-2mm that share walls with one another, arranged in a multilayered subpleural distribution
Findings: Clustered subpleural cystic spaces, walls 1-2 mm thick, shared walls, arranged in layers = Honeycombing = Typical UIP. Biopsy NOT required.

3d - Combined Pulmonary Fibrosis + Emphysema (CPFE)

Axial HRCT at lung bases showing combined pulmonary fibrosis and emphysema - basal reticulation with architectural distortion and honeycombing (arrow) in the lower lobes consistent with UIP/IPF, while upper lobes showed centrilobular and paraseptal emphysema
Finding: Upper emphysema + lower honeycombing (arrow). Spirometry near-normal; DLCO disproportionately reduced; high risk of pulmonary hypertension.

4. HYPERSENSITIVITY PNEUMONITIS (HP)

4a - Fibrotic HP: GGO + Reticulation + Traction Bronchiectasis

Fibrotic hypersensitivity pneumonitis - bilateral areas of ground-glass opacity admixed with extensive reticulation, traction bronchiectasis, and architectural distortion. The peribronchial distribution and coexistence of GGO with fibrotic features (without pure lower zone predilection) helps distinguish from IPF
Figure 91.4 - Fibrotic HP: key feature is GGO admixed with fibrosis (unlike pure IPF where GGO is minimal). No lower-zone predominance - helps differentiate from IPF/NSIP.

4b - HP: The "Headcheese" Sign

The headcheese sign in hypersensitivity pneumonitis - axial HRCT shows a striking combination of multifocal ground-glass opacity (infiltrative disease) with areas of lobular low attenuation/mosaic perfusion (arrowheads, obstructive disease due to air trapping) on the SAME inspiratory scan. This three-density pattern of high (GGO), intermediate (normal), and low (air trap) attenuation resembles the variegated appearance of headcheese meat.
Figure 91.5 - The "Headcheese" sign: Three densities on one inspiratory scan:
  • White/grey = GGO (infiltration/inflammation)
  • Normal grey = unaffected lung
  • Dark lobules (arrowheads) = air trapping / mosaic perfusion
This sign has specificity of 93% for HP. It reflects simultaneous restrictive (GGO) and obstructive (air trapping) pathology from bronchiolar inflammation.
HP vs IPF differentiators:
FeatureHPIPF/UIP
ZoneMid/upper, or diffuseBasal + subpleural
GGOProminent, diffuseMinimal
Air trappingYES (on expiratory scan)Absent
Centrilobular nodulesYES (subacute phase)No
Headcheese signYES (highly specific)No
Exposure historyBird/mould/farmerNone

5. SARCOIDOSIS (Scadding Stages 1-4)

5a - Stage 1: Bilateral Hilar Lymphadenopathy Only

Sarcoidosis Stage 1 - (A) CXR showing bilateral symmetric peribronchial lymphadenopathy (arrows) and right paratracheal enlargement (arrowhead); (B-C) CECT confirms bilateral peribronchial (arrows) and right paratracheal (arrowhead) lymphadenopathy plus additional mediastinal nodes (asterisks). No parenchymal disease.
Figure 93.3 - Scadding Stage 1: BHL only. The "1-2-3 sign" or "Pawnbroker sign" = right paratracheal + bilateral hilar enlargement. CECT confirms bilateral symmetric hilar + mediastinal nodes.

5b - Stage 2: BHL + Parenchymal Nodules

Sarcoidosis Stage 2 - (A) CXR with bilateral peribronchial lymph node enlargement (arrows) and small nodules; (B) lung window HRCT shows perilymphatic nodules along fissural surfaces (white arrowhead) and beaded appearance of pulmonary vessels from peribronchovascular nodules (black arrowheads); (C) soft tissue window CECT confirms hilar lymphadenopathy (arrows)
Figure 93.4 - Stage 2: BHL + parenchymal nodules. Key HRCT features:
  • Nodules along fissural surfaces (perilymphatic, white arrowhead)
  • "Beaded" appearance of bronchovascular bundles (black arrowheads) - pathognomonic of sarcoidosis

5c - Stage 3: Parenchymal Disease Only (No BHL)

Sarcoidosis Stage 3 - (A) CXR shows upper lobe nodular opacities without hilar enlargement; (B-C) HRCT shows small nodules along fissures (arrowheads) and along bronchovascular bundles (arrows), creating the characteristic beaded vessels. No significant hilar or mediastinal adenopathy. Upper lobe predominance.
Figure 93.5 - Stage 3: Parenchymal disease without hilar nodes. Perilymphatic nodules remain on HRCT - distributed along fissures and bronchovascular bundles (arrows = "beaded vessels"). Upper lobe predominance.

5d - Stage 4: Fibrotic Sarcoidosis

Sarcoidosis Stage 4 (fibrotic) - (A) CXR shows upper lobe reticular opacities with architectural distortion and bilateral superior hilar retraction consistent with fibrosis; (B-C) HRCT shows upper lobe patchy opacities with architectural distortion and traction bronchiectasis (arrows), plus subpleural and perifissural nodular opacities (arrowheads)
Figure 93.11 - Stage 4: Upper lobe fibrosis (OPPOSITE of IPF which is basal). Traction bronchiectasis (arrows) + architectural distortion. Superior hilar retraction on CXR. Residual perilymphatic nodules (arrowheads) help maintain the diagnosis.

6. NODULE DISTRIBUTION: PERILYMPHATIC vs RANDOM vs CENTRILOBULAR

6a - Perilymphatic (Sarcoidosis)

Perilymphatic nodule distribution in sarcoidosis - axial HRCT showing nodules clustered at the subpleural and fissural surfaces (single arrowheads) and along bronchovascular bundles (double arrowheads). The diagram shows nodules following the lymphatic pathways in the secondary pulmonary lobule along the septa and pleura.
Key: Nodules at the pleura, fissures, and bronchovascular bundles.

6b - Random (Miliary TB)

Random nodule distribution in miliary TB - axial HRCT shows numerous small circumscribed nodules uniformly distributed throughout both lungs. Nodules are seen both touching and not touching the fissural pleural surfaces, with no zonal or lobular preference - the hallmark of hematogenous/random spread.
Key: Uniform distribution, no zonal preference, nodules at AND away from pleura.

6c - Centrilobular (Pulmonary Haemorrhage / HP)

Centrilobular nodule distribution in diffuse pulmonary haemorrhage - axial HRCT shows bilateral poorly defined ground-glass nodules that approach but do not touch the pleural surfaces and fissures. The spared rim of lung between the nodules and the major fissure is the key indicator of centrilobular rather than perilymphatic distribution.
Key: Nodules near but NOT touching the pleura - the preserved rim is the defining sign.

Quick Summary Table

DiseaseDominant PatternZoneKey SignAvoid Confusing With
IPF (UIP)Reticulation + HoneycombingBasal + SubpleuralTraction bronchiectasis, no GGONSIP (has GGO + subpleural sparing)
EmphysemaLow attenuation (no walls)Upper (centrilobular)Central dot signLAM (has visible walls)
HPGGO + Centrilobular nodules + Air trappingMid/upper or diffuseHeadcheese signIPF (HP has air trapping + GGO)
SarcoidosisPerilymphatic nodulesUpper, perihilarBeaded vessels, 1-2-3 signLymphangitic carcinomatosis
Miliary TBRandom tiny nodulesUniform, all zonesNo zonal preferenceHematogenous mets (need clinical context)
LAMMultiple round thin-walled cystsDiffuseUniform cysts in young womanLCH (bizarre shapes), emphysema (no walls)
LCHBizarre-shaped cysts + nodulesUpper, spares basesSmoker, upper predominanceLAM (uniform), emphysema
Source: Murray & Nadel's Textbook of Respiratory Medicine, 2-Volume Set.

Heart failure according tin tintinalli textbook

Reading File
Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
I now have all the core content from Tintinalli's chapter on Acute Heart Failure. Here is the complete, structured summary:

Heart Failure — Tintinalli's Emergency Medicine (Chapter 53)

Alan B. Storrow, Brian Bales, Sean P. Collins

INTRODUCTION & EPIDEMIOLOGY

Acute heart failure (AHF) covers a wide spectrum - from gradual leg swelling and reduced exercise tolerance to abrupt pulmonary oedema. The terms "decompensated heart failure," "acute heart failure syndrome," and "hospitalized with heart failure" are often used interchangeably. The outdated term congestive heart failure specifically describes fluid accumulation signs and symptoms.
  • >80% of ED visits for AHF result in hospital admission
  • ~50% of patients die within 5 years of diagnosis
  • Hospitalization is an inflection point - hospitalised patients have higher mortality than matched non-hospitalised cohort
  • Prevalence is increasing due to aging population and improved AMI survival

PATHOPHYSIOLOGY

Heart failure is a clinical syndrome of dyspnoea, oedema, and fatigue from structural or functional cardiac damage impairing pump efficiency.

Neurohormonal Cascade

When cardiac output drops:
  • Renin-angiotensin-aldosterone system (RAAS) and sympathetic nervous system activate
  • Norepinephrine, vasopressin, endothelin (potent vasoconstrictor), TNF-α are released
  • Net effect: Na+ and water retention + increased SVR
  • This maintains BP and perfusion short-term but increases myocardial workload, wall tension, and O2 demand
  • Leads to cardiac remodelling and progressive dysfunction

Natriuretic Peptides (Counterregulatory Response)

PeptideSourceHalf-life
ANP (Atrial Natriuretic Peptide)AtriaShort
BNP (B-type Natriuretic Peptide)Cardiac ventricle~20 min
NT-proBNP (inactive cleavage product)Ventricle~2 hours
CNPEndothelium-
BNP/NT-proBNP produce vasodilation, natriuresis, decrease endothelin, and inhibit RAAS and sympathetic nervous system.

Acute Pulmonary Oedema Mechanism

A downward spiral occurs:
  1. CO drops → SVR rises (compensatory vasoconstriction)
  2. ↑ SVR → further drops CO
  3. Even small BP elevations can trigger this spiral in susceptible patients
  4. Can be abrupt, severely symptomatic, and rapidly fatal

CLASSIFICATION (6 Phenotypes - Table 53-1)

ClassificationCharacteristics
Hypertensive AHFAHF signs + BP >140 mmHg + preserved LV function; CXR: pulmonary oedema; onset <48 h; fluid redistribution more than overload
Pulmonary OedemaRespiratory distress + rales + reduced SpO2 + CXR findings
Cardiogenic ShockTissue hypoperfusion; SBP typically <90 mmHg (see Ch. 50)
Acute-on-Chronic HFMild-moderate AHF; SBP 90-140 mmHg; increased peripheral oedema; onset over days
High-Output FailureHigh CO, tachycardia, warm extremities, pulmonary congestion; caused by anaemia, thyrotoxicosis
Right Heart FailureLow-output syndrome; JVD + hepatomegaly ± hypotension; from pulmonary disease, TR, OSA; no pulmonary congestion

SYSTOLIC vs. DIASTOLIC HEART FAILURE

HFrEF (Systolic)HFpEF (Diastolic)
EF<50%Preserved (≥50%)
MechanismVentricle fails to eject bloodVentricle fails to receive blood
ProblemAfterload sensitivity, ↑ intracardiac volume↓ LV compliance, preload sensitivity
Common causesIschaemic CM, dilated CMHypertension, LVH, ageing
Key feature↑ LV volumes, dilatedNormal or ↑ wall thickness, stiff

DIAGNOSIS

"There is no single diagnostic test for heart failure; it is a clinical diagnosis based on all clinical data, especially history and physical examination."

History

FindingSensitivitySpecificityLR+
History of HF56%80%2.7
Dyspnoea on exertion84% (highest sensitivity)--
Paroxysmal nocturnal dyspnoeaLower76-84% (most specific)-
OrthopnoeaLower76-84%-
OedemaLower76-84%-

Physical Examination

FindingLR+LR-Note
S3 gallop4.0 (highest LR+)0.91Not useful if absent; inter-rater reliability poor
Overall clinical gestaltSensitivity 61%, specificity 86%-Best when >80% confident

Common Precipitants of AHF (Table 53-2)

Non-adherence:
  • Excess salt or fluid intake
  • Medication non-adherence
Cardiac:
  • Acute coronary syndrome
  • New-onset or poorly controlled arrhythmias (especially AF)
  • Worsening valvular disease
  • Hypertensive crisis
Non-cardiac:
  • Renal failure (especially missed dialysis)
  • Substance abuse (cocaine, methamphetamines, ethanol)
  • Poorly controlled hypertension
  • Recent addition of negative inotropes (CCB, beta-blockers)
  • NSAID use
  • Infections (sepsis, pneumonia)
  • Pulmonary embolism
  • Anaemia/thyrotoxicosis (high-output)

Diagnostic Testing

BNP / NT-proBNP:
  • Most useful objective test in undifferentiated dyspnoea
  • BNP <100 pg/mL = very unlikely HF; BNP >400 pg/mL = highly likely HF
  • NT-proBNP: age-adjusted cutoffs (>450 pg/mL in <50 yrs; >900 in 50-75 yrs; >1800 in >75 yrs)
  • Elevated troponin in AHF = high-risk marker (myocardial injury component)
CXR findings in AHF:
  • Cardiomegaly (CTR >0.5)
  • Pulmonary vascular redistribution (upper lobe diversion)
  • Interstitial oedema (Kerley B lines)
  • Alveolar oedema ("butterfly/bat-wing" pattern)
  • Pleural effusions (right > left)
ECG:
  • Not diagnostic but identifies precipitants: AF, ischaemia, LVH, bundle branch blocks
Echocardiography (ED ultrasound - POCUS):
  • IVC: collapsibility assesses volume status
  • B-lines on lung US: ≥3 per zone = interstitial oedema (sens ~90%)
  • LV function: gross assessment of systolic function
  • Pericardial effusion/tamponade

TREATMENT

Initial Measures (All AHF)

  • Supplemental oxygen to keep SpO2 ≥95%
  • Cardiac monitoring + IV access
  • Frequent vital signs
  • Urinary catheter only for severely ill or incontinent (avoid routine use)

1. Noninvasive Ventilation (NIV)

  • CPAP or BiPAP - decreases work of breathing, avoids intubation
  • Indicated for severe dyspnoea, pulmonary oedema with hypoxia
  • Reduces need for intubation and improves short-term survival (Tables 53-3 & 53-4)

2. HYPERTENSIVE AHF: Vasodilators First

"Initiate nitrates BEFORE diuretics in hypertensive AHF"

Nitroglycerin (NTG)

  • Short-acting, rapid-onset venous and arterial dilator
  • Decreases preload; at high doses, reduces afterload
  • May have coronary vasodilatory benefit (↓ myocardial ischaemia)
RouteDoseNotes
Sublingual0.4 mg (400 mcg) spray/tablet q 1-5 minEasily administered, rapidly bioavailable
IVStart 0.5-0.7 mcg/kg/min; titrate up to 200 mcg/minBased on BP + symptoms
Transdermal0.5-2 inches to chest wallSlow onset - only after initial improvement
Complications: Hypotension (transient; if persistent → think volume depletion or RV infarct → 250-1000 mL NS bolus), headache

Nitroprusside

  • Start 0.3 mcg/kg/min; max 10 mcg/kg/min
  • Complications: Hypotension, cyanide/thiocyanate toxicity, coronary steal

3. DIURETICS (Table 53-6)

Start after vasodilators (in hypertensive AHF). Indicated for volume overload.
DrugIV DoseOnsetDuration
FurosemideNo prior use: 20-40 mg IVP; Prior use: total daily oral dose × 1-2.5, given as IV boluses q12h15-20 min4-6 h
Bumetanide1-3 mg IV (40:1 furosemide equivalent)10 minPeak at 60 min
Torsemide10-20 mg IV (2:1 furosemide equivalent)10 minPeak 1-2 h
Complications: ↓K+, ↓Mg2+, hyperuricaemia, hypovolaemia, ototoxicity, prerenal azotaemia
Note: One trial found hospital mortality benefit (2.3% vs. 6%) for IV diuretics within 60 min of arrival.

4. NORMOTENSIVE / ACUTE-ON-CHRONIC HF

Management flowchart:
  1. Oxygen + monitoring
  2. BNP, troponin, renal function, electrolytes
  3. Loop diuretic (furosemide)
  4. Consider NIV if dyspnoeic
  5. Assess for ischaemia/arrhythmia as precipitant

5. DRUGS TO AVOID / USE CAUTIOUSLY

DrugReason to Avoid in Acute HF
Morphine↑ need for mechanical ventilation, prolonged hospitalisation, ICU admission, mortality. If used, small titrated doses (2-4 mg IV) only
NSAIDsNa+/water retention, blunt diuretic effect, ↑ morbidity/mortality
CCBs (oral)Myocardial depressant activity; no benefit or worse outcomes
Beta-blockersNot initiated in acute setting (except rate control); do not start de novo
IV ACE-I/ARBsInsufficient ED data; continue oral if already on them

HIGH-RISK FEATURES (Admission Criteria - Table 53-7/53-8)

Admit patients with any of the following:
  • Hypotension (SBP <90 mmHg) - cardiogenic shock
  • Persistent hypoxia despite NIV
  • Altered mental status
  • New ischaemia: elevated troponin, ischaemic ECG changes
  • Tachycardia (new AF or refractory arrhythmia)
  • Severe renal dysfunction (elevated creatinine, low Na+)
  • Need for invasive monitoring or procedures → ICU
High-risk biochemical markers (consistently predictive):
  • Elevated troponin or natriuretic peptides
  • Low serum sodium
  • Renal dysfunction (elevated creatinine)
  • Low blood pressure
Note: High-risk markers are absent in up to 50% of ED patients with AHF, limiting their impact on disposition.

DISPOSITION ALGORITHM

AHF patient responds to ED therapy?
        │
    YES ─────────────────────────────── NO
        │                                 │
High-risk features present?         Admit to hospital
        │
    NO ──────────────── YES
        │                 │
Observation unit     Hospital admission
(12-24 h, monitor   (consider ICU if needs
 vitals, UO, weight,  invasive monitoring)
 further testing)
        │
Discharge if improved + no high-risk features
+ outpatient follow-up within 5 days
  • ~75% of AHF patients respond to therapy, have no high-risk features, and can be discharged
  • Outpatient follow-up within 5 days decreases readmissions
  • Observation unit strategy reduces costs for selected lower-risk patients

SPECIAL SITUATIONS

Hypertensive Acute Pulmonary Oedema

  • Treat aggressively with nitrates + NIV first
  • Failing heart very sensitive to afterload - even SBP 150 mmHg can trigger pulmonary oedema
  • Prompt vasodilation can avoid intubation

High-Output Heart Failure

  • Normal/elevated EF; tachycardia; warm extremities
  • Causes: anaemia, thyrotoxicosis, AV fistula, Paget's, sepsis
  • Treat the underlying cause - volume removal may worsen if low-output coexists

Right Heart Failure

  • JVD + hepatomegaly + peripheral oedema; no pulmonary oedema
  • Causes: pulmonary hypertension, COPD, PE, TR, OSA
  • Do NOT aggressively diurese - RV is volume-dependent
  • Treat precipitant; cautious fluids if needed

KEY PEARLS FROM TINTINALLI'S

  1. No single test diagnoses AHF - it is a clinical diagnosis
  2. Dyspnoea on exertion = most sensitive symptom (84%); S3 gallop = most specific sign (LR+ 4.0)
  3. Nitrates before diuretics in hypertensive AHF
  4. Avoid morphine - associated with worse outcomes
  5. NIV (CPAP/BiPAP) reduces intubation and improves survival in respiratory distress
  6. BNP <100 makes HF unlikely; >400 makes it likely
  7. High-risk markers absent in 50% of patients - use clinical judgment for disposition
  8. 5-day follow-up after discharge reduces readmissions
  9. In normotensive AHF, look for and treat the precipitant (ACS, AF, infection, missed dialysis, non-compliance)
  10. Calcium channel blockers are contraindicated in systolic HF
  • Tintinalli's Emergency Medicine: A Comprehensive Study Guide, Chapter 53 - Acute Heart Failure
This is a shared conversation. Sign in to Orris to start your own chat.