Lung ultrasound
"lung ultrasound"[MeSH Terms] AND diagnosis
"lung ultrasound" AND "point of care"

Z-lines vs. B-lines: Z-lines are hyperechoic lines from the pleura that extend <15 cm and are non-diagnostic. B-lines must reach >15 cm depth to count.
| Finding | M-Mode Pattern | Meaning |
|---|---|---|
| Normal lung sliding | "Seashore sign" - horizontal lines (chest wall) above, granular pattern below | Pleura are apposed; no pneumothorax |
| Pneumothorax | "Stratosphere sign" / "Barcode sign" - horizontal lines throughout | Air separates pleural layers |
| Lung point | Alternating seashore + barcode | Edge of pneumothorax - 100% specific |


| Test | Pooled Sensitivity | Pooled Specificity |
|---|---|---|
| Lung ultrasound | 79-95% | 98-99% |
| Chest radiography | 40-52% | 99-100% |


| Profile | Typical Diagnosis |
|---|---|
| A-profile (A-lines + sliding) | Normal or PE/asthma/COPD |
| B-profile (bilateral B-lines) | Pulmonary edema |
| A/B profile (asymmetric) | Pneumonia |
| C-profile (anterior consolidation) | Pneumonia |
| A-profile + no sliding | Pneumothorax |
Generate a quick reference PDF comparing LUS patterns in pneumonia vs. pulmonary edema vs. pneumothorax
ls -la /tmp/workspace/ 2>/dev/null || echo "workspace not found"
mkdir -p /tmp/workspace/lus-quickref && python3 -c "from reportlab.platypus import SimpleDocTemplate; print('reportlab ok')"
~/lus-quickref/generate_lus_pdf.py
#!/usr/bin/env python3
"""
LUS Quick Reference PDF - Pneumonia vs Pulmonary Edema vs Pneumothorax
Generated with ReportLab
"""
from reportlab.platypus import (
SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer,
HRFlowable, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib.units import cm, mm
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus.flowables import BalancedColumns
import os
OUTPUT = "/tmp/workspace/lus-quickref/LUS_Quick_Reference.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
C_NAVY = colors.HexColor("#1A2B4A") # header bg
C_BLUE = colors.HexColor("#2E6DA4") # section headers
C_LTBLUE = colors.HexColor("#D6E8F7") # alternate row / label bg
C_ORANGE = colors.HexColor("#E8500A") # pneumothorax accent
C_GREEN = colors.HexColor("#1E7E34") # pneumonia accent
C_PURPLE = colors.HexColor("#5A3E8B") # pulm edema accent
C_GOLD = colors.HexColor("#C98B00") # warnings / notes
C_LIGHTGRAY = colors.HexColor("#F4F6F8")
C_WHITE = colors.white
C_BLACK = colors.black
# ── Page setup ───────────────────────────────────────────────────────────────
PAGE = landscape(A4)
MARGIN = 1.5 * cm
doc = SimpleDocTemplate(
OUTPUT,
pagesize=PAGE,
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=MARGIN, bottomMargin=MARGIN,
title="Lung Ultrasound Quick Reference",
author="Orris Medical",
subject="LUS patterns: Pneumonia vs Pulmonary Edema vs Pneumothorax"
)
styles = getSampleStyleSheet()
def style(name, **kw):
s = ParagraphStyle(name, **kw)
return s
# Custom paragraph styles
S_TITLE = style("Title2",
fontSize=20, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER, leading=24)
S_SUBTITLE = style("Subtitle2",
fontSize=10, fontName="Helvetica",
textColor=colors.HexColor("#BBDDFF"), alignment=TA_CENTER, leading=13)
S_SECTION = style("SectionHead",
fontSize=11, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER, leading=14)
S_BODY = style("Body2",
fontSize=8.5, fontName="Helvetica",
textColor=C_BLACK, leading=12)
S_BODY_BOLD = style("BodyBold",
fontSize=8.5, fontName="Helvetica-Bold",
textColor=C_BLACK, leading=12)
S_SMALL = style("Small",
fontSize=7.5, fontName="Helvetica",
textColor=colors.HexColor("#333333"), leading=10)
S_SMALL_BOLD = style("SmallBold",
fontSize=7.5, fontName="Helvetica-Bold",
textColor=C_BLACK, leading=10)
S_CELL = style("Cell",
fontSize=8, fontName="Helvetica",
textColor=C_BLACK, leading=11, leftIndent=3)
S_CELL_BOLD = style("CellBold",
fontSize=8, fontName="Helvetica-Bold",
textColor=C_BLACK, leading=11, leftIndent=3)
S_CELL_WHITE = style("CellWhite",
fontSize=8.5, fontName="Helvetica-Bold",
textColor=C_WHITE, leading=11, alignment=TA_CENTER)
S_TICK = style("Tick",
fontSize=8, fontName="Helvetica",
textColor=C_BLACK, leading=11, leftIndent=6)
S_NOTE = style("Note",
fontSize=7.5, fontName="Helvetica-Oblique",
textColor=colors.HexColor("#444444"), leading=10, leftIndent=3)
S_WARN = style("Warn",
fontSize=7.5, fontName="Helvetica-Bold",
textColor=C_GOLD, leading=10, leftIndent=3)
S_FOOTER = style("Footer",
fontSize=7, fontName="Helvetica",
textColor=colors.HexColor("#777777"), alignment=TA_CENTER, leading=9)
def P(text, s=S_BODY): return Paragraph(text, s)
def SP(h=4): return Spacer(1, h)
# ── Helper: coloured header cell ─────────────────────────────────────────────
def hdr(text, bg, color=C_WHITE, size=9):
s = ParagraphStyle("hdr_tmp", fontSize=size, fontName="Helvetica-Bold",
textColor=color, alignment=TA_CENTER, leading=size+3)
return Paragraph(text, s)
# ────────────────────────────────────────────────────────────────────────────
# PAGE HEADER BANNER
# ────────────────────────────────────────────────────────────────────────────
header_data = [[
P("🫁 LUNG ULTRASOUND · QUICK REFERENCE CARD", S_TITLE),
]]
header_table = Table(header_data, colWidths=[PAGE[0] - 2*MARGIN])
header_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_NAVY),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING",(0,0),(-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING",(0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [6,6,6,6]),
]))
subtitle_data = [[
P("Pneumonia · Pulmonary Edema · Pneumothorax | Pattern Recognition at the Bedside", S_SUBTITLE),
]]
subtitle_table = Table(subtitle_data, colWidths=[PAGE[0] - 2*MARGIN])
subtitle_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_BLUE),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
]))
# ────────────────────────────────────────────────────────────────────────────
# MAIN COMPARISON TABLE
# ────────────────────────────────────────────────────────────────────────────
# Column widths (landscape A4 usable ~25 cm)
USABLE = PAGE[0] - 2*MARGIN
C0 = USABLE * 0.17 # Feature
C1 = USABLE * 0.28 # Pneumonia
C2 = USABLE * 0.28 # Pulm Edema
C3 = USABLE * 0.27 # Pneumothorax
def cell(txt, bold=False, italic=False, color=C_BLACK, size=8):
fname = "Helvetica-Bold" if bold else ("Helvetica-Oblique" if italic else "Helvetica")
s = ParagraphStyle("_c", fontSize=size, fontName=fname, textColor=color, leading=11, leftIndent=4)
return Paragraph(txt, s)
def bullet_list(items, color=C_BLACK, size=8):
lines = "".join(f"• {i}<br/>" for i in items)
s = ParagraphStyle("_bl", fontSize=size, fontName="Helvetica",
textColor=color, leading=11.5, leftIndent=4)
return Paragraph(lines, s)
PNE_C = colors.HexColor("#E8F5E9") # pneumonia row bg
OED_C = colors.HexColor("#EDE7F6") # oedema row bg
PTX_C = colors.HexColor("#FFF3E0") # pneumothorax row bg
HEADER_ROW = [
hdr("Feature", C_NAVY, size=9),
hdr("Pneumonia", C_GREEN, size=9),
hdr("Pulmonary Edema", C_PURPLE, size=9),
hdr("Pneumothorax", C_ORANGE, size=9),
]
rows = [HEADER_ROW]
data_rows = [
(
"Lung Sliding",
["Usually PRESENT", "May be ABSENT over consolidation"],
["Usually PRESENT", "May be absent in severe cases"],
["ABSENT (key sign)", "NPV 100% when present → rules out PTX"],
),
(
"A-Lines\n(horizontal artifacts)",
["Absent or obscured", "Replaced by B-lines or consolidation"],
["Absent (replaced by B-lines)"],
["PRESENT with absent sliding", "= diagnostic combination for PTX"],
),
(
"B-Lines\n(vertical artifacts)",
["Focal / regional", "Non-homogenous distribution", "Localised to affected lobe/segment"],
["Diffuse bilateral ≥3 per ICS", "Homogenous distribution", "May fuse into 'white lung'"],
["ABSENT (rules out PTX when present)", "B-lines confirm pleural apposition"],
),
(
"Pleural Line\nAppearance",
["Irregular, fragmented", "Subpleural micro-consolidations", "Pleural line thickened"],
["Smooth, regular", "Normal pleural line"],
["Normal but STATIONARY", "No shimmer/glide sign"],
),
(
"Consolidation",
["YES — tissue-like hepatization", "Air bronchograms present", "Parallel to pulmonary arteries"],
["Absent (unless severe ARDS overlap)"],
["Absent", "(collapsed lung may be seen only with large PTX)"],
),
(
"Air Bronchograms",
["Static or dynamic air bronchograms", "Static = obstruction", "Dynamic = patent airway"],
["Absent"],
["Absent"],
),
(
"Pleural Effusion",
["May be present (parapneumonic)", "Anechoic or complex with septations"],
["Often bilateral, anechoic", "Corresponds to heart failure severity"],
["Absent"],
),
(
"M-Mode Pattern",
["Seashore sign may be lost over consolidation"],
["Seashore sign present (if sliding intact)"],
["Stratosphere / Barcode sign", "Lung point: 100% specific for PTX"],
),
(
"Distribution",
["Focal / lobar", "Posterior > anterior", "Usually unilateral"],
["Bilateral, symmetric", "Dependent > non-dependent"],
["Anterior in supine", "Small: anterior MCL", "Large: extends laterally"],
),
(
"Lung Point",
["Absent"],
["Absent"],
["PRESENT — 100% specific", "= edge of pneumothorax", "Not always found in large PTX"],
),
(
"Key Differentiator",
["Irregular pleural line +\nfocal B-lines +\nconsolidation with air bronchograms"],
["Diffuse bilateral B-lines\n(≥3 per ICS bilateral)\nSmooth pleural line\nNo consolidation"],
["Absent sliding +\nA-lines +\nLung point (confirms)"],
),
(
"BLUE Protocol\nProfile",
["C-profile (anterior consolidation)", "A/B profile (asymmetric B-lines)", "A-profile + posterior consolidation"],
["B-profile (bilateral B-lines)"],
["A-profile + absent sliding"],
),
(
"Performance\n(vs CXR)",
["Sens 85-93%, Spec 88-95%\n(paediatric CAP)", "Superior to CXR in resource-limited settings"],
["LR+ and LR− outperform\nall other binary tests\nfor AHF in ED"],
["Sens 79-95%, Spec 98-99%\n(meta-analysis)\nvs CXR Sens 40-52%"],
),
]
for feature, pne, oed, ptx in data_rows:
row = [
cell(feature, bold=True, color=C_NAVY, size=8),
bullet_list(pne, color=colors.HexColor("#1B5E20"), size=8),
bullet_list(oed, color=colors.HexColor("#311B92"), size=8),
bullet_list(ptx, color=colors.HexColor("#BF360C"), size=8),
]
rows.append(row)
main_table = Table(rows, colWidths=[C0, C1, C2, C3], repeatRows=1)
# Build row-by-row background styles
bg_styles = [
# Header row
("BACKGROUND", (0,0), (0,0), C_NAVY),
("BACKGROUND", (1,0), (1,0), C_GREEN),
("BACKGROUND", (2,0), (2,0), C_PURPLE),
("BACKGROUND", (3,0), (3,0), C_ORANGE),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 9),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,-1), "TOP"),
# Alternating row bgs (feature col always light blue)
("BACKGROUND", (0,1), (0,-1), C_LTBLUE),
]
for i in range(1, len(rows)):
bg = C_LIGHTGRAY if i % 2 == 1 else C_WHITE
bg_styles += [
("BACKGROUND", (1,i), (1,i), PNE_C if i % 2 == 1 else colors.HexColor("#F1F8F1")),
("BACKGROUND", (2,i), (2,i), OED_C if i % 2 == 1 else colors.HexColor("#F5F0FB")),
("BACKGROUND", (3,i), (3,i), PTX_C if i % 2 == 1 else colors.HexColor("#FFF8F0")),
]
main_table.setStyle(TableStyle(bg_styles + [
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#BBBBCC")),
("LINEBELOW", (0,0), (-1,0), 1.5, C_NAVY),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING",(0,0),(-1,-1), 4),
# Thicker left border for each condition column
("LINEAFTER", (0,0), (0,-1), 1.5, C_NAVY),
("LINEBEFORE", (1,0), (1,-1), 1.5, C_GREEN),
("LINEBEFORE", (2,0), (2,-1), 1.5, C_PURPLE),
("LINEBEFORE", (3,0), (3,-1), 1.5, C_ORANGE),
]))
# ────────────────────────────────────────────────────────────────────────────
# BOTTOM LEGEND / NOTES BAR
# ────────────────────────────────────────────────────────────────────────────
legend_items = [
("B-line rule:", "Discrete vertical hyperechoic ray from pleura to screen bottom (>15 cm). ≥3 per ICS = abnormal."),
("Z-line:", "Hyperechoic vertical line <15 cm — non-diagnostic, do not count as B-lines."),
("Lung point:", "100% specific for PTX. Alternating seashore/barcode on M-mode."),
("BLUE protocol:", "3 binary signs: anterior sliding · bilateral B-lines · posterior consolidation/effusion."),
("Transducer:", "Linear 5-10 MHz for PTX (higher sensitivity). Phased-array/curvilinear for B-lines (need >15 cm depth)."),
("Sources:", "Fishman's Pulmonary Diseases · Rosen's Emergency Medicine · Murray & Nadel · Grainger & Allison · Miller's Anesthesia"),
]
def legend_cell(label, text):
s_lbl = ParagraphStyle("_ll", fontSize=7, fontName="Helvetica-Bold",
textColor=C_NAVY, leading=10)
s_txt = ParagraphStyle("_lt", fontSize=7, fontName="Helvetica",
textColor=colors.HexColor("#222222"), leading=10)
return [Paragraph(label, s_lbl), Paragraph(text, s_txt)]
legend_rows = [legend_cell(lbl, txt) for lbl, txt in legend_items]
# Two columns of legend items
half = len(legend_rows) // 2 + len(legend_rows) % 2
col1_rows = legend_rows[:half]
col2_rows = legend_rows[half:]
def flat_legend_col(items):
"""Flatten legend items into a single-column 2-col table"""
rows = [[lbl, txt] for lbl, txt in items]
t = Table(rows, colWidths=[2.8*cm, 9.5*cm])
t.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 1),
("BOTTOMPADDING", (0,0), (-1,-1), 1),
("LEFTPADDING", (0,0), (-1,-1), 2),
("RIGHTPADDING", (0,0), (-1,-1), 2),
]))
return t
legend_outer = Table(
[[flat_legend_col(col1_rows), flat_legend_col(col2_rows)]],
colWidths=[(USABLE/2)-4, (USABLE/2)-4]
)
legend_outer.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#EEF3F9")),
("BOX", (0,0), (-1,-1), 0.5, C_BLUE),
("LINEAFTER", (0,0), (0,-1), 0.5, C_BLUE),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
]))
legend_header = Table(
[[hdr(" KEY · DEFINITIONS · SOURCES", C_BLUE, size=8)]],
colWidths=[USABLE]
)
legend_header.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_BLUE),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
]))
# ────────────────────────────────────────────────────────────────────────────
# BUILD DOCUMENT
# ────────────────────────────────────────────────────────────────────────────
elements = [
header_table,
subtitle_table,
SP(5),
main_table,
SP(6),
legend_header,
legend_outer,
]
doc.build(elements)
print(f"PDF created: {OUTPUT}")
print(f"File size: {os.path.getsize(OUTPUT):,} bytes")
cd /tmp/workspace/lus-quickref && python3 generate_lus_pdf.py 2>&1
python3 -c " import pdfplumber with pdfplumber.open('/tmp/workspace/lus-quickref/LUS_Quick_Reference.pdf') as pdf: print(f'Pages: {len(pdf.pages)}') page = pdf.pages[0] print(f'Page size: {round(page.width/72*2.54,1)} x {round(page.height/72*2.54,1)} cm (landscape A4 = ~29.7 x 21 cm)') text = page.extract_text() print('--- First 400 chars ---') print(text[:400] if text else '(no text extracted)') "
LUS Quick Reference
PDF Document · PDF
| Row | What's compared |
|---|---|
| Lung Sliding | Present vs. absent, with NPV note for PTX |
| A-Lines | Role in each pattern |
| B-Lines | Focal/regional vs. diffuse bilateral vs. absent |
| Pleural Line | Irregular + fragmented vs. smooth vs. stationary |
| Consolidation | Hepatization + air bronchograms vs. absent vs. absent |
| Air Bronchograms | Static/dynamic vs. absent vs. absent |
| Pleural Effusion | Parapneumonic vs. bilateral anechoic vs. absent |
| M-Mode Pattern | Seashore loss vs. intact vs. barcode/stratosphere |
| Distribution | Focal/lobar vs. bilateral symmetric vs. anterior-first |
| Lung Point | Absent vs. absent vs. 100% specific |
| Key Differentiator | Summary diagnostic fingerprint per condition |
| BLUE Protocol Profile | C/A-B profile vs. B-profile vs. A + absent sliding |
| Performance vs. CXR | Sensitivity/specificity from meta-analyses |