Ventilator scalars

Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Searching the Web

ventilator scalars pressure flow volume time waveforms interpretation

Finding Sources
Reading Web Page

https://pmc.ncbi.nlm.nih.gov/articles/PMC7782574

Finding Sources
Reading Web Page

https://indiachest.org/wp-content/uploads/2019/07/Scalars-an…

Here is a thorough breakdown of ventilator scalars:

Ventilator Scalars

Scalars are real-time graphical displays where a respiratory variable (pressure, flow, or volume) is plotted on the y-axis against time on the x-axis. Each scalar represents one complete breath cycle - from the start of inspiration through the end of expiration. Ventilators measure airway pressure and airway flow directly; volume is a derived (calculated) value from the flow signal.

The Three Scalars

1. Flow-Time (Flow vs. Time) Scalar

Baseline convention: Flow above the baseline = inspiration; flow below = expiration.
Inspiratory arm (active - machine-determined):
ModeInspiratory Flow Shape
Volume Control (VC)Square/rectangular wave - constant fixed flow throughout inspiration
Pressure Control (PC)Decelerating ramp - rapid initial flow that tapers as pressure target is reached
Pressure Support (PS)Decelerating ramp - similar to PC, cycles off when flow drops to a set threshold
Expiratory arm (passive - patient-determined):
  • Shaped by elastic recoil of lungs/chest wall and airway resistance
  • Normally returns smoothly to zero baseline before the next breath
Key abnormalities on the flow-time scalar:
  • Air trapping / auto-PEEP: Expiratory flow does not return to zero before the next breath begins - the tracing is "cut off" above the baseline
  • Increased airway resistance (bronchospasm, secretions): Prolonged, scooped-out expiratory curve; peak expiratory flow is reduced
  • Flow starvation (asynchrony): A notch or "scalloping" seen on the inspiratory flow plateau in VC mode, indicating the patient is trying to pull more flow than the ventilator is delivering
  • Air leak: Asymmetry between inspiratory and expiratory flow areas; expiratory flow may be less than inspiratory flow

2. Pressure-Time (Pressure vs. Time) Scalar

Normal morphology in Volume Control with a square-wave flow pattern:
  1. Initial sharp rise - pressure to overcome airway resistance (R) at the onset of flow delivery
  2. Linear rise - pressure climbs as volume fills the lungs (proportional to elastance)
  3. Peak inspiratory pressure (PIP) - the highest pressure during breath delivery
  4. Pressure drop at end-inspiration (inspiratory pause) - pressure falls from PIP to plateau pressure (Pplat); the difference = resistive pressure (Pres = PIP - Pplat)
  5. Return to PEEP during expiration
Key formula: Paw = (Flow × Resistance) + (Volume / Compliance) + PEEP
Key abnormalities:
  • Increased PIP with normal Pplat → increased airway resistance (bronchospasm, secretion, kinked ETT)
  • Increased PIP AND increased Pplat → decreased compliance (ARDS, pneumothorax, pulmonary edema, abdominal compartment syndrome)
  • Negative deflection at start of breath → patient-triggered breath (the patient's inspiratory effort creates a brief negative pressure dip before the ventilator delivers flow)
  • Double-triggering: Two pressure peaks in rapid succession from one patient effort
  • Flow starvation: The pressure-time scalar shows a concave "scooped" appearance on the inspiratory plateau, indicating the patient is actively pulling against an inadequate flow delivery
  • Stress index (in VC at constant flow):
    • Straight upslope = normal compliance
    • Upward concavity (accelerating pressure rise) = overdistension / alveolar over-recruitment
    • Downward concavity (decelerating pressure rise) = recruitable lung / atelectasis

3. Volume-Time (Volume vs. Time) Scalar

  • The upslope represents inspiratory volume delivery
  • The downslope returns to zero as gas exhales
  • Inspiratory and expiratory volumes should be equal (symmetric curve)
Key abnormalities:
  • Air leak (cuff leak, circuit disconnect): Expiratory volume is less than inspiratory volume; the tracing does not return fully to zero OR inspiratory volume is reduced
  • Auto-PEEP / air trapping: Expiratory limb plateaus and never returns to the zero baseline before the next breath begins
  • Low compliance: Steeper, narrower volume curve

Waveform Patterns by Ventilator Mode

ModePressure-TimeFlow-TimeVolume-Time
Volume Control (VC-AC)Variable (reflects compliance/resistance) - monitor Ppeak & PplatSquare wave (constant flow) inspiratory; passive expirationConsistent Vt; check for leak or trapping
Pressure Control (PC-AC)Square/rectangular - pressure is the independent variableDecelerating ramp; Vt varies with complianceVariable Vt - monitor for changes in lung mechanics
Pressure Support (PS/CPAP)Pressure rises to set support level, patient-cycledDecelerating; cycles off at flow thresholdVariable Vt; RR patient-determined

Clinical Diagnoses Made with Scalars

Finding on ScalarLikely Problem
Expiratory flow doesn't return to zeroAuto-PEEP / air trapping
High Ppeak, normal PplatAirway resistance problem (bronchospasm, secretions, kinked tube)
High Ppeak AND high PplatCompliance problem (ARDS, pneumothorax, edema)
Inspiratory/expiratory volume mismatchAir leak (cuff, circuit)
Scalloped/concave inspiratory pressure curveFlow starvation (asynchrony)
Upward concavity of pressure-time slope (stress index)Overdistension risk
Two pressure peaks per patient effortDouble-triggering asynchrony
Negative pressure deflection without delivered breathAuto-triggering or missed trigger
Distorted flow at onset of expirationLate cycling (pressure support too high)
Abrupt reversal of expiratory flow toward zeroEarly cycling (patient's effort outlasts ventilator Ti)

Summary

  • Flow-time: Most useful for detecting air trapping, obstruction, and flow asynchrony
  • Pressure-time: Most useful for assessing compliance vs. resistance, overdistension (stress index), and patient-ventilator triggering
  • Volume-time: Most useful for detecting air leaks and confirming Vt delivery
Together with the two loops (pressure-volume and flow-volume), scalars give a complete real-time picture of patient-ventilator interaction and respiratory mechanics. A key reference for this topic is the PMC review on ventilator waveform basics, which covers pediatric and adult applications in depth.

Make a pdf of this

Reading File
Running Command

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

Writing File

~/ventilator-scalars/create_pdf.py

from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER

OUTPUT = "/home/daytona/workspace/ventilator-scalars/Ventilator_Scalars.pdf"

doc = SimpleDocTemplate(
    OUTPUT,
    pagesize=A4,
    leftMargin=2*cm, rightMargin=2*cm,
    topMargin=2*cm, bottomMargin=2*cm
)

styles = getSampleStyleSheet()

# Custom styles
title_style = ParagraphStyle(
    'CustomTitle',
    parent=styles['Title'],
    fontSize=22,
    textColor=colors.HexColor('#1a3a5c'),
    spaceAfter=6,
    spaceBefore=0,
    fontName='Helvetica-Bold',
)
h1_style = ParagraphStyle(
    'H1',
    parent=styles['Heading1'],
    fontSize=15,
    textColor=colors.HexColor('#1a3a5c'),
    spaceBefore=14,
    spaceAfter=4,
    fontName='Helvetica-Bold',
    borderPad=2,
)
h2_style = ParagraphStyle(
    'H2',
    parent=styles['Heading2'],
    fontSize=12,
    textColor=colors.HexColor('#2e6da4'),
    spaceBefore=10,
    spaceAfter=3,
    fontName='Helvetica-Bold',
)
h3_style = ParagraphStyle(
    'H3',
    parent=styles['Heading3'],
    fontSize=11,
    textColor=colors.HexColor('#444444'),
    spaceBefore=8,
    spaceAfter=2,
    fontName='Helvetica-BoldOblique',
)
body_style = ParagraphStyle(
    'Body',
    parent=styles['Normal'],
    fontSize=10,
    leading=15,
    spaceAfter=4,
    fontName='Helvetica',
)
bullet_style = ParagraphStyle(
    'Bullet',
    parent=body_style,
    leftIndent=18,
    firstLineIndent=0,
    spaceAfter=3,
    bulletIndent=6,
)
formula_style = ParagraphStyle(
    'Formula',
    parent=body_style,
    fontName='Helvetica-BoldOblique',
    fontSize=10,
    textColor=colors.HexColor('#1a3a5c'),
    backColor=colors.HexColor('#eef4fb'),
    borderPad=5,
    leftIndent=20,
    spaceAfter=6,
)

def b(t): return f"<b>{t}</b>"
def i(t): return f"<i>{t}</i>"
def bi(t): return f"<b><i>{t}</i></b>"

# Table style helper
def make_table_style(header_color=colors.HexColor('#1a3a5c')):
    return TableStyle([
        ('BACKGROUND', (0, 0), (-1, 0), header_color),
        ('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
        ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
        ('FONTSIZE', (0, 0), (-1, 0), 10),
        ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
        ('VALIGN', (0, 0), (-1, -1), 'TOP'),
        ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
        ('FONTSIZE', (0, 1), (-1, -1), 9),
        ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.HexColor('#f5f9ff'), colors.white]),
        ('GRID', (0, 0), (-1, -1), 0.4, colors.HexColor('#bbccdd')),
        ('TOPPADDING', (0, 0), (-1, -1), 5),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 5),
        ('LEFTPADDING', (0, 0), (-1, -1), 6),
        ('RIGHTPADDING', (0, 0), (-1, -1), 6),
    ])

story = []

# ── Title ─────────────────────────────────────────────────────────────────────
story.append(Paragraph("Ventilator Scalars", title_style))
story.append(Paragraph(
    "A clinical guide to pressure, flow, and volume waveforms in mechanical ventilation",
    ParagraphStyle('subtitle', parent=body_style, fontSize=11,
                   textColor=colors.HexColor('#555555'), spaceAfter=4)
))
story.append(HRFlowable(width="100%", thickness=2, color=colors.HexColor('#1a3a5c'), spaceAfter=10))

# ── Introduction ──────────────────────────────────────────────────────────────
story.append(Paragraph("Overview", h1_style))
story.append(Paragraph(
    "Scalars are real-time graphical displays where a respiratory variable is plotted on the "
    "<b>y-axis</b> against <b>time on the x-axis</b>. Each scalar represents one complete breath "
    "cycle — from the start of inspiration through the end of expiration. "
    "Ventilators <i>measure</i> airway pressure and airway flow directly; "
    "volume is a <i>derived</i> (calculated) value from the flow signal.",
    body_style
))
story.append(Paragraph(
    "The three scalars are: <b>Flow-Time</b>, <b>Pressure-Time</b>, and <b>Volume-Time</b>. "
    "Together with the two loops (pressure-volume and flow-volume), they provide a complete "
    "real-time picture of patient-ventilator interaction and respiratory mechanics.",
    body_style
))

# ── SCALAR 1: Flow-Time ────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#bbccdd'), spaceAfter=6))
story.append(Paragraph("1. Flow-Time Scalar", h1_style))
story.append(Paragraph(
    "<b>Baseline convention:</b> Flow above baseline = inspiration; flow below baseline = expiration.",
    body_style
))

story.append(Paragraph("Inspiratory Arm (Active — machine-determined)", h2_style))
flow_insp_data = [
    ["Mode", "Inspiratory Flow Shape"],
    ["Volume Control (VC)", "Square / rectangular wave — constant fixed flow throughout inspiration"],
    ["Pressure Control (PC)", "Decelerating ramp — rapid initial flow that tapers as pressure target is reached"],
    ["Pressure Support (PS)", "Decelerating ramp — cycles off when flow drops to a set % threshold"],
]
story.append(Table(flow_insp_data, colWidths=[5*cm, 11.5*cm], style=make_table_style()))
story.append(Spacer(1, 6))

story.append(Paragraph("Expiratory Arm (Passive — patient-determined)", h2_style))
story.append(Paragraph(
    "Shaped by elastic recoil of lungs/chest wall and airway resistance. "
    "Normally returns smoothly to zero baseline before the next breath.",
    body_style
))

story.append(Paragraph("Key Abnormalities — Flow-Time", h2_style))
flow_abn = [
    ["Abnormality", "Appearance", "Cause"],
    ["Auto-PEEP / Air trapping",
     "Expiratory flow cut off above zero before next breath",
     "Incomplete exhalation; obstructive disease"],
    ["Increased airway resistance",
     "Prolonged, scooped expiratory curve; low peak expiratory flow",
     "Bronchospasm, secretions, kinked ETT"],
    ["Flow starvation",
     "Notch / scalloping on inspiratory flow plateau (VC mode)",
     "Patient pulling more flow than ventilator delivers"],
    ["Air leak",
     "Inspiratory > expiratory flow area; asymmetric curve",
     "Cuff leak, circuit disconnect"],
]
story.append(Table(flow_abn, colWidths=[4.5*cm, 6*cm, 6*cm], style=make_table_style()))

# ── SCALAR 2: Pressure-Time ────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#bbccdd'), spaceAfter=6))
story.append(Paragraph("2. Pressure-Time Scalar", h1_style))
story.append(Paragraph(
    "Normal morphology in <b>Volume Control with a square-wave (constant) flow pattern:</b>",
    body_style
))

ptime_phases = [
    ["Phase", "Description"],
    ["1. Initial sharp rise", "Pressure overcomes airway resistance at onset of flow delivery"],
    ["2. Linear rise", "Pressure climbs as volume fills the lungs (proportional to elastance)"],
    ["3. Peak Inspiratory Pressure (PIP)", "Highest pressure reached during breath delivery"],
    ["4. End-inspiratory pressure drop",
     "Pressure falls from PIP → Plateau pressure (Pplat) during an inspiratory pause; "
     "difference = Resistive pressure (Pres = PIP − Pplat)"],
    ["5. Return to PEEP", "Passive expiration; pressure returns to set PEEP level"],
]
story.append(Table(ptime_phases, colWidths=[5.5*cm, 11*cm], style=make_table_style()))
story.append(Spacer(1, 6))

story.append(Paragraph("Key Formula", h2_style))
story.append(Paragraph(
    "Paw  =  (Flow × Resistance)  +  (Volume / Compliance)  +  PEEP",
    formula_style
))

story.append(Paragraph("Key Abnormalities — Pressure-Time", h2_style))
pt_abn = [
    ["Finding", "Interpretation"],
    ["High PIP, normal Pplat", "Airway resistance problem (bronchospasm, secretions, kinked ETT)"],
    ["High PIP AND high Pplat", "Compliance problem (ARDS, pneumothorax, pulmonary edema, ACS)"],
    ["Negative deflection at breath start", "Patient-triggered breath (effort dip before flow delivery)"],
    ["Double pressure peaks per effort", "Double-triggering asynchrony"],
    ["Concave/scooped inspiratory plateau", "Flow starvation — patient effort exceeds set flow"],
    ["Upward concavity of pressure slope\n(Stress Index > 1)",
     "Overdistension risk — pressure accelerates faster than expected"],
    ["Downward concavity\n(Stress Index < 1)",
     "Recruitable lung / atelectasis — pressure decelerates (compliance improving)"],
    ["Straight upslope (Stress Index = 1)", "Normal compliance throughout breath"],
]
story.append(Table(pt_abn, colWidths=[7*cm, 9.5*cm], style=make_table_style()))

# ── SCALAR 3: Volume-Time ──────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#bbccdd'), spaceAfter=6))
story.append(Paragraph("3. Volume-Time Scalar", h1_style))
story.append(Paragraph(
    "The upslope represents inspiratory volume delivery; the downslope returns to zero as gas exhales. "
    "Inspiratory and expiratory volumes should be <b>equal</b> (symmetric curve). "
    "Volume is <i>calculated</i> from the flow signal, not directly measured.",
    body_style
))

story.append(Paragraph("Key Abnormalities — Volume-Time", h2_style))
vt_abn = [
    ["Finding", "Interpretation"],
    ["Expiratory volume < inspiratory volume\n(curve doesn't return to zero)",
     "Air leak (cuff leak, circuit disconnect, bronchopleural fistula)"],
    ["Expiratory limb plateaus above zero", "Auto-PEEP / air trapping"],
    ["Steep narrow curve", "Low compliance (stiff lungs)"],
    ["Reduced inspiratory volume", "Circuit leak or inadequate flow delivery"],
]
story.append(Table(vt_abn, colWidths=[7.5*cm, 9*cm], style=make_table_style()))

# ── Mode-based waveform summary ────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#bbccdd'), spaceAfter=6))
story.append(Paragraph("Waveform Patterns by Ventilator Mode", h1_style))

mode_data = [
    ["Mode", "Pressure-Time", "Flow-Time", "Volume-Time"],
    ["Volume Control\n(VC-AC)",
     "Variable — reflects compliance & resistance; monitor Ppeak & Pplat",
     "Square wave (constant flow) on inspiration; passive expiration",
     "Consistent Vt; check for leak or trapping"],
    ["Pressure Control\n(PC-AC)",
     "Rectangular — pressure is the independent variable",
     "Decelerating ramp; Vt varies with lung compliance",
     "Variable Vt — monitor for changes in mechanics"],
    ["Pressure Support\n(PS/CPAP)",
     "Rises to set support level; patient-cycled",
     "Decelerating; cycles off at flow threshold (e.g. 25% of peak)",
     "Variable Vt; RR is patient-determined"],
]
story.append(Table(mode_data, colWidths=[3.5*cm, 5.5*cm, 5.5*cm, 4*cm], style=make_table_style()))

# ── Clinical diagnosis quick-reference ────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#bbccdd'), spaceAfter=6))
story.append(Paragraph("Clinical Diagnosis Quick Reference", h1_style))

diag_data = [
    ["Scalar Finding", "Likely Problem"],
    ["Expiratory flow does not return to zero (flow-time)", "Auto-PEEP / air trapping"],
    ["High Ppeak, normal Pplat (pressure-time)", "Airway resistance (bronchospasm, secretions, kinked ETT)"],
    ["High Ppeak AND high Pplat (pressure-time)", "Low compliance (ARDS, pneumothorax, pulmonary edema)"],
    ["Insp > exp volume mismatch (volume-time)", "Air leak (cuff, circuit, bronchopleural fistula)"],
    ["Scooped/concave inspiratory pressure (pressure-time)", "Flow starvation asynchrony"],
    ["Upward concavity of pressure slope (pressure-time)", "Overdistension (stress index > 1)"],
    ["Two pressure peaks per patient effort (pressure-time)", "Double-triggering asynchrony"],
    ["Negative pressure dip without breath delivery", "Auto-triggering or missed trigger"],
    ["Distorted flow at start of expiration (flow-time in PS)", "Late cycling — PS level too high"],
    ["Abrupt reversal of expiratory flow toward zero (flow-time)", "Early cycling — Ti shorter than patient's neural Ti"],
]
story.append(Table(diag_data, colWidths=[8*cm, 8.5*cm], style=make_table_style()))

# ── Summary ────────────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#bbccdd'), spaceAfter=6))
story.append(Paragraph("Summary", h1_style))

summary_data = [
    ["Scalar", "Primary Clinical Use"],
    ["Flow-Time",
     "Detecting air trapping (auto-PEEP), airway obstruction, flow asynchrony, and air leaks"],
    ["Pressure-Time",
     "Assessing compliance vs. resistance, overdistension (stress index), triggering quality, and Ppeak/Pplat analysis"],
    ["Volume-Time",
     "Detecting air leaks, confirming Vt delivery, and identifying air trapping"],
]
story.append(Table(summary_data, colWidths=[4*cm, 12.5*cm], style=make_table_style()))
story.append(Spacer(1, 8))
story.append(Paragraph(
    "Key reference: Bhatt JM et al., <i>The Basics of Ventilator Waveforms</i>, "
    "Curr Treat Options Pediatr. 2021 — PMC7782574",
    ParagraphStyle('ref', parent=body_style, fontSize=9, textColor=colors.HexColor('#666666'))
))

# ── Build ──────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF created: {OUTPUT}")
Running Command

python /home/daytona/workspace/ventilator-scalars/create_pdf.py

Your PDF is ready:

Ventilator Scalars

PDF Document · PDF

It includes all sections from the previous response - overview, all three scalars (flow-time, pressure-time, volume-time) with their abnormalities, waveform patterns by ventilator mode, a clinical diagnosis quick-reference table, and a summary - formatted with color-coded headers and tables for easy reading.

Need a diagrams

Asking for Preferences
Writing File

~/ventilator-scalars/draw_diagrams.py

"""
Draw ventilator scalar diagrams and save as PNG images.
Three panels per scalar:
  Flow-Time:     VC square wave | PC decelerating | abnormal (air trapping)
  Pressure-Time: VC normal      | PC normal       | abnormal (high resistance vs low compliance)
  Volume-Time:   normal         | air leak        | air trapping
"""

import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.lines import Line2D
import os

OUT = "/home/daytona/workspace/ventilator-scalars/img"
os.makedirs(OUT, exist_ok=True)

BLUE   = '#1a3a5c'
RED    = '#c0392b'
GREEN  = '#27ae60'
ORANGE = '#e67e22'
GREY   = '#888888'
LBLUE  = '#2e86de'
BG     = '#f8fbff'

# ─────────────────────────────────────────────────────────────────────────────
# Helper
# ─────────────────────────────────────────────────────────────────────────────
def new_fig(ncols=3, title=""):
    fig, axes = plt.subplots(1, ncols, figsize=(14, 4.2), facecolor='white')
    fig.suptitle(title, fontsize=14, fontweight='bold', color=BLUE, y=1.01)
    for ax in axes:
        ax.set_facecolor(BG)
        ax.spines['top'].set_visible(False)
        ax.spines['right'].set_visible(False)
        ax.spines['left'].set_color('#aaaaaa')
        ax.spines['bottom'].set_color('#aaaaaa')
        ax.tick_params(colors='#555555', labelsize=8)
    return fig, axes

def save(fig, name):
    path = f"{OUT}/{name}.png"
    fig.tight_layout(pad=1.5)
    fig.savefig(path, dpi=150, bbox_inches='tight', facecolor='white')
    plt.close(fig)
    print(f"  Saved: {path}")
    return path

def axis_labels(ax, xlabel="Time (s)", ylabel="", zero_line=True):
    ax.set_xlabel(xlabel, fontsize=8, color='#555555')
    ax.set_ylabel(ylabel, fontsize=8, color='#555555')
    if zero_line:
        ax.axhline(0, color='#999999', linewidth=0.8, linestyle='--')

def annotate(ax, x, y, text, color=BLUE, fontsize=8, xytext=(0,10)):
    ax.annotate(text, xy=(x, y), xytext=xytext,
                textcoords='offset points', fontsize=fontsize,
                color=color, fontweight='bold',
                arrowprops=dict(arrowstyle='->', color=color, lw=1.0))

# ─────────────────────────────────────────────────────────────────────────────
# 1. FLOW-TIME SCALAR
# ─────────────────────────────────────────────────────────────────────────────
def flow_time():
    fig, axes = new_fig(3, "Flow-Time Scalar")
    t = np.linspace(0, 6, 1000)

    def breath_vc(t_start, flow_insp=0.5, t_insp=0.8, t_exp=1.2):
        """Square wave VC breath"""
        seg = []
        # insp: constant flow
        ti = np.linspace(0, t_insp, 200)
        seg.append((ti + t_start, np.full_like(ti, flow_insp)))
        # exp: passive exponential decay
        te = np.linspace(0, t_exp, 200)
        exp_flow = -flow_insp * 1.3 * np.exp(-te / 0.35)
        seg.append((te + t_start + t_insp, exp_flow))
        return seg

    def breath_pc(t_start, peak=0.6, t_insp=0.9, t_exp=1.1):
        """Decelerating ramp PC breath"""
        seg = []
        ti = np.linspace(0, t_insp, 200)
        insp = peak * np.exp(-ti / 0.25)
        seg.append((ti + t_start, insp))
        te = np.linspace(0, t_exp, 200)
        exp = -peak * 0.9 * np.exp(-te / 0.32)
        seg.append((te + t_start + t_insp, exp))
        return seg

    # Panel A: VC square wave
    ax = axes[0]
    ax.set_title("Volume Control\n(Square Wave)", fontsize=9, color=BLUE, fontweight='bold')
    for t_s in [0.2, 2.2, 4.2]:
        for xs, ys in breath_vc(t_s):
            ax.plot(xs, ys, color=LBLUE, lw=2)
    ax.fill_between([0.2, 1.0], 0, 0.5, alpha=0.12, color=LBLUE)
    ax.text(0.6, 0.54, 'Insp', fontsize=7, color=LBLUE, ha='center')
    ax.fill_between([1.0, 2.2], -0.65, 0, alpha=0.10, color=ORANGE)
    ax.text(1.6, -0.70, 'Exp', fontsize=7, color=ORANGE, ha='center')
    ax.set_ylim(-0.85, 0.80)
    ax.set_xlim(0, 6)
    axis_labels(ax, ylabel="Flow (L/s)")
    ax.axhline(0, color='#777777', lw=0.8, ls='--')

    # Panel B: PC decelerating
    ax = axes[1]
    ax.set_title("Pressure Control\n(Decelerating Ramp)", fontsize=9, color=BLUE, fontweight='bold')
    for t_s in [0.2, 2.3, 4.4]:
        for xs, ys in breath_pc(t_s):
            ax.plot(xs, ys, color=GREEN, lw=2)
    ax.set_ylim(-0.85, 0.80)
    ax.set_xlim(0, 6)
    axis_labels(ax, ylabel="Flow (L/s)")
    ax.axhline(0, color='#777777', lw=0.8, ls='--')

    # Panel C: Air trapping (flow doesn't return to zero)
    ax = axes[2]
    ax.set_title("Abnormal: Auto-PEEP\n(Air Trapping)", fontsize=9, color=RED, fontweight='bold')
    def breath_trap(t_start):
        ti = np.linspace(0, 0.8, 200)
        insp = 0.55 * np.exp(-ti / 0.22)
        te = np.linspace(0, 0.9, 200)      # Short exp time
        exp = -0.55 * 0.9 * np.exp(-te / 0.28)
        return [(ti + t_start, insp), (te + t_start + 0.8, exp)]

    for t_s in [0.2, 1.9, 3.6]:
        segs = breath_trap(t_s)
        for xs, ys in segs:
            ax.plot(xs, ys, color=RED, lw=2)

    # Highlight that flow doesn't reach zero
    ax.axhline(-0.12, color=RED, lw=1.0, ls=':', alpha=0.7)
    ax.annotate('Flow never\nreturns to 0', xy=(1.7, -0.12), xytext=(3.0, -0.55),
                textcoords='data', fontsize=7.5, color=RED, fontweight='bold',
                arrowprops=dict(arrowstyle='->', color=RED, lw=1.0))
    ax.set_ylim(-0.85, 0.80)
    ax.set_xlim(0, 5.2)
    axis_labels(ax, ylabel="Flow (L/s)")
    ax.axhline(0, color='#777777', lw=0.8, ls='--')

    return save(fig, "flow_time")


# ─────────────────────────────────────────────────────────────────────────────
# 2. PRESSURE-TIME SCALAR
# ─────────────────────────────────────────────────────────────────────────────
def pressure_time():
    fig, axes = new_fig(3, "Pressure-Time Scalar")

    # --- Normal VC breath builder ---
    def vc_breath(t0, peep=5, pip=25, pplat=20, t_insp=0.8, t_pause=0.2, t_exp=1.2):
        t_list, p_list = [], []
        # rise to PIP
        t1 = np.linspace(0, t_insp, 150)
        p1 = peep + (pip - peep) * (t1 / t_insp)
        t_list.append(t1 + t0); p_list.append(p1)
        # pause at pplat
        t2 = np.linspace(0, t_pause, 50)
        p2 = np.linspace(pip, pplat, 50)
        t2b = np.full(50, pplat)
        t_list.append(t2 + t0 + t_insp); p_list.append(p2)
        # expiration decay back to PEEP
        t3 = np.linspace(0, t_exp, 150)
        p3 = peep + (pplat - peep) * np.exp(-t3 / 0.4)
        t_list.append(t3 + t0 + t_insp + t_pause); p_list.append(p3)
        return t_list, p_list

    def pc_breath(t0, peep=5, pc_level=20, t_insp=0.9, t_exp=1.1):
        t_list, p_list = [], []
        # rapid rise to PC level
        t1 = np.linspace(0, 0.1, 40)
        p1 = np.linspace(peep, pc_level, 40)
        t_list.append(t1 + t0); p_list.append(p1)
        # hold at PC level
        t2 = np.linspace(0, t_insp - 0.1, 100)
        p2 = np.full_like(t2, pc_level)
        t_list.append(t2 + t0 + 0.1); p_list.append(p2)
        # expiration
        t3 = np.linspace(0, t_exp, 150)
        p3 = peep + (pc_level - peep) * np.exp(-t3 / 0.35)
        t_list.append(t3 + t0 + t_insp); p_list.append(p3)
        return t_list, p_list

    # Panel A: Normal VC
    ax = axes[0]
    ax.set_title("Volume Control — Normal\n(with inspiratory pause)", fontsize=9, color=BLUE, fontweight='bold')
    for t_s in [0.3, 2.6, 4.9]:
        tl, pl = vc_breath(t_s)
        for x, y in zip(tl, pl):
            ax.plot(x, y, color=LBLUE, lw=2)

    # Annotate one breath
    ax.annotate('PIP', xy=(1.1, 25), xytext=(1.5, 28),
                textcoords='data', fontsize=8, color=BLUE, fontweight='bold',
                arrowprops=dict(arrowstyle='->', color=BLUE, lw=0.9))
    ax.annotate('Pplat', xy=(1.45, 20), xytext=(1.9, 22),
                textcoords='data', fontsize=8, color=GREEN, fontweight='bold',
                arrowprops=dict(arrowstyle='->', color=GREEN, lw=0.9))
    ax.axhline(5, color=GREY, lw=0.9, ls=':', alpha=0.7)
    ax.text(0.05, 5.8, 'PEEP', fontsize=7, color=GREY)
    ax.set_ylim(0, 35)
    ax.set_xlim(0, 6.8)
    axis_labels(ax, ylabel="Pressure (cmH₂O)", zero_line=False)

    # Panel B: PC (rectangular)
    ax = axes[1]
    ax.set_title("Pressure Control — Normal\n(Square pressure wave)", fontsize=9, color=BLUE, fontweight='bold')
    for t_s in [0.3, 2.6, 4.9]:
        tl, pl = pc_breath(t_s)
        for x, y in zip(tl, pl):
            ax.plot(x, y, color=GREEN, lw=2)
    ax.axhline(5, color=GREY, lw=0.9, ls=':', alpha=0.7)
    ax.text(0.05, 5.8, 'PEEP', fontsize=7, color=GREY)
    ax.annotate('PC level\n(constant)', xy=(1.0, 20), xytext=(2.5, 26),
                textcoords='data', fontsize=8, color=GREEN, fontweight='bold',
                arrowprops=dict(arrowstyle='->', color=GREEN, lw=0.9))
    ax.set_ylim(0, 35)
    ax.set_xlim(0, 6.8)
    axis_labels(ax, ylabel="Pressure (cmH₂O)", zero_line=False)

    # Panel C: Abnormal — high resistance (high PIP, normal Pplat) vs low compliance overlay
    ax = axes[2]
    ax.set_title("Abnormal: High Resistance\nvs Low Compliance", fontsize=9, color=RED, fontweight='bold')

    # Normal reference (grey)
    tl, pl = vc_breath(0.3, peep=5, pip=22, pplat=18)
    for x, y in zip(tl, pl):
        ax.plot(x, y, color=GREY, lw=1.5, ls='--', alpha=0.5)

    # High resistance: high PIP, normal Pplat (wide PIP-Pplat gap)
    tl, pl = vc_breath(0.3, peep=5, pip=38, pplat=18)
    for x, y in zip(tl, pl):
        ax.plot(x, y, color=ORANGE, lw=2)
    ax.annotate('↑ PIP\n(resistance)', xy=(1.1, 38), xytext=(1.6, 40),
                textcoords='data', fontsize=7.5, color=ORANGE, fontweight='bold',
                arrowprops=dict(arrowstyle='->', color=ORANGE, lw=0.9))

    # Low compliance: both PIP and Pplat elevated
    tl2, pl2 = vc_breath(3.8, peep=5, pip=40, pplat=34)
    for x, y in zip(tl2, pl2):
        ax.plot(x, y, color=RED, lw=2)
    ax.annotate('↑ PIP + ↑ Pplat\n(compliance)', xy=(4.9, 40), xytext=(5.2, 43),
                textcoords='data', fontsize=7.5, color=RED, fontweight='bold',
                arrowprops=dict(arrowstyle='->', color=RED, lw=0.9))

    # Legend
    legend_els = [
        Line2D([0],[0], color=GREY, lw=1.5, ls='--', label='Normal (reference)'),
        Line2D([0],[0], color=ORANGE, lw=2, label='High resistance'),
        Line2D([0],[0], color=RED, lw=2, label='Low compliance'),
    ]
    ax.legend(handles=legend_els, fontsize=7, loc='lower right')
    ax.axhline(5, color=GREY, lw=0.9, ls=':', alpha=0.7)
    ax.set_ylim(0, 50)
    ax.set_xlim(0, 6.8)
    axis_labels(ax, ylabel="Pressure (cmH₂O)", zero_line=False)

    return save(fig, "pressure_time")


# ─────────────────────────────────────────────────────────────────────────────
# 3. VOLUME-TIME SCALAR
# ─────────────────────────────────────────────────────────────────────────────
def volume_time():
    fig, axes = new_fig(3, "Volume-Time Scalar")

    def normal_breath(t0, vt=500, t_insp=0.8, t_exp=1.4):
        ti = np.linspace(0, t_insp, 150)
        vi = vt * (ti / t_insp)
        te = np.linspace(0, t_exp, 200)
        ve = vt * np.exp(-te / 0.45)
        return [(ti + t0, vi), (te + t0 + t_insp, ve)]

    def leak_breath(t0, vt=500, t_insp=0.8, t_exp=1.4, leak_frac=0.25):
        """Expiratory vol less than inspiratory — air leak"""
        ti = np.linspace(0, t_insp, 150)
        vi = vt * (ti / t_insp)
        te = np.linspace(0, t_exp, 200)
        ve_end = vt * (1 - leak_frac)  # doesn't fully return
        ve = ve_end * np.exp(-te / 0.45)
        return [(ti + t0, vi), (te + t0 + t_insp, ve)]

    def trap_breath(t0, vt=500, t_insp=0.8, t_exp=0.9, trapped=80):
        """Volume doesn't return to zero — air trapping"""
        ti = np.linspace(0, t_insp, 150)
        vi = vt * (ti / t_insp)
        te = np.linspace(0, t_exp, 200)
        ve = trapped + (vt - trapped) * np.exp(-te / 0.38)
        return [(ti + t0, vi), (te + t0 + t_insp, ve)]

    # Panel A: Normal
    ax = axes[0]
    ax.set_title("Normal Volume-Time\n(Insp = Exp Volume)", fontsize=9, color=BLUE, fontweight='bold')
    for t_s in [0.2, 2.2, 4.2]:
        for xs, ys in normal_breath(t_s):
            ax.plot(xs, ys, color=LBLUE, lw=2)
    ax.fill_between([0.2, 1.0], 0, 500, alpha=0.10, color=LBLUE)
    ax.text(0.6, 520, 'Insp\nVt', fontsize=7, color=LBLUE, ha='center')
    ax.axhline(0, color='#777777', lw=0.8, ls='--')
    ax.set_ylim(-50, 650)
    ax.set_xlim(0, 6)
    axis_labels(ax, ylabel="Volume (mL)", zero_line=False)

    # Panel B: Air leak
    ax = axes[1]
    ax.set_title("Abnormal: Air Leak\n(Exp Vol < Insp Vol)", fontsize=9, color=RED, fontweight='bold')
    for t_s in [0.2, 2.2, 4.2]:
        for xs, ys in leak_breath(t_s):
            ax.plot(xs, ys, color=RED, lw=2)
    # Annotate gap
    ax.annotate('', xy=(1.15, 375), xytext=(1.15, 0),
                arrowprops=dict(arrowstyle='<->', color=RED, lw=1.2))
    ax.text(1.25, 175, 'Leaked\nvolume', fontsize=7.5, color=RED, fontweight='bold')
    ax.annotate('Exp curve\nstops here', xy=(2.1, 375), xytext=(2.8, 480),
                textcoords='data', fontsize=7.5, color=RED, fontweight='bold',
                arrowprops=dict(arrowstyle='->', color=RED, lw=0.9))
    ax.axhline(0, color='#777777', lw=0.8, ls='--')
    ax.set_ylim(-50, 650)
    ax.set_xlim(0, 6)
    axis_labels(ax, ylabel="Volume (mL)", zero_line=False)

    # Panel C: Air trapping
    ax = axes[2]
    ax.set_title("Abnormal: Auto-PEEP\n(Vol never returns to 0)", fontsize=9, color=ORANGE, fontweight='bold')
    for t_s in [0.2, 1.9, 3.6]:
        for xs, ys in trap_breath(t_s):
            ax.plot(xs, ys, color=ORANGE, lw=2)
    ax.axhline(80, color=ORANGE, lw=1.2, ls=':', alpha=0.8)
    ax.text(0.05, 90, 'Trapped\nvolume', fontsize=7.5, color=ORANGE, fontweight='bold')
    ax.annotate('Baseline\nnever = 0', xy=(1.75, 80), xytext=(2.6, 200),
                textcoords='data', fontsize=7.5, color=ORANGE, fontweight='bold',
                arrowprops=dict(arrowstyle='->', color=ORANGE, lw=0.9))
    ax.axhline(0, color='#777777', lw=0.8, ls='--')
    ax.set_ylim(-50, 650)
    ax.set_xlim(0, 5.2)
    axis_labels(ax, ylabel="Volume (mL)", zero_line=False)

    return save(fig, "volume_time")


# Run all
print("Drawing diagrams...")
p1 = flow_time()
p2 = pressure_time()
p3 = volume_time()
print("All diagrams done.")
print(p1, p2, p3)
Running Command

python /home/daytona/workspace/ventilator-scalars/draw_diagrams.py

Writing File

~/ventilator-scalars/build_pdf.py

"""
Rebuild Ventilator_Scalars.pdf with embedded diagrams.
Each scalar section is followed immediately by its diagram.
"""

from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    HRFlowable, Image, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER

OUTPUT = "/home/daytona/workspace/ventilator-scalars/Ventilator_Scalars.pdf"
IMG    = "/home/daytona/workspace/ventilator-scalars/img"

doc = SimpleDocTemplate(
    OUTPUT, pagesize=A4,
    leftMargin=2*cm, rightMargin=2*cm,
    topMargin=2*cm, bottomMargin=2*cm
)

styles = getSampleStyleSheet()

title_style = ParagraphStyle('T', parent=styles['Title'], fontSize=22,
    textColor=colors.HexColor('#1a3a5c'), spaceAfter=4, fontName='Helvetica-Bold')
subtitle_style = ParagraphStyle('Sub', parent=styles['Normal'], fontSize=11,
    textColor=colors.HexColor('#555555'), spaceAfter=4, fontName='Helvetica')
h1_style = ParagraphStyle('H1', parent=styles['Heading1'], fontSize=15,
    textColor=colors.HexColor('#1a3a5c'), spaceBefore=14, spaceAfter=4,
    fontName='Helvetica-Bold')
h2_style = ParagraphStyle('H2', parent=styles['Heading2'], fontSize=12,
    textColor=colors.HexColor('#2e6da4'), spaceBefore=10, spaceAfter=3,
    fontName='Helvetica-Bold')
body_style = ParagraphStyle('B', parent=styles['Normal'], fontSize=10,
    leading=15, spaceAfter=4, fontName='Helvetica')
bullet_style = ParagraphStyle('Bul', parent=body_style, leftIndent=18,
    firstLineIndent=0, spaceAfter=3)
formula_style = ParagraphStyle('Frm', parent=body_style,
    fontName='Helvetica-BoldOblique', fontSize=10,
    textColor=colors.HexColor('#1a3a5c'), backColor=colors.HexColor('#eef4fb'),
    borderPad=5, leftIndent=20, spaceAfter=6)
caption_style = ParagraphStyle('Cap', parent=body_style, fontSize=8.5,
    textColor=colors.HexColor('#555555'), alignment=TA_CENTER,
    spaceAfter=8, fontName='Helvetica-Oblique')

def tbl_style(hdr=colors.HexColor('#1a3a5c')):
    return TableStyle([
        ('BACKGROUND', (0,0), (-1,0), hdr),
        ('TEXTCOLOR', (0,0), (-1,0), colors.white),
        ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
        ('FONTSIZE', (0,0), (-1,0), 10),
        ('ALIGN', (0,0), (-1,-1), 'LEFT'),
        ('VALIGN', (0,0), (-1,-1), 'TOP'),
        ('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
        ('FONTSIZE', (0,1), (-1,-1), 9),
        ('ROWBACKGROUNDS', (0,1), (-1,-1),
         [colors.HexColor('#f5f9ff'), colors.white]),
        ('GRID', (0,0), (-1,-1), 0.4, colors.HexColor('#bbccdd')),
        ('TOPPADDING', (0,0), (-1,-1), 5),
        ('BOTTOMPADDING', (0,0), (-1,-1), 5),
        ('LEFTPADDING', (0,0), (-1,-1), 6),
        ('RIGHTPADDING', (0,0), (-1,-1), 6),
    ])

def diagram(fname, caption):
    """Return image + caption as a list of flowables."""
    img = Image(f"{IMG}/{fname}.png", width=16.5*cm, height=5.0*cm)
    cap = Paragraph(caption, caption_style)
    return [img, cap]

story = []

# ── Title ──────────────────────────────────────────────────────────────────
story.append(Paragraph("Ventilator Scalars", title_style))
story.append(Paragraph(
    "A clinical guide to pressure, flow, and volume waveforms in mechanical ventilation",
    subtitle_style))
story.append(HRFlowable(width="100%", thickness=2,
    color=colors.HexColor('#1a3a5c'), spaceAfter=10))

# ── Overview ───────────────────────────────────────────────────────────────
story.append(Paragraph("Overview", h1_style))
story.append(Paragraph(
    "Scalars are real-time graphical displays where a respiratory variable is plotted on the "
    "<b>y-axis</b> against <b>time on the x-axis</b>. Each scalar represents one complete breath "
    "cycle — from the start of inspiration through the end of expiration. "
    "Ventilators <i>measure</i> airway pressure and airway flow directly; "
    "volume is a <i>derived</i> (calculated) value from the flow signal.", body_style))
story.append(Paragraph(
    "The three scalars are: <b>Flow-Time</b>, <b>Pressure-Time</b>, and <b>Volume-Time</b>. "
    "Together with the two loops (pressure-volume and flow-volume), they provide a complete "
    "real-time picture of patient-ventilator interaction and respiratory mechanics.", body_style))

# ── SCALAR 1: Flow-Time ────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1,
    color=colors.HexColor('#bbccdd'), spaceAfter=6))
story.append(Paragraph("1. Flow-Time Scalar", h1_style))
story.append(Paragraph(
    "<b>Baseline convention:</b> Flow <i>above</i> baseline = inspiration; "
    "flow <i>below</i> baseline = expiration.", body_style))

story.append(Paragraph("Inspiratory Arm (Active — machine-determined)", h2_style))
story.append(Table([
    ["Mode", "Inspiratory Flow Shape"],
    ["Volume Control (VC)",
     "Square / rectangular wave — constant fixed flow throughout inspiration"],
    ["Pressure Control (PC)",
     "Decelerating ramp — rapid initial flow that tapers as pressure target is reached"],
    ["Pressure Support (PS)",
     "Decelerating ramp — cycles off when flow drops to a set % threshold"],
], colWidths=[5*cm, 11.5*cm], style=tbl_style()))
story.append(Spacer(1, 4))

story.append(Paragraph("Expiratory Arm (Passive — patient-determined)", h2_style))
story.append(Paragraph(
    "Shaped by elastic recoil of lungs/chest wall and airway resistance. "
    "Normally returns smoothly to zero baseline before the next breath.", body_style))

story.append(Paragraph("Key Abnormalities", h2_style))
story.append(Table([
    ["Abnormality", "Appearance", "Cause"],
    ["Auto-PEEP / Air trapping",
     "Expiratory flow cut off above zero before next breath",
     "Incomplete exhalation; obstructive disease"],
    ["Increased airway resistance",
     "Prolonged, scooped expiratory curve; low peak expiratory flow",
     "Bronchospasm, secretions, kinked ETT"],
    ["Flow starvation",
     "Notch / scalloping on inspiratory plateau (VC mode)",
     "Patient demands more flow than ventilator delivers"],
    ["Air leak",
     "Insp > exp flow area; asymmetric curve",
     "Cuff leak, circuit disconnect"],
], colWidths=[4.5*cm, 6*cm, 6*cm], style=tbl_style()))
story.append(Spacer(1, 8))

# Diagram 1
story += diagram("flow_time",
    "Figure 1. Flow-Time Scalars — Volume Control (square wave), Pressure Control (decelerating ramp), "
    "and Abnormal (auto-PEEP: expiratory flow fails to return to zero before the next breath).")

# ── SCALAR 2: Pressure-Time ────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1,
    color=colors.HexColor('#bbccdd'), spaceAfter=6))
story.append(Paragraph("2. Pressure-Time Scalar", h1_style))
story.append(Paragraph(
    "Normal morphology in <b>Volume Control with a constant (square-wave) flow pattern:</b>",
    body_style))

story.append(Table([
    ["Phase", "Description"],
    ["1. Initial sharp rise",
     "Pressure overcomes airway resistance at onset of flow delivery"],
    ["2. Linear rise",
     "Pressure climbs as volume fills the lungs (proportional to elastance)"],
    ["3. Peak Inspiratory Pressure (PIP)",
     "Highest pressure reached during breath delivery"],
    ["4. End-inspiratory pressure drop",
     "Pressure falls from PIP to Plateau pressure (Pplat) during an inspiratory pause; "
     "difference = Resistive pressure (Pres = PIP - Pplat)"],
    ["5. Return to PEEP",
     "Passive expiration; pressure returns to set PEEP level"],
], colWidths=[5.5*cm, 11*cm], style=tbl_style()))
story.append(Spacer(1, 4))

story.append(Paragraph("Key Formula", h2_style))
story.append(Paragraph(
    "Paw  =  (Flow x Resistance)  +  (Volume / Compliance)  +  PEEP",
    formula_style))

story.append(Paragraph("Key Abnormalities", h2_style))
story.append(Table([
    ["Finding", "Interpretation"],
    ["High PIP, normal Pplat",
     "Airway resistance problem (bronchospasm, secretions, kinked ETT)"],
    ["High PIP AND high Pplat",
     "Compliance problem (ARDS, pneumothorax, pulmonary edema, ACS)"],
    ["Negative deflection at breath start",
     "Patient-triggered breath (effort dip before flow delivery)"],
    ["Double pressure peaks per effort", "Double-triggering asynchrony"],
    ["Concave/scooped inspiratory plateau",
     "Flow starvation — patient effort exceeds set flow"],
    ["Upward concavity of pressure slope (Stress Index > 1)",
     "Overdistension risk — pressure accelerates faster than expected"],
    ["Downward concavity (Stress Index < 1)",
     "Recruitable lung / atelectasis — compliance improving mid-breath"],
    ["Straight upslope (Stress Index = 1)",
     "Normal compliance throughout breath"],
], colWidths=[7*cm, 9.5*cm], style=tbl_style()))
story.append(Spacer(1, 8))

# Diagram 2
story += diagram("pressure_time",
    "Figure 2. Pressure-Time Scalars — Volume Control normal (with PIP, Pplat, and PEEP labelled), "
    "Pressure Control normal (rectangular waveform), and Abnormal overlays showing high resistance "
    "(raised PIP, normal Pplat) vs low compliance (both PIP and Pplat raised).")

# ── SCALAR 3: Volume-Time ──────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1,
    color=colors.HexColor('#bbccdd'), spaceAfter=6))
story.append(Paragraph("3. Volume-Time Scalar", h1_style))
story.append(Paragraph(
    "The upslope represents inspiratory volume delivery; the downslope returns to zero as gas exhales. "
    "Inspiratory and expiratory volumes should be <b>equal</b> (symmetric curve). "
    "Volume is <i>calculated</i> from the flow signal, not directly measured.", body_style))

story.append(Paragraph("Key Abnormalities", h2_style))
story.append(Table([
    ["Finding", "Interpretation"],
    ["Exp volume < insp volume (curve doesn't return to zero)",
     "Air leak (cuff leak, circuit disconnect, bronchopleural fistula)"],
    ["Expiratory limb plateaus above zero",
     "Auto-PEEP / air trapping"],
    ["Steep narrow curve", "Low compliance (stiff lungs)"],
    ["Reduced inspiratory volume", "Circuit leak or inadequate flow delivery"],
], colWidths=[7.5*cm, 9*cm], style=tbl_style()))
story.append(Spacer(1, 8))

# Diagram 3
story += diagram("volume_time",
    "Figure 3. Volume-Time Scalars — Normal (symmetric inspiratory = expiratory volume), "
    "Air leak (expiratory volume less than inspiratory; curve stops above zero), "
    "and Auto-PEEP / air trapping (baseline never returns to zero between breaths).")

# ── Mode-based summary ─────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1,
    color=colors.HexColor('#bbccdd'), spaceAfter=6))
story.append(Paragraph("Waveform Patterns by Ventilator Mode", h1_style))
story.append(Table([
    ["Mode", "Pressure-Time", "Flow-Time", "Volume-Time"],
    ["Volume Control\n(VC-AC)",
     "Variable — reflects compliance & resistance; monitor Ppeak & Pplat",
     "Square wave on inspiration; passive expiration",
     "Consistent Vt; check for leak or trapping"],
    ["Pressure Control\n(PC-AC)",
     "Rectangular — pressure is the independent variable",
     "Decelerating ramp; Vt varies with compliance",
     "Variable Vt — monitor for changes in mechanics"],
    ["Pressure Support\n(PS/CPAP)",
     "Rises to set support level; patient-cycled",
     "Decelerating; cycles off at flow threshold (e.g. 25% of peak)",
     "Variable Vt; RR is patient-determined"],
], colWidths=[3.5*cm, 5.5*cm, 5.5*cm, 4*cm], style=tbl_style()))

# ── Clinical diagnosis quick-reference ────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1,
    color=colors.HexColor('#bbccdd'), spaceAfter=6))
story.append(Paragraph("Clinical Diagnosis Quick Reference", h1_style))
story.append(Table([
    ["Scalar Finding", "Likely Problem"],
    ["Expiratory flow doesn't return to zero (flow-time)",
     "Auto-PEEP / air trapping"],
    ["High Ppeak, normal Pplat (pressure-time)",
     "Airway resistance (bronchospasm, secretions, kinked ETT)"],
    ["High Ppeak AND high Pplat (pressure-time)",
     "Low compliance (ARDS, pneumothorax, pulmonary edema)"],
    ["Insp > exp volume mismatch (volume-time)",
     "Air leak (cuff, circuit, bronchopleural fistula)"],
    ["Scooped/concave inspiratory pressure (pressure-time)",
     "Flow starvation asynchrony"],
    ["Upward concavity of pressure slope (pressure-time)",
     "Overdistension (stress index > 1)"],
    ["Two pressure peaks per patient effort (pressure-time)",
     "Double-triggering asynchrony"],
    ["Negative pressure dip without delivered breath",
     "Auto-triggering or missed trigger"],
    ["Distorted flow at onset of expiration (flow-time in PS)",
     "Late cycling — PS level too high"],
    ["Abrupt reversal of expiratory flow toward zero (flow-time)",
     "Early cycling — Ti shorter than patient's neural Ti"],
], colWidths=[8*cm, 8.5*cm], style=tbl_style()))

# ── Summary ────────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1,
    color=colors.HexColor('#bbccdd'), spaceAfter=6))
story.append(Paragraph("Summary", h1_style))
story.append(Table([
    ["Scalar", "Primary Clinical Use"],
    ["Flow-Time",
     "Detecting air trapping (auto-PEEP), airway obstruction, flow asynchrony, air leaks"],
    ["Pressure-Time",
     "Assessing compliance vs. resistance, overdistension (stress index), triggering quality, Ppeak/Pplat"],
    ["Volume-Time",
     "Detecting air leaks, confirming Vt delivery, identifying air trapping"],
], colWidths=[4*cm, 12.5*cm], style=tbl_style()))
story.append(Spacer(1, 8))
story.append(Paragraph(
    "Key reference: Bhatt JM et al., The Basics of Ventilator Waveforms, "
    "Curr Treat Options Pediatr. 2021 — PMC7782574",
    ParagraphStyle('ref', parent=body_style, fontSize=9,
                   textColor=colors.HexColor('#666666'))))

doc.build(story)
print(f"PDF created: {OUTPUT}")
Running Command

python /home/daytona/workspace/ventilator-scalars/build_pdf.py

Updated PDF is ready with all three diagrams embedded:

Ventilator Scalars

PDF Document · PDF

Each scalar section now has a dedicated 3-panel diagram placed directly below it:
  • Figure 1 - Flow-Time: VC square wave / PC decelerating ramp / Auto-PEEP (flow never returns to zero)
  • Figure 2 - Pressure-Time: VC normal with PIP, Pplat, PEEP labelled / PC rectangular wave / Abnormal overlay (high resistance vs low compliance)
  • Figure 3 - Volume-Time: Normal symmetric curve / Air leak (expiratory < inspiratory) / Air trapping (baseline never zero)
This is a shared conversation. Sign in to Orris to start your own chat.