Nst in detail

Asking for Preferences
Reading File
Finding Sources
Searching PubMed

"non-stress test" AND fetal surveillance

Finding Sources
Finding Sources
Reading File
Finding Sources
Reading File
Reading File
Reading File
Reading File
Finding Sources
Finding Sources
Reading File
Reading File
Loading Image
Here is a detailed, structured overview of the Non-Stress Test (NST):

Non-Stress Test (NST) - Detailed Overview

1. Definition and Rationale

The NST is a primary tool in antepartum fetal surveillance. It documents second and third-trimester fetal well-being by serving as a surrogate measure of:
  • The developing fetal autonomic nervous system
  • The adequacy of uteroplacental function
The test is more specific than sensitive - making it a better indicator of fetal health than fetal illness. It has been used clinically for over 40 years. - Textbook of Family Medicine 9e
The physiological basis is straightforward: fetal movement is coupled to FHR accelerations through intact central and autonomic nervous system pathways. If the fetus is well oxygenated, movement produces demonstrable accelerations. A hypoxic or acidemic fetus loses this coupling. - Creasy & Resnik's Maternal-Fetal Medicine

2. Indications

NST is indicated for any condition associated with increased fetal morbidity and mortality, particularly conditions causing:
  • Fetal hypoxia
  • Uteroplacental insufficiency
  • Risk of intrauterine fetal death
Common specific indications include:
  • Gestational diabetes mellitus (GDM) - uncontrolled or newly diagnosed
  • Chronic hypertension / pre-eclampsia
  • Intrauterine growth restriction (IUGR/FGR)
  • Post-term pregnancy (>42 weeks)
  • Decreased fetal movement
  • Antiphospholipid syndrome, thyroid disease, other maternal conditions
  • Multiple gestation with complications
General protocol: Weekly NST starting at 32 weeks for low-to-moderate risk; earlier and more frequent testing for higher-risk pregnancies, requiring individualized approach. - Textbook of Family Medicine 9e
There are no contraindications to the NST as a primary screening tool.

3. Procedure / How It Is Performed

  1. Patient placed in semi-recumbent position (NOT supine - avoids supine hypotension and a false-positive nonreactive result)
  2. External cardiotocograph (CTG) applied to the maternal abdomen:
    • Upper tracing: Fetal heart rate (FHR) - recorded by Doppler ultrasound transducer
    • Lower tracing: Uterine activity / fetal movement - tocodynamometer
  3. Patient is given a button to press each time she perceives fetal movement (shown as vertical marks on the lower tracing)
  4. Monitoring continues for a minimum of 20 minutes, extendable to 40-90 minutes if initially nonreactive (to account for fetal sleep cycles)

4. Interpretation: Reactive vs. Nonreactive

Reactive (Normal/Reassuring)

At term (≥32 weeks):
  • At least 2 FHR accelerations in a 20-minute period
  • Each acceleration must peak ≥15 beats/min above baseline
  • Each acceleration must last ≥15 seconds
  • (Accelerations lasting >2 minutes are classified differently)
Preterm (<32 weeks):
  • Modified criteria accepted: accelerations of ≥10 beats/min lasting ≥10 seconds, in a background of normal FHR variability
  • Reflects normal physiology - younger fetuses have smaller accelerations

Nonreactive (Abnormal)

  • Failure to meet the above acceleration criteria within the allotted time
  • ~10-12% of fetuses in the third trimester do not meet criteria at 30 minutes; this drops to <6% by 40 minutes
  • A nonreactive NST does NOT automatically indicate fetal jeopardy - it is an indication for further evaluation

5. NST Tracings

A = Reactive NST | B = Nonreactive NST
Reactive vs Nonreactive NST tracings
  • Panel A (Reactive): The upper FHR tracing shows clear accelerations coinciding with fetal movements marked on the lower tracing - the heart rate climbs visibly above baseline with each perceived kick
  • Panel B (Nonreactive): Despite fetal movements noted on the lower tracing, the FHR in the upper tracing shows no corresponding accelerations - the line is nearly flat at ~150 bpm
  • Pfenninger and Fowler's Procedures for Primary Care, 3rd ed.

6. Causes of a Nonreactive NST

CauseNotes
Fetal sleep cycleMost common cause; sleep cycles last 20-40 min, occasionally up to 2 hours near term
PrematurityPreterm fetuses have immature autonomic responses
Fetal hypoxia / acidemiaGenuine compromise - requires urgent further evaluation
Maternal narcotics / opioidsBlunt fetal CNS responses
Magnesium sulfateDepresses fetal neuromuscular activity
Fetal CNS or cardiac anomaliesStructural reasons for absent acceleration
Extreme prematurity<28 weeks: criteria modification needed

7. False-Positive and False-Negative Rates

MetricValue
False-negative rate (reactive NST)~1.9:1000 (fetal death within 1 week after reactive result) - Creasy & Resnik's
False-negative rate (modified BPP = NST + AFI)~0.8:1000 (better than NST alone)
Positive predictive value of nonreactive NST15% (post-term) to 69% (IUGR) - so false-positives are common
This high false-positive rate means a nonreactive NST should never prompt immediate intervention without further confirmation testing. - Pfenninger and Fowler's Procedures for Primary Care, 3rd ed.

8. Management of a Nonreactive NST

Nonreactive NST
       |
       ↓
Extend monitoring to 40-90 min
(exclude fetal sleep state)
       |
   Still nonreactive?
       |
   ┌───┴───────────┐
   ↓               ↓
Vibroacoustic   Further testing:
Stimulation     - BPP or modified BPP
(VAS)           - CST
                - Consider delivery if mature
                  + favorable cervix
Options available for a persistently nonreactive NST:
  1. Biophysical Profile (BPP) - adds ultrasound assessment of amniotic fluid, breathing, movement, tone
  2. Modified BPP - NST + amniotic fluid index (AFI); excellent sensitivity, false-negative rate 0.8:1000
  3. Contraction Stress Test (CST)
  4. Delivery - if fetus is mature and cervix favorable

9. Vibroacoustic Stimulation (VAS) for Nonreactive NST

When the NST is nonreactive and fetal sleep is suspected, VAS is used:
  • A handheld battery-powered artificial larynx is applied to the maternal abdomen
  • Produces a combination of sound + vibration (0.1-10 kHz broadband)
  • Goals: alter fetal behavioral state, wake a sleeping fetus, provoke FHR accelerations
  • Effect: Converts nonreactive to reactive by inducing fetal state change (quiet sleep → active sleep)
  • VAS-generated accelerations are equally predictive of favorable fetal outcome as spontaneously generated accelerations
  • Significantly decreases total testing time
  • Vibration sense is fully developed by 22-24 weeks; auditory response by ~25-26 weeks - Creasy & Resnik's Maternal-Fetal Medicine

10. NST as Part of the Modified BPP

The Modified Biophysical Profile = NST + Amniotic Fluid Index (AFI)
This combination:
  • Reflects both acute fetal status (NST - short-term CNS function) and chronic uteroplacental reserve (AFI - long-term amniotic fluid accumulation)
  • Proven to be as effective as a full BPP for assessing fetal well-being
  • Has become the most commonly used first-line surveillance tool in many centers
  • False-negative rate: 0.8:1000 (better than NST alone)

11. NST as a Component of the Full BPP

In the Biophysical Profile (BPP), the NST contributes 2 points out of a possible 10:
ComponentNormal (2 pts)Abnormal (0 pts)
NST≥2 accelerations ≥15 bpm above baseline for ≥15 sec<2 accelerations of sufficient height/duration
Amniotic fluid volume≥1 pocket ≥2×2 cmNo 2×2 cm pocket or AFI <5
Fetal breathing movements≥30 sec sustained breathing<30 sec
Fetal body movements≥3 limb/body movements<3 movements
Fetal toneExtension + return to flexionStays extended; no return to flexion
BPP scoring: 8-10 = reassuring; 6 = suspicious, re-evaluate; ≤4 = ominous, immediate intervention - Textbook of Family Medicine 9e

12. Key Advantages and Limitations

Advantages:
  • Non-invasive
  • No contraindications
  • Easily reproducible
  • Relatively inexpensive
  • Acceptable to most patients
  • Can be done in outpatient setting
Limitations:
  • High false-positive rate (nonreactive NST is commonly due to fetal sleep, not compromise)
  • Limited sensitivity in isolation - best combined with AFI (modified BPP)
  • Cannot detect all fetal compromise (1.9:1000 false-negative rate)
  • Interpretation can be affected by maternal medications, prematurity, anomalies

Summary: The NST is the cornerstone of antepartum fetal surveillance. A reactive NST (2+ accelerations ≥15 bpm × ≥15 sec in 20 min) is strongly reassuring. A nonreactive NST most commonly reflects fetal sleep and should prompt extended monitoring and/or VAS before escalating to BPP, CST, or delivery. It is best used in combination with AFI (modified BPP) to minimize false-positives and improve sensitivity.

Generate a clinical reference guide for NST interpretation

Asking for Preferences
Running Command

ls -la /tmp/workspace/ 2>/dev/null || mkdir -p /tmp/workspace && echo "workspace ready"

Running Command

mkdir -p /tmp/workspace/nst-guide && python3 - <<'PYEOF' from PIL import Image, ImageDraw, ImageFont import os # Canvas: A3 portrait at 150 DPI = 1240 x 1754 px (good for printing) W, H = 1240, 1754 img = Image.new("RGB", (W, H), "#FAFAFA") draw = ImageDraw.Draw(img) # ── Colour palette ────────────────────────────────────────────────────────── NAVY = "#1B3A6B" TEAL = "#007A8C" GREEN = "#1A7A4A" RED = "#C0392B" AMBER = "#D4850A" WHITE = "#FFFFFF" LGRAY = "#E8EDF2" DGRAY = "#4A4A4A" MGRAY = "#7A7A7A" BORDER = "#B0BEC5" # ── Font helper ───────────────────────────────────────────────────────────── def font(size, bold=False): paths = [ f"/usr/share/fonts/truetype/dejavu/DejaVuSans{'-Bold' if bold else ''}.ttf", f"/usr/share/fonts/truetype/liberation/LiberationSans{'-Bold' if bold else ''}.ttf", ] for p in paths: if os.path.exists(p): return ImageFont.truetype(p, size) return ImageFont.load_default() # ── Rounded rectangle helper ──────────────────────────────────────────────── def rrect(draw, xy, fill, radius=12, outline=None, width=2): x0,y0,x1,y1 = xy draw.rounded_rectangle([x0,y0,x1,y1], radius=radius, fill=fill, outline=outline, width=width) # ── Text wrap helper ──────────────────────────────────────────────────────── def wrap_text(text, font_obj, max_width): words = text.split() lines, line = [], "" for w in words: test = (line + " " + w).strip() bbox = font_obj.getbbox(test) if bbox[2] - bbox[0] <= max_width: line = test else: if line: lines.append(line) line = w if line: lines.append(line) return lines def draw_wrapped(draw, text, xy, font_obj, fill, max_width): x, y = xy lines = wrap_text(text, font_obj, max_width) for ln in lines: draw.text((x, y), ln, font=font_obj, fill=fill) y += font_obj.getbbox("Ag")[3] + 4 return y # ══════════════════════════════════════════════════════════════════════════════ # HEADER # ══════════════════════════════════════════════════════════════════════════════ rrect(draw, [0, 0, W, 100], fill=NAVY) draw.text((W//2, 28), "NON-STRESS TEST (NST)", font=font(38, True), fill=WHITE, anchor="mm") draw.text((W//2, 72), "CLINICAL INTERPRETATION REFERENCE GUIDE", font=font(18), fill="#90CAF9", anchor="mm") # Thin accent bar draw.rectangle([0, 100, W, 108], fill=TEAL) # ══════════════════════════════════════════════════════════════════════════════ # SECTION A — What is NST + Indications (two columns) # ══════════════════════════════════════════════════════════════════════════════ y_sec = 120 COL_L = 30 COL_R = W//2 + 15 COL_W = W//2 - 45 # --- What is NST --- rrect(draw, [COL_L, y_sec, COL_L+COL_W, y_sec+200], fill=LGRAY, outline=BORDER) draw.text((COL_L+14, y_sec+10), "WHAT IS THE NST?", font=font(14, True), fill=NAVY) draw.line([COL_L+14, y_sec+30, COL_L+COL_W-14, y_sec+30], fill=TEAL, width=2) info_lines = [ "• External cardiotocograph (CTG) recording of", " fetal heart rate (FHR) & uterine activity", "• Primary antepartum fetal surveillance tool", "• Reflects fetal autonomic nervous system", " integrity & uteroplacental function", "• No contraindications • Non-invasive", "• Patient semi-recumbent (avoids supine hypotension)", "• Mother presses button on fetal movement", ] fy = y_sec + 38 for ln in info_lines: draw.text((COL_L+14, fy), ln, font=font(12), fill=DGRAY) fy += 19 # --- Indications --- rrect(draw, [COL_R, y_sec, COL_R+COL_W, y_sec+200], fill=LGRAY, outline=BORDER) draw.text((COL_R+14, y_sec+10), "INDICATIONS", font=font(14, True), fill=NAVY) draw.line([COL_R+14, y_sec+30, COL_R+COL_W-14, y_sec+30], fill=TEAL, width=2) ind_lines = [ "• Gestational diabetes (GDM) — uncontrolled", "• Chronic hypertension / pre-eclampsia", "• Intrauterine growth restriction (IUGR/FGR)", "• Post-term pregnancy (≥41–42 weeks)", "• Decreased fetal movement (DFM)", "• Multiple gestation with complications", "• Antiphospholipid syndrome / thyroid disease", " START: 32 wks (low-risk) — earlier if high-risk", ] fy = y_sec + 38 for ln in ind_lines: draw.text((COL_R+14, fy), ln, font=font(12), fill=DGRAY) fy += 19 # ══════════════════════════════════════════════════════════════════════════════ # SECTION B — Interpretation Criteria (full width, 3-column boxes) # ══════════════════════════════════════════════════════════════════════════════ y_crit = y_sec + 215 draw.text((W//2, y_crit), "INTERPRETATION CRITERIA", font=font(17, True), fill=NAVY, anchor="mm") y_crit += 24 THIRD_W = (W - 60) // 3 GAP = 15 # --- REACTIVE --- rx0 = 30 rrect(draw, [rx0, y_crit, rx0+THIRD_W, y_crit+155], fill="#E8F5E9", outline=GREEN, width=3) draw.text((rx0+THIRD_W//2, y_crit+14), "✓ REACTIVE", font=font(15, True), fill=GREEN, anchor="mm") draw.line([rx0+12, y_crit+28, rx0+THIRD_W-12, y_crit+28], fill=GREEN, width=2) react_lines = [ "≥ 2 accelerations in 20 min", "", "Each acceleration must have:", " Peak ≥ 15 bpm above baseline", " Duration ≥ 15 seconds", " (Duration < 2 minutes)", "", "Preterm < 32 wks: ≥10 bpm × ≥10 sec", " (with normal FHR variability)", ] fy = y_crit + 34 for ln in react_lines: draw.text((rx0+12, fy), ln, font=font(11), fill="#1B5E20") fy += 14 # --- NONREACTIVE --- nx0 = rx0 + THIRD_W + GAP rrect(draw, [nx0, y_crit, nx0+THIRD_W, y_crit+155], fill="#FFF3E0", outline=AMBER, width=3) draw.text((nx0+THIRD_W//2, y_crit+14), "⚠ NONREACTIVE", font=font(15, True), fill=AMBER, anchor="mm") draw.line([nx0+12, y_crit+28, nx0+THIRD_W-12, y_crit+28], fill=AMBER, width=2) nonreact_lines = [ "Fails to meet reactive criteria", "within the monitoring period", "", "~10–12% at 30 min (drops to", "<6% by 40 min) — 3rd trimester", "", "Does NOT = fetal jeopardy", "→ Indicates need for further", " evaluation, not immediate action", ] fy = y_crit + 34 for ln in nonreact_lines: draw.text((nx0+12, fy), ln, font=font(11), fill="#7B4400") fy += 14 # --- UNSATISFACTORY --- ux0 = nx0 + THIRD_W + GAP rrect(draw, [ux0, y_crit, ux0+THIRD_W, y_crit+155], fill="#FDECEA", outline=RED, width=3) draw.text((ux0+THIRD_W//2, y_crit+14), "✗ UNSATISFACTORY", font=font(14, True), fill=RED, anchor="mm") draw.line([ux0+12, y_crit+28, ux0+THIRD_W-12, y_crit+28], fill=RED, width=2) unsat_lines = [ "Inadequate tracing quality:", "• Persistent poor signal", "• Cannot assess FHR baseline", "• Unable to detect accelerations", "", "Action:", "• Reposition patient", "• Reapply transducer", "• Repeat test / escalate", ] fy = y_crit + 34 for ln in unsat_lines: draw.text((ux0+12, fy), ln, font=font(11), fill="#8B0000") fy += 14 # ══════════════════════════════════════════════════════════════════════════════ # SECTION C — Causes of Nonreactive NST (full width table) # ══════════════════════════════════════════════════════════════════════════════ y_cause = y_crit + 170 rrect(draw, [30, y_cause, W-30, y_cause+195], fill="#EEF2F7", outline=BORDER) draw.text((W//2, y_cause+12), "CAUSES OF NONREACTIVE NST", font=font(15, True), fill=NAVY, anchor="mm") draw.line([30, y_cause+28, W-30, y_cause+28], fill=TEAL, width=2) causes = [ ("Fetal Sleep Cycle", "MOST COMMON • Quiet sleep 20-40 min (up to 2 hrs near term)\n→ Extend monitoring; use VAS to wake fetus"), ("Prematurity (<32 wks)", "Immature autonomic nervous system\n→ Use modified criteria (10×10 rule)"), ("Fetal Hypoxia/Acidemia", "Genuine compromise — CNS depression from hypoxemia\n→ Urgent BPP or delivery assessment"), ("Maternal Medications", "Opioids/narcotics, Magnesium sulfate, Beta-blockers,\nSteroids — all blunt fetal CNS/cardiac responses"), ("Fetal CNS/Cardiac\nAnomalies", "Structural reasons for absent acceleration\n→ Ultrasound assessment"), ] cx = 40; cy = y_cause + 36 col_gap = (W - 60) // len(causes) for title, desc in causes: draw.text((cx, cy), title, font=font(11, True), fill=NAVY) dy = cy + 16 for dline in desc.split("\n"): draw.text((cx, dy), dline, font=font(10), fill=DGRAY) dy += 14 # vertical divider if cx + col_gap < W - 30: draw.line([cx + col_gap - 8, y_cause+32, cx + col_gap - 8, y_cause+185], fill=BORDER, width=1) cx += col_gap # ══════════════════════════════════════════════════════════════════════════════ # SECTION D — Management Flowchart # ══════════════════════════════════════════════════════════════════════════════ y_flow = y_cause + 208 draw.text((W//2, y_flow), "MANAGEMENT ALGORITHM", font=font(17, True), fill=NAVY, anchor="mm") y_flow += 22 # Flowchart boxes BOX_H = 38 BOX_W = 240 cx_fc = W // 2 def flow_box(y, text, color, text_color=WHITE, w=BOX_W): x0 = cx_fc - w//2; x1 = cx_fc + w//2 rrect(draw, [x0, y, x1, y+BOX_H], fill=color, radius=8) draw.text((cx_fc, y+BOX_H//2), text, font=font(12, True), fill=text_color, anchor="mm") return y + BOX_H def arrow(y_from, y_to, label=""): draw.line([cx_fc, y_from, cx_fc, y_to], fill=DGRAY, width=2) # arrowhead draw.polygon([(cx_fc-7, y_to-10),(cx_fc+7, y_to-10),(cx_fc, y_to)], fill=DGRAY) if label: draw.text((cx_fc+10, (y_from+y_to)//2 - 7), label, font=font(10), fill=MGRAY) yf = y_flow yf_end = flow_box(yf, "NST Ordered (≥32 weeks)", NAVY) arrow(yf_end, yf_end+18) yf = yf_end + 18 yf_end = flow_box(yf, "Monitor 20 Minutes", TEAL) arrow(yf_end, yf_end+14) yf = yf_end + 14 # Decision diamond (simulate with rotated box) def diamond(cx, cy, text, fill_col, w=200, h=44): pts = [(cx, cy),(cx+w//2, cy+h//2),(cx, cy+h),(cx-w//2, cy+h//2)] draw.polygon(pts, fill=fill_col) draw.polygon(pts, outline="#555", width=2) draw.text((cx, cy+h//2), text, font=font(11, True), fill=WHITE, anchor="mm") return cy + h d_bot = diamond(cx_fc, yf, "REACTIVE?", AMBER, w=220, h=48) yf_dbot = d_bot # YES branch (left) draw.line([cx_fc - 110, yf + 24, cx_fc - 180, yf + 24], fill=GREEN, width=2) draw.line([cx_fc - 180, yf + 24, cx_fc - 180, yf + 90], fill=GREEN, width=2) rrect(draw, [cx_fc-300, yf+90, cx_fc-60, yf+140], fill="#E8F5E9", outline=GREEN, width=2) draw.text((cx_fc-180, yf+115), "✓ REACTIVE", font=font(12, True), fill=GREEN, anchor="mm") draw.text((cx_fc-180, yf+130), "Reassuring — routine follow-up", font=font(10), fill=GREEN, anchor="mm") draw.text((cx_fc-155, yf+8), "YES", font=font(10, True), fill=GREEN) # NO branch (right) draw.line([cx_fc + 110, yf + 24, cx_fc + 190, yf + 24], fill=RED, width=2) draw.line([cx_fc + 190, yf + 24, cx_fc + 190, yf + 90], fill=RED, width=2) rrect(draw, [cx_fc+60, yf+90, cx_fc+320, yf+140], fill="#FDECEA", outline=RED, width=2) draw.text((cx_fc+190, yf+107), "Extend to 40 min", font=font(11, True), fill=RED, anchor="mm") draw.text((cx_fc+190, yf+124), "Try Vibroacoustic Stim (VAS)", font=font(10), fill=RED, anchor="mm") draw.text((cx_fc+130, yf+8), "NO", font=font(10, True), fill=RED) # After VAS — still nonreactive? yf_vas = yf + 155 arrow(d_bot + 14, yf_vas, "Still nonreactive") d2_bot = diamond(cx_fc, yf_vas, "STILL NONREACTIVE?", RED, w=240, h=48) # Left: BPP / modified BPP draw.line([cx_fc - 120, yf_vas + 24, cx_fc - 200, yf_vas + 24], fill=AMBER, width=2) draw.line([cx_fc - 200, yf_vas + 24, cx_fc - 200, yf_vas + 90], fill=AMBER, width=2) rrect(draw, [cx_fc-325, yf_vas+90, cx_fc-75, yf_vas+155], fill="#FFF3E0", outline=AMBER, width=2) draw.text((cx_fc-200, yf_vas+107), "Modified BPP", font=font(11, True), fill=AMBER, anchor="mm") draw.text((cx_fc-200, yf_vas+122), "(NST + AFI)", font=font(10), fill=AMBER, anchor="mm") draw.text((cx_fc-200, yf_vas+137), "or Full BPP / CST", font=font(10), fill=AMBER, anchor="mm") draw.text((cx_fc-155, yf_vas+8), "YES", font=font(10, True), fill=AMBER) # Right: Delivery draw.line([cx_fc + 120, yf_vas + 24, cx_fc + 200, yf_vas + 24], fill=GREEN, width=2) draw.line([cx_fc + 200, yf_vas + 24, cx_fc + 200, yf_vas + 90], fill=GREEN, width=2) rrect(draw, [cx_fc+75, yf_vas+90, cx_fc+325, yf_vas+155], fill="#E8F5E9", outline=GREEN, width=2) draw.text((cx_fc+200, yf_vas+107), "✓ Reassuring", font=font(11, True), fill=GREEN, anchor="mm") draw.text((cx_fc+200, yf_vas+122), "Continue routine", font=font(10), fill=GREEN, anchor="mm") draw.text((cx_fc+200, yf_vas+137), "surveillance plan", font=font(10), fill=GREEN, anchor="mm") draw.text((cx_fc+128, yf_vas+8), "NO", font=font(10, True), fill=GREEN) # ══════════════════════════════════════════════════════════════════════════════ # SECTION E — Performance Metrics (bottom, 3 boxes) # ══════════════════════════════════════════════════════════════════════════════ y_metrics = yf_vas + 170 rrect(draw, [30, y_metrics, W-30, y_metrics+130], fill="#EEF2F7", outline=BORDER) draw.text((W//2, y_metrics+10), "PERFORMANCE METRICS & PPV/NPV", font=font(14, True), fill=NAVY, anchor="mm") draw.line([30, y_metrics+26, W-30, y_metrics+26], fill=TEAL, width=2) metrics = [ ("False-Negative Rate\n(Reactive NST)", "~1.9 / 1000\n(fetal death within 1 week)", GREEN), ("False-Negative Rate\n(Modified BPP)", "~0.8 / 1000\n(NST + AFI combined)", TEAL), ("PPV of Nonreactive NST", "15% (post-term)\n→ 69% (IUGR)\n(false-positives are COMMON)", RED), ] mx = 50 for title, val, col in metrics: rrect(draw, [mx, y_metrics+34, mx+350, y_metrics+120], fill=WHITE, outline=col, width=2) ty = y_metrics + 42 for tl in title.split("\n"): draw.text((mx+175, ty), tl, font=font(11, True), fill=col, anchor="mm") ty += 16 ty += 4 for vl in val.split("\n"): draw.text((mx+175, ty), vl, font=font(11), fill=DGRAY, anchor="mm") ty += 15 mx += 375 # ══════════════════════════════════════════════════════════════════════════════ # SECTION F — Modified BPP + BPP Components (side by side) # ══════════════════════════════════════════════════════════════════════════════ y_bpp = y_metrics + 143 # Modified BPP box rrect(draw, [30, y_bpp, 580, y_bpp+175], fill="#F0F4FF", outline=NAVY, width=2) draw.text((305, y_bpp+10), "MODIFIED BPP", font=font(14, True), fill=NAVY, anchor="mm") draw.line([44, y_bpp+26, 566, y_bpp+26], fill=TEAL, width=2) mbpp = [ "= NST + Amniotic Fluid Index (AFI)", "", "NST: Acute fetal status (CNS integrity)", "AFI: Chronic uteroplacental reserve", "", "AFI Normal: ≥ 5 cm (or MVP ≥ 2 cm)", "AFI Low: < 5 cm → Oligohydramnios", "", "False-negative rate: 0.8 / 1000", "As effective as full BPP — preferred first-line", ] fy = y_bpp + 34 for ln in mbpp: draw.text((44, fy), ln, font=font(11), fill=DGRAY) fy += 15 # BPP components box rrect(draw, [595, y_bpp, W-30, y_bpp+175], fill="#F9F0FF", outline="#7B1FA2", width=2) draw.text(((595+W-30)//2, y_bpp+10), "BPP COMPONENTS (each = 2 pts)", font=font(13, True), fill="#7B1FA2", anchor="mm") draw.line([609, y_bpp+26, W-44, y_bpp+26], fill="#7B1FA2", width=2) bpp_items = [ ("NST", "≥2 accel ≥15 bpm × ≥15 sec"), ("Breathing", "≥30 sec sustained in 30 min"), ("Movement", "≥3 body/limb movements"), ("Tone", "Extension + return to flexion"), ("AFV", "≥1 pocket ≥2×2 cm"), ] fy = y_bpp + 34 for comp, crit in bpp_items: draw.text((609, fy), f"• {comp}:", font=font(11, True), fill="#7B1FA2") draw.text((685, fy), crit, font=font(11), fill=DGRAY) fy += 18 draw.line([609, fy+4, W-44, fy+4], fill=BORDER, width=1) fy += 10 scores = [("8-10", "Reassuring", GREEN), ("6", "Suspicious — re-evaluate", AMBER), ("≤ 4", "Ominous — immediate action", RED)] for sc, meaning, col in scores: draw.text((609, fy), f"Score {sc}: ", font=font(11, True), fill=DGRAY) draw.text((680, fy), meaning, font=font(11), fill=col) fy += 15 # ══════════════════════════════════════════════════════════════════════════════ # SECTION G — VAS + Tips (2 narrow boxes) # ══════════════════════════════════════════════════════════════════════════════ y_vas = y_bpp + 188 HALF = (W - 75) // 2 rrect(draw, [30, y_vas, 30+HALF, y_vas+145], fill="#E0F7FA", outline=TEAL, width=2) draw.text((30+HALF//2, y_vas+10), "VIBROACOUSTIC STIMULATION (VAS)", font=font(13, True), fill=TEAL, anchor="mm") draw.line([44, y_vas+26, 30+HALF-14, y_vas+26], fill=TEAL, width=2) vas_lines = [ "• Handheld electro-larynx on maternal abdomen", "• 1–3 sec burst of vibration + sound (0.1–10 kHz)", "• Goal: State change (quiet → active sleep)", "• Vibration sense: fully developed by 22–24 wks", "• Auditory response: ~25–26 weeks onward", "• Significantly shortens testing time", "• VAS-generated accelerations = equal predictive", " value to spontaneous accelerations", "• Risk: Rare fetal bradycardia reported", ] fy = y_vas + 34 for ln in vas_lines: draw.text((44, fy), ln, font=font(11), fill="#004D40") fy += 13 # Tips box tx0 = 30 + HALF + 15 rrect(draw, [tx0, y_vas, tx0+HALF, y_vas+145], fill="#FFFDE7", outline=AMBER, width=2) draw.text((tx0+HALF//2, y_vas+10), "CLINICAL TIPS & PITFALLS", font=font(13, True), fill=AMBER, anchor="mm") draw.line([tx0+14, y_vas+26, tx0+HALF-14, y_vas+26], fill=AMBER, width=2) tips = [ "⚑ Semi-recumbent position — NEVER supine", "⚑ Extend to 40–90 min before calling nonreactive", "⚑ Fetal sleep is most common cause of NR NST", "⚑ Check maternal meds (opioids, MgSO₄)", "⚑ Variable decelerations in reactive NST → AFI", "⚑ NR NST in IUGR: PPV 69% — act quickly", "⚑ NR NST post-term: PPV only 15% — extend test", "⚑ Combine with AFI (modified BPP) for best accuracy", "⚑ Document all accelerations, baseline, variability", ] fy = y_vas + 34 for ln in tips: draw.text((tx0+14, fy), ln, font=font(11), fill="#5D4037") fy += 13 # ══════════════════════════════════════════════════════════════════════════════ # FOOTER # ══════════════════════════════════════════════════════════════════════════════ y_foot = H - 38 draw.rectangle([0, y_foot, W, H], fill=NAVY) draw.text((W//2, y_foot+10), "Sources: Textbook of Family Medicine 9e • Creasy & Resnik's Maternal-Fetal Medicine • Pfenninger & Fowler's Procedures for Primary Care", font=font(9), fill="#90CAF9", anchor="mm") draw.text((W//2, y_foot+24), "For educational use only — clinical decisions should be individualised based on current guidelines and patient context.", font=font(9), fill="#90CAF9", anchor="mm") # Save out_path = "/tmp/workspace/nst-guide/NST_Clinical_Reference_Guide.png" img.save(out_path, "PNG", dpi=(150, 150)) print(f"Saved: {out_path} ({W}x{H} px)") PYEOF

Reading File
Running Command

python3 - <<'PYEOF' from PIL import Image, ImageDraw, ImageFont import os # A3 @ 200 DPI = 1654 x 2339 px — bigger canvas for more breathing room W, H = 1654, 2339 img = Image.new("RGB", (W, H), "#F7F9FC") draw = ImageDraw.Draw(img) # Palette NAVY = "#1B3A6B" TEAL = "#006E7F" GREEN = "#1A7A4A" RED = "#C0392B" AMBER = "#C97A00" WHITE = "#FFFFFF" LGRAY = "#EDF1F6" DGRAY = "#3A3A3A" MGRAY = "#8A8A8A" BORDER = "#C5D0DA" PURPLE = "#6A1B9A" def font(size, bold=False): for name in ["DejaVuSans", "LiberationSans"]: suffix = "-Bold" if bold else "" for base in ["/usr/share/fonts/truetype/dejavu/", "/usr/share/fonts/truetype/liberation/"]: p = base + name + suffix + ".ttf" if os.path.exists(p): return ImageFont.truetype(p, size) return ImageFont.load_default() def rrect(draw, xy, fill, radius=14, outline=None, width=2): draw.rounded_rectangle(xy, radius=radius, fill=fill, outline=outline, width=width) def txt(draw, pos, text, fnt, fill, anchor="la"): draw.text(pos, text, font=fnt, fill=fill, anchor=anchor) def hline(draw, x0, y, x1, col=BORDER, w=1): draw.line([(x0,y),(x1,y)], fill=col, width=w) def vline(draw, x, y0, y1, col=BORDER, w=1): draw.line([(x,y0),(x,y1)], fill=col, width=w) # ──────────────────────────────────────────────────────────────────────────── # HEADER # ──────────────────────────────────────────────────────────────────────────── rrect(draw, [0,0,W,130], fill=NAVY, radius=0) txt(draw, (W//2,38), "NON-STRESS TEST (NST)", font(50,True), WHITE, "mm") txt(draw, (W//2,88), "CLINICAL INTERPRETATION · REFERENCE GUIDE", font(24), "#90CAF9", "mm") draw.rectangle([0,130,W,142], fill=TEAL) # ──────────────────────────────────────────────────────────────────────────── # ROW 1 — What is NST | Indications # ──────────────────────────────────────────────────────────────────────────── y0 = 154; PAD = 32; MID = W//2 + 8 COL = MID - PAD - 32 # column width for half-page boxes def half_box(x, y, w, h, title, lines, hdr_col, body_col, text_col, border_col): rrect(draw, [x, y, x+w, y+h], fill=body_col, outline=border_col, width=2) rrect(draw, [x, y, x+w, y+38], fill=hdr_col, radius=12, outline=None) draw.rectangle([x, y+26, x+w, y+38], fill=hdr_col) # square off bottom of header txt(draw, (x+w//2, y+19), title, font(17,True), WHITE, "mm") fy = y + 46 for ln in lines: txt(draw, (x+16, fy), ln, font(14), text_col) fy += 22 return fy lft_lines = [ "· External cardiotocograph (CTG) — FHR upper tracing,", " uterine activity on lower tracing", "· Primary antepartum fetal surveillance tool", "· Reflects fetal autonomic nervous system integrity", "· Reflects adequacy of uteroplacental function", "· NO contraindications · Non-invasive · Inexpensive", "· Patient position: semi-recumbent (never flat supine)", "· Patient presses button when fetal movement felt", ] rgt_lines = [ "· Gestational diabetes (GDM) — uncontrolled/new", "· Chronic hypertension / pre-eclampsia", "· Intrauterine growth restriction (IUGR / FGR)", "· Post-term pregnancy (≥ 41–42 weeks)", "· Decreased fetal movement (DFM)", "· Multiple gestation with complications", "· Antiphospholipid syndrome / thyroid disease", " START: 32 wks (low-risk) · earlier if high-risk", ] BOX1H = 230 half_box(PAD, y0, COL, BOX1H, "WHAT IS THE NST?", lft_lines, NAVY, LGRAY, DGRAY, BORDER) half_box(MID, y0, COL, BOX1H, "INDICATIONS", rgt_lines, TEAL, LGRAY, DGRAY, BORDER) # ──────────────────────────────────────────────────────────────────────────── # ROW 2 — Interpretation Criteria (3 columns) # ──────────────────────────────────────────────────────────────────────────── y2 = y0 + BOX1H + 22 txt(draw, (W//2, y2), "INTERPRETATION CRITERIA", font(20,True), NAVY, "mm") y2 += 28 CRIT_H = 210 C3W = (W - PAD*2 - 28) // 3 # Reactive rx = PAD rrect(draw, [rx, y2, rx+C3W, y2+CRIT_H], fill="#E8F5E9", outline=GREEN, width=3) rrect(draw, [rx, y2, rx+C3W, y2+40], fill=GREEN, radius=12, outline=None) draw.rectangle([rx, y2+28, rx+C3W, y2+40], fill=GREEN) txt(draw, (rx+C3W//2, y2+20), "✓ REACTIVE (NORMAL)", font(16,True), WHITE, "mm") react = [ "≥ 2 accelerations in a 20-min window", "", "Each acceleration must:", " · Peak ≥ 15 bpm above baseline FHR", " · Last ≥ 15 seconds", " · Last < 2 minutes", "", "Preterm < 32 wks (modified):", " · ≥ 10 bpm above baseline", " · Duration ≥ 10 seconds", " · Normal FHR variability present", ] fy = y2+50 for ln in react: txt(draw, (rx+16, fy), ln, font(13), "#1B5E20") fy += 16 # Nonreactive nx = rx + C3W + 14 rrect(draw, [nx, y2, nx+C3W, y2+CRIT_H], fill="#FFF8E1", outline=AMBER, width=3) rrect(draw, [nx, y2, nx+C3W, y2+40], fill=AMBER, radius=12, outline=None) draw.rectangle([nx, y2+28, nx+C3W, y2+40], fill=AMBER) txt(draw, (nx+C3W//2, y2+20), "⚠ NONREACTIVE", font(16,True), WHITE, "mm") nonr = [ "Fails to meet reactive criteria", "within the monitoring period", "", "~10–12% of 3rd-trimester fetuses", "at 30 min → < 6% by 40 min", "", "Does NOT equal fetal jeopardy", "", "→ Indicates need for further", " evaluation, not immediate action", "", "→ Extend monitoring & consider VAS", ] fy = y2+50 for ln in nonr: txt(draw, (nx+16, fy), ln, font(13), "#6D4C00") fy += 16 # Unsatisfactory ux = nx + C3W + 14 rrect(draw, [ux, y2, ux+C3W, y2+CRIT_H], fill="#FDECEA", outline=RED, width=3) rrect(draw, [ux, y2, ux+C3W, y2+40], fill=RED, radius=12, outline=None) draw.rectangle([ux, y2+28, ux+C3W, y2+40], fill=RED) txt(draw, (ux+C3W//2, y2+20), "✗ UNSATISFACTORY", font(16,True), WHITE, "mm") unsat = [ "Inadequate tracing quality —", "cannot be interpreted", "", "Causes:", " · Persistent poor signal", " · Cannot assess FHR baseline", " · Unable to detect accelerations", "", "Action:", " · Reposition patient", " · Reapply transducer", " · Repeat / escalate", ] fy = y2+50 for ln in unsat: txt(draw, (ux+16, fy), ln, font(13), "#7F0000") fy += 16 # ──────────────────────────────────────────────────────────────────────────── # ROW 3 — Causes of Nonreactive NST # ──────────────────────────────────────────────────────────────────────────── y3 = y2 + CRIT_H + 22 rrect(draw, [PAD, y3, W-PAD, y3+220], fill=LGRAY, outline=BORDER, width=2) rrect(draw, [PAD, y3, W-PAD, y3+38], fill=NAVY, radius=12, outline=None) draw.rectangle([PAD, y3+26, W-PAD, y3+38], fill=NAVY) txt(draw, (W//2, y3+19), "CAUSES OF A NONREACTIVE NST", font(18,True), WHITE, "mm") causes = [ ("Fetal Sleep Cycle", ["MOST COMMON", "Quiet sleep 20–40 min", "(up to 2 hrs near term)", "→ Extend monitoring", "→ Use VAS to wake"]), ("Prematurity\n< 32 Weeks", ["Immature autonomic", "nervous system", "→ Use modified 10×10", "criteria"]), ("Fetal Hypoxia /\nAcidemia", ["Genuine compromise", "CNS depression from", "hypoxemia/acidosis", "→ Urgent BPP or", "delivery assessment"]), ("Maternal\nMedications", ["Opioids / narcotics", "Magnesium sulfate", "Beta-blockers", "Corticosteroids", "→ All blunt CNS/cardiac"]), ("Fetal CNS /\nCardiac Anomalies", ["Structural reasons for", "absent acceleration", "→ Targeted ultrasound", "assessment"]), ] CW5 = (W - PAD*2 - 4*14) // 5 cx5 = PAD + 12 for i,(title,desc) in enumerate(causes): fy = y3 + 50 # Title for tl in title.split("\n"): txt(draw, (cx5, fy), tl, font(14,True), NAVY) fy += 20 hline(draw, cx5, fy, cx5+CW5-16, TEAL, 1) fy += 8 for dl in desc: txt(draw, (cx5, fy), dl, font(12), DGRAY) fy += 17 if i < len(causes)-1: vline(draw, cx5+CW5+6, y3+42, y3+215, BORDER, 1) cx5 += CW5 + 14 # ──────────────────────────────────────────────────────────────────────────── # ROW 4 — Management Flowchart # ──────────────────────────────────────────────────────────────────────────── y4 = y3 + 234 txt(draw, (W//2, y4), "MANAGEMENT ALGORITHM", font(20,True), NAVY, "mm") y4 += 28 FCW = 290 # flowchart box width FCH = 44 cx_fc = W // 2 def fc_box(draw, cy, text, col, w=FCW, h=FCH, text_col=WHITE): x0 = cx_fc - w//2; x1 = cx_fc + w//2 rrect(draw, [x0, cy, x1, cy+h], fill=col, radius=10, outline=None) txt(draw, (cx_fc, cy+h//2), text, font(14,True), text_col, "mm") return cy+h def arrow_down(draw, y_from, y_to, label="", col=DGRAY): draw.line([(cx_fc, y_from),(cx_fc, y_to-10)], fill=col, width=2) draw.polygon([(cx_fc-8,y_to-12),(cx_fc+8,y_to-12),(cx_fc,y_to)], fill=col) if label: txt(draw, (cx_fc+12, (y_from+y_to)//2-10), label, font(11), MGRAY) def diamond(draw, cy, text, fill_col, w=260, h=52): pts = [(cx_fc,cy),(cx_fc+w//2,cy+h//2),(cx_fc,cy+h),(cx_fc-w//2,cy+h//2)] draw.polygon(pts, fill=fill_col) draw.polygon(pts, outline="#555", width=2) txt(draw, (cx_fc, cy+h//2), text, font(14,True), WHITE, "mm") return cy+h def side_box(draw, cx, cy, text1, text2, col, w=280, h=72): rrect(draw, [cx-w//2, cy, cx+w//2, cy+h], fill=col+"22", outline=col, width=2) txt(draw, (cx, cy+20), text1, font(13,True), col, "mm") txt(draw, (cx, cy+40), text2, font(12), DGRAY, "mm") yf = y4 yf_end = fc_box(draw, yf, "NST Ordered (≥ 32 weeks)", NAVY) arrow_down(draw, yf_end, yf_end+20) yf = yf_end + 20 yf_end = fc_box(draw, yf, "Monitor for 20 Minutes", TEAL) arrow_down(draw, yf_end, yf_end+20) yf = yf_end + 20 d1_top = yf d1_bot = diamond(draw, yf, "REACTIVE?", AMBER, w=280, h=52) # YES — left LX = cx_fc - 320 draw.line([(cx_fc-140, d1_top+26),(LX, d1_top+26)], fill=GREEN, width=2) draw.line([(LX, d1_top+26),(LX, d1_top+110)], fill=GREEN, width=2) rrect(draw, [LX-130, d1_top+110, LX+130, d1_top+170], fill="#E8F5E9", outline=GREEN, width=2) txt(draw, (LX, d1_top+135), "✓ REACTIVE", font(14,True), GREEN, "mm") txt(draw, (LX, d1_top+154), "Reassuring — routine follow-up", font(12), "#2E7D32", "mm") txt(draw, (cx_fc-175, d1_top+10), "YES", font(12,True), GREEN) # NO — right RX = cx_fc + 320 draw.line([(cx_fc+140, d1_top+26),(RX, d1_top+26)], fill=RED, width=2) draw.line([(RX, d1_top+26),(RX, d1_top+90)], fill=RED, width=2) rrect(draw, [RX-145, d1_top+90, RX+145, d1_top+170], fill="#FDECEA", outline=RED, width=2) txt(draw, (RX, d1_top+110), "Extend to 40–90 min", font(13,True), RED, "mm") txt(draw, (RX, d1_top+132), "Try Vibroacoustic", font(12), DGRAY, "mm") txt(draw, (RX, d1_top+150), "Stimulation (VAS)", font(12), DGRAY, "mm") txt(draw, (cx_fc+148, d1_top+10), "NO", font(12,True), RED) # Still nonreactive arrow down arrow_down(draw, d1_bot, d1_bot+30, "Still nonreactive", RED) yf = d1_bot + 30 d2_top = yf d2_bot = diamond(draw, yf, "STILL NONREACTIVE?", RED, w=300, h=52) # YES — left (BPP) draw.line([(cx_fc-150, d2_top+26),(LX, d2_top+26)], fill=AMBER, width=2) draw.line([(LX, d2_top+26),(LX, d2_top+110)], fill=AMBER, width=2) rrect(draw, [LX-155, d2_top+110, LX+155, d2_top+185], fill="#FFF8E1", outline=AMBER, width=2) txt(draw, (LX, d2_top+128), "Modified BPP", font(14,True), AMBER, "mm") txt(draw, (LX, d2_top+148), "(NST + AFI)", font(13), DGRAY, "mm") txt(draw, (LX, d2_top+166), "or Full BPP / CST", font(13), DGRAY, "mm") txt(draw, (cx_fc-178, d2_top+10), "YES", font(12,True), AMBER) # NO — right (reassuring) draw.line([(cx_fc+150, d2_top+26),(RX, d2_top+26)], fill=GREEN, width=2) draw.line([(RX, d2_top+26),(RX, d2_top+110)], fill=GREEN, width=2) rrect(draw, [RX-145, d2_top+110, RX+145, d2_top+185], fill="#E8F5E9", outline=GREEN, width=2) txt(draw, (RX, d2_top+130), "✓ Reassuring", font(14,True), GREEN, "mm") txt(draw, (RX, d2_top+150), "Continue routine", font(12), "#2E7D32", "mm") txt(draw, (RX, d2_top+168), "surveillance plan", font(12), "#2E7D32", "mm") txt(draw, (cx_fc+155, d2_top+10), "NO", font(12,True), GREEN) yf_after = d2_bot + 20 # ──────────────────────────────────────────────────────────────────────────── # ROW 5 — Performance Metrics # ──────────────────────────────────────────────────────────────────────────── y5 = yf_after + 10 rrect(draw, [PAD, y5, W-PAD, y5+155], fill=LGRAY, outline=BORDER, width=2) rrect(draw, [PAD, y5, W-PAD, y5+38], fill=NAVY, radius=12, outline=None) draw.rectangle([PAD, y5+26, W-PAD, y5+38], fill=NAVY) txt(draw, (W//2, y5+19), "PERFORMANCE METRICS", font(18,True), WHITE, "mm") metrics = [ ("False-Negative Rate\n(Reactive NST alone)", "~1.9 / 1,000", "fetal death within 1 week", GREEN), ("False-Negative Rate\n(Modified BPP = NST+AFI)", "~0.8 / 1,000", "NST + AFI combined", TEAL), ("PPV of Nonreactive NST\n(post-term pregnancies)", "~15%", "False-positives are COMMON", RED), ("PPV of Nonreactive NST\n(IUGR/FGR pregnancies)", "~69%", "Act promptly in IUGR", AMBER), ] M4W = (W - PAD*2 - 3*16) // 4 mx = PAD + 10 for title, val, sub, col in metrics: rrect(draw, [mx, y5+46, mx+M4W, y5+145], fill=WHITE, outline=col, width=2) ty = y5+58 for tl in title.split("\n"): txt(draw, (mx+M4W//2, ty), tl, font(13,True), col, "mm") ty += 20 txt(draw, (mx+M4W//2, ty+4), val, font(22,True), col, "mm") txt(draw, (mx+M4W//2, ty+32), sub, font(11), MGRAY, "mm") mx += M4W + 16 # ──────────────────────────────────────────────────────────────────────────── # ROW 6 — Modified BPP | BPP Components # ──────────────────────────────────────────────────────────────────────────── y6 = y5 + 168 BPP_H = 230 HALF_W = (W - PAD*2 - 20) // 2 # Modified BPP rrect(draw, [PAD, y6, PAD+HALF_W, y6+BPP_H], fill="#EEF2FF", outline=NAVY, width=2) rrect(draw, [PAD, y6, PAD+HALF_W, y6+38], fill=NAVY, radius=12, outline=None) draw.rectangle([PAD, y6+26, PAD+HALF_W, y6+38], fill=NAVY) txt(draw, (PAD+HALF_W//2, y6+19), "MODIFIED BPP", font(17,True), WHITE, "mm") mbpp = [ ("= NST + Amniotic Fluid Index (AFI)", True), ("", False), ("NST → Acute fetal CNS status (short-term)", False), ("AFI → Chronic uteroplacental reserve (long-term)", False), ("", False), ("AFI Normal: ≥ 5 cm (or MVP ≥ 2 cm)", False), ("AFI Low: < 5 cm → Oligohydramnios", False), ("", False), ("False-negative rate: 0.8 / 1,000", True), ("As effective as full BPP — preferred first-line", False), ] fy = y6 + 48 for text, bold in mbpp: txt(draw, (PAD+16, fy), text, font(13,bold), DGRAY if not bold else NAVY) fy += 18 # BPP Components bx = PAD + HALF_W + 20 rrect(draw, [bx, y6, bx+HALF_W, y6+BPP_H], fill="#F9F0FF", outline=PURPLE, width=2) rrect(draw, [bx, y6, bx+HALF_W, y6+38], fill=PURPLE, radius=12, outline=None) draw.rectangle([bx, y6+26, bx+HALF_W, y6+38], fill=PURPLE) txt(draw, (bx+HALF_W//2, y6+19), "BPP COMPONENTS (each = 2 points)", font(16,True), WHITE, "mm") bpp_comps = [ ("NST", "≥ 2 accel ≥15 bpm × ≥15 sec in 30 min window"), ("Breathing", "≥ 30 sec sustained fetal breathing in 30 min"), ("Movement", "≥ 3 discrete body / limb movements in 30 min"), ("Tone", "Extension + return to flexion of extremity / spine"), ("AFV", "≥ 1 amniotic fluid pocket ≥ 2×2 cm"), ] fy = y6 + 50 for comp, crit in bpp_comps: txt(draw, (bx+16, fy), f"· {comp}:", font(13,True), PURPLE) txt(draw, (bx+130, fy), crit, font(13), DGRAY) fy += 22 hline(draw, bx+16, fy+4, bx+HALF_W-16, BORDER, 1) fy += 14 scores = [("8 – 10", "Reassuring — continue plan", GREEN), ("6", "Suspicious — re-evaluate urgently", AMBER), ("≤ 4", "Ominous — immediate intervention", RED)] for sc, meaning, col in scores: txt(draw, (bx+16, fy), f"Score {sc}:", font(13,True), DGRAY) txt(draw, (bx+120, fy), meaning, font(13), col) fy += 20 # ──────────────────────────────────────────────────────────────────────────── # ROW 7 — VAS | Clinical Tips # ──────────────────────────────────────────────────────────────────────────── y7 = y6 + BPP_H + 18 VAS_H = 220 # VAS box rrect(draw, [PAD, y7, PAD+HALF_W, y7+VAS_H], fill="#E0F7FA", outline=TEAL, width=2) rrect(draw, [PAD, y7, PAD+HALF_W, y7+38], fill=TEAL, radius=12, outline=None) draw.rectangle([PAD, y7+26, PAD+HALF_W, y7+38], fill=TEAL) txt(draw, (PAD+HALF_W//2, y7+19), "VIBROACOUSTIC STIMULATION (VAS)", font(16,True), WHITE, "mm") vas = [ "· Handheld electro-larynx applied to maternal abdomen", "· 1–3 sec burst of vibration + broad-band sound (0.1–10 kHz)", "· Goal: state change from quiet sleep → active sleep", "· Vibration sense: fully developed by 22–24 weeks", "· Auditory response: ~25–26 weeks onward", "· Significantly shortens total monitoring time", "· VAS-generated accelerations carry EQUAL predictive", " value to spontaneous accelerations", "· On occasion, fetal bradycardia has been reported", "· NOT recommended if fetus already in active state", ] fy = y7 + 50 for ln in vas: txt(draw, (PAD+16, fy), ln, font(13), "#004D40") fy += 17 # Tips box tx = PAD + HALF_W + 20 rrect(draw, [tx, y7, tx+HALF_W, y7+VAS_H], fill="#FFFDE7", outline=AMBER, width=2) rrect(draw, [tx, y7, tx+HALF_W, y7+38], fill=AMBER, radius=12, outline=None) draw.rectangle([tx, y7+26, tx+HALF_W, y7+38], fill=AMBER) txt(draw, (tx+HALF_W//2, y7+19), "CLINICAL TIPS & PITFALLS", font(16,True), WHITE, "mm") tips = [ "⚑ Semi-recumbent position — NEVER supine", "⚑ Extend to 40–90 min before calling nonreactive", "⚑ Fetal sleep is the most common cause of NR NST", "⚑ Check all maternal medications before interpreting", "⚑ Variable decelerations in reactive NST → check AFI", "⚑ NR NST in IUGR: PPV 69% — escalate quickly", "⚑ NR NST post-term: PPV only 15% — extend test first", "⚑ Combine NST + AFI (modified BPP) for best accuracy", "⚑ Always document: baseline FHR, variability, accels,", " decelerations, fetal movement marks", ] fy = y7 + 50 for ln in tips: txt(draw, (tx+16, fy), ln, font(13), "#4E3100") fy += 17 # ──────────────────────────────────────────────────────────────────────────── # FOOTER # ──────────────────────────────────────────────────────────────────────────── y_foot = H - 50 draw.rectangle([0, y_foot, W, H], fill=NAVY) txt(draw, (W//2, y_foot+10), "Sources: Textbook of Family Medicine 9e · Creasy & Resnik's Maternal-Fetal Medicine · Pfenninger & Fowler's Procedures for Primary Care, 3rd ed.", font(12), "#90CAF9", "mm") txt(draw, (W//2, y_foot+30), "For educational use only — clinical decisions must be individualised based on current guidelines, gestational age, and patient context.", font(12), "#90CAF9", "mm") out = "/tmp/workspace/nst-guide/NST_Clinical_Reference_Guide.png" img.save(out, "PNG", dpi=(200,200)) print(f"Saved: {out} {W}x{H}px") PYEOF

Reading File
Running Command

python3 - <<'PYEOF' from PIL import Image, ImageDraw, ImageFont import os W, H = 1654, 2100 # trimmed height img = Image.new("RGB", (W, H), "#F7F9FC") draw = ImageDraw.Draw(img) NAVY="#1B3A6B"; TEAL="#006E7F"; GREEN="#1A7A4A"; RED="#C0392B" AMBER="#C97A00"; WHITE="#FFFFFF"; LGRAY="#EDF1F6"; DGRAY="#3A3A3A" MGRAY="#8A8A8A"; BORDER="#C5D0DA"; PURPLE="#6A1B9A" def font(size, bold=False): for name in ["DejaVuSans","LiberationSans"]: sfx="-Bold" if bold else "" for base in ["/usr/share/fonts/truetype/dejavu/","/usr/share/fonts/truetype/liberation/"]: p=base+name+sfx+".ttf" if os.path.exists(p): return ImageFont.truetype(p,size) return ImageFont.load_default() def rrect(d,xy,fill,r=14,ol=None,w=2): d.rounded_rectangle(xy,radius=r,fill=fill,outline=ol,width=w) def txt(d,pos,t,f,fill,a="la"): d.text(pos,t,font=f,fill=fill,anchor=a) def hdr(d,x,y,w,h,title,hcol): rrect(d,[x,y,x+w,y+h],fill=hcol,r=12); d.rectangle([x,y+h-12,x+w,y+h],fill=hcol) txt(d,(x+w//2,y+h//2),title,font(15,True),WHITE,"mm") PAD=32; MID=W//2+8; COL=MID-PAD-32 # ═══════════ HEADER ═══════════ rrect(draw,[0,0,W,120],fill=NAVY,r=0) txt(draw,(W//2,36),"NON-STRESS TEST (NST)",font(46,True),WHITE,"mm") txt(draw,(W//2,82),"CLINICAL INTERPRETATION · REFERENCE GUIDE",font(22),"#90CAF9","mm") draw.rectangle([0,120,W,130],fill=TEAL) # ═══════════ ROW 1 — What/Indications ═══════════ y0=142; BOX1H=215 for xb,title,lines,hc in [ (PAD,"WHAT IS THE NST?",[ "· External CTG — FHR upper tracing, UA lower tracing", "· Primary antepartum fetal surveillance tool", "· Reflects fetal autonomic nervous system integrity", "· Reflects adequacy of uteroplacental function", "· NO contraindications · Non-invasive · Inexpensive", "· Semi-recumbent position (never flat supine)", "· Mother presses button when fetal movement perceived", ],NAVY), (MID,"INDICATIONS",[ "· Gestational diabetes (GDM) — uncontrolled / new", "· Chronic hypertension / pre-eclampsia", "· Intrauterine growth restriction (IUGR / FGR)", "· Post-term pregnancy (≥ 41–42 weeks)", "· Decreased fetal movement (DFM)", "· Multiple gestation with complications", "· START: 32 wks low-risk · earlier for high-risk", ],TEAL), ]: rrect(draw,[xb,y0,xb+COL,y0+BOX1H],fill=LGRAY,ol=BORDER) hdr(draw,xb,y0,COL,36,title,hc) fy=y0+44 for ln in lines: txt(draw,(xb+14,fy),ln,font(13),DGRAY); fy+=22 # ═══════════ ROW 2 — Criteria ═══════════ y2=y0+BOX1H+18 txt(draw,(W//2,y2),"INTERPRETATION CRITERIA",font(19,True),NAVY,"mm"); y2+=24 CRIT_H=200; C3W=(W-PAD*2-28)//3 for xc,title,tcol,bgcol,lines in [ (PAD,"✓ REACTIVE (NORMAL)",GREEN,"#E8F5E9",[ "≥ 2 accelerations in a 20-min window","", "Each acceleration must have:", " · Peak ≥ 15 bpm above baseline FHR", " · Duration ≥ 15 seconds (< 2 minutes)","", "Preterm < 32 wks (modified criteria):", " · Peak ≥ 10 bpm above baseline", " · Duration ≥ 10 seconds", " · Normal FHR variability present", ]), (PAD+C3W+14,"⚠ NONREACTIVE",AMBER,"#FFF8E1",[ "Fails to meet reactive criteria","within the monitoring period","", "~10–12% of 3rd-trimester fetuses","at 30 min → < 6% by 40 min","", "Does NOT equal fetal jeopardy","", "→ Extend monitoring & apply VAS", "→ Nonreactive ≠ immediate delivery", ]), (PAD+2*(C3W+14),"✗ UNSATISFACTORY",RED,"#FDECEA",[ "Inadequate quality — uninterpretable","", "Causes:", " · Persistent poor signal", " · Cannot assess FHR baseline", " · Cannot detect accelerations","", "Action:", " · Reposition · Reapply probe", " · Repeat test · Escalate", ]), ]: rrect(draw,[xc,y2,xc+C3W,y2+CRIT_H],fill=bgcol,ol=tcol,w=3) hdr(draw,xc,y2,C3W,36,title,tcol) fy=y2+44 for ln in lines: txt(draw,(xc+14,fy),ln,font(12),DGRAY); fy+=15 # ═══════════ ROW 3 — Causes ═══════════ y3=y2+CRIT_H+18 rrect(draw,[PAD,y3,W-PAD,y3+200],fill=LGRAY,ol=BORDER) hdr(draw,PAD,y3,W-PAD*2,36,"CAUSES OF A NONREACTIVE NST",NAVY) causes=[ ("Fetal Sleep Cycle",["MOST COMMON","Quiet sleep 20–40 min","(up to 2 hrs near term)","→ Extend monitoring","→ Use VAS"]), ("Prematurity\n< 32 Weeks",["Immature autonomic","nervous system","→ Modified 10×10","criteria"]), ("Fetal Hypoxia /\nAcidemia",["Genuine compromise","CNS depression","→ Urgent BPP or","delivery assessment"]), ("Maternal\nMedications",["Opioids / narcotics","Magnesium sulfate","Beta-blockers","Corticosteroids"]), ("Fetal CNS /\nCardiac Anomaly",["Structural reason","for absent accel.","→ Targeted USS"]), ] CW5=(W-PAD*2-4*14)//5; cx5=PAD+12 for i,(title,desc) in enumerate(causes): fy=y3+46 for tl in title.split("\n"): txt(draw,(cx5,fy),tl,font(13,True),NAVY); fy+=18 draw.line([(cx5,fy),(cx5+CW5-16,fy)],fill=TEAL,width=1); fy+=8 for dl in desc: txt(draw,(cx5,fy),dl,font(12),DGRAY); fy+=15 if i<4: draw.line([(cx5+CW5+6,y3+40),(cx5+CW5+6,y3+192)],fill=BORDER,width=1) cx5+=CW5+14 # ═══════════ ROW 4 — Flowchart (compact) ═══════════ y4=y3+215 txt(draw,(W//2,y4),"MANAGEMENT ALGORITHM",font(19,True),NAVY,"mm"); y4+=24 FCW=280; FCH=40; cx_fc=W//2 def fc_box(cy,text,col,w=FCW,h=FCH): x0=cx_fc-w//2; x1=cx_fc+w//2 rrect(draw,[x0,cy,x1,cy+h],fill=col,r=10) txt(draw,(cx_fc,cy+h//2),text,font(13,True),WHITE,"mm"); return cy+h def arr(yf,yt,col=DGRAY,lbl=""): draw.line([(cx_fc,yf),(cx_fc,yt-10)],fill=col,width=2) draw.polygon([(cx_fc-7,yt-12),(cx_fc+7,yt-12),(cx_fc,yt)],fill=col) if lbl: txt(draw,(cx_fc+10,(yf+yt)//2-7),lbl,font(10),MGRAY) def diam(cy,text,col,w=280,h=50): pts=[(cx_fc,cy),(cx_fc+w//2,cy+h//2),(cx_fc,cy+h),(cx_fc-w//2,cy+h//2)] draw.polygon(pts,fill=col); draw.polygon(pts,outline="#555",width=2) txt(draw,(cx_fc,cy+h//2),text,font(13,True),WHITE,"mm"); return cy+h yf=y4 yf=fc_box(yf,"NST Ordered (≥ 32 weeks)",NAVY)+0; arr(yf,yf+16); yf+=16 yf=fc_box(yf,"Monitor for 20 Minutes",TEAL)+0; arr(yf,yf+16); yf+=16 d1t=yf; d1b=diam(yf,"REACTIVE?",AMBER) LX=cx_fc-310; RX=cx_fc+310 # YES draw.line([(cx_fc-140,d1t+25),(LX,d1t+25)],fill=GREEN,width=2) draw.line([(LX,d1t+25),(LX,d1t+90)],fill=GREEN,width=2) rrect(draw,[LX-130,d1t+90,LX+130,d1t+145],fill="#E8F5E9",ol=GREEN,w=2) txt(draw,(LX,d1t+107),"✓ REACTIVE",font(13,True),GREEN,"mm") txt(draw,(LX,d1t+127),"Reassuring — continue plan",font(11),"#1B5E20","mm") txt(draw,(cx_fc-175,d1t+8),"YES",font(11,True),GREEN) # NO draw.line([(cx_fc+140,d1t+25),(RX,d1t+25)],fill=RED,width=2) draw.line([(RX,d1t+25),(RX,d1t+90)],fill=RED,width=2) rrect(draw,[RX-140,d1t+90,RX+140,d1t+155],fill="#FDECEA",ol=RED,w=2) txt(draw,(RX,d1t+107),"Extend to 40–90 min",font(13,True),RED,"mm") txt(draw,(RX,d1t+128),"Apply Vibroacoustic Stim (VAS)",font(11),DGRAY,"mm") txt(draw,(cx_fc+145,d1t+8),"NO",font(11,True),RED) arr(d1b,d1b+22,RED,"Still nonreactive"); yf=d1b+22 d2t=yf; d2b=diam(yf,"STILL NONREACTIVE?",RED,w=300) # YES draw.line([(cx_fc-150,d2t+25),(LX,d2t+25)],fill=AMBER,width=2) draw.line([(LX,d2t+25),(LX,d2t+90)],fill=AMBER,width=2) rrect(draw,[LX-140,d2t+90,LX+140,d2t+160],fill="#FFF8E1",ol=AMBER,w=2) txt(draw,(LX,d2t+107),"Modified BPP",font(13,True),AMBER,"mm") txt(draw,(LX,d2t+127),"(NST + AFI)",font(12),DGRAY,"mm") txt(draw,(LX,d2t+145),"or Full BPP / CST",font(12),DGRAY,"mm") txt(draw,(cx_fc-178,d2t+8),"YES",font(11,True),AMBER) # NO draw.line([(cx_fc+150,d2t+25),(RX,d2t+25)],fill=GREEN,width=2) draw.line([(RX,d2t+25),(RX,d2t+90)],fill=GREEN,width=2) rrect(draw,[RX-140,d2t+90,RX+140,d2t+160],fill="#E8F5E9",ol=GREEN,w=2) txt(draw,(RX,d2t+110),"✓ Reassuring",font(13,True),GREEN,"mm") txt(draw,(RX,d2t+130),"Continue routine plan",font(12),"#1B5E20","mm") txt(draw,(cx_fc+155,d2t+8),"NO",font(11,True),GREEN) y5=d2b+18 # ═══════════ ROW 5 — Metrics ═══════════ rrect(draw,[PAD,y5,W-PAD,y5+140],fill=LGRAY,ol=BORDER) hdr(draw,PAD,y5,W-PAD*2,36,"PERFORMANCE METRICS",NAVY) metrics=[ ("False-Neg Rate\n(Reactive NST)","~1.9 / 1,000","fetal death within 1 wk",GREEN), ("False-Neg Rate\n(Modified BPP)","~0.8 / 1,000","NST + AFI combined",TEAL), ("PPV Nonreactive NST\n(post-term)","~15%","False-positives COMMON",RED), ("PPV Nonreactive NST\n(IUGR/FGR)","~69%","Act promptly in IUGR",AMBER), ] M4W=(W-PAD*2-3*16)//4; mx=PAD+8 for title,val,sub,col in metrics: rrect(draw,[mx,y5+44,mx+M4W,y5+132],fill=WHITE,ol=col,w=2) ty=y5+54 for tl in title.split("\n"): txt(draw,(mx+M4W//2,ty),tl,font(12,True),col,"mm"); ty+=18 txt(draw,(mx+M4W//2,ty+2),val,font(20,True),col,"mm") txt(draw,(mx+M4W//2,ty+28),sub,font(11),MGRAY,"mm") mx+=M4W+16 # ═══════════ ROW 6 — Mod BPP + BPP ═══════════ y6=y5+152; HALF_W=(W-PAD*2-20)//2; BPP_H=215 rrect(draw,[PAD,y6,PAD+HALF_W,y6+BPP_H],fill="#EEF2FF",ol=NAVY,w=2) hdr(draw,PAD,y6,HALF_W,36,"MODIFIED BPP = NST + AFI",NAVY) mbpp_lines=[("= NST + Amniotic Fluid Index (AFI)",True),("",False), ("NST → Acute fetal CNS status (short-term)",False), ("AFI → Chronic uteroplacental reserve (long-term)",False),("",False), ("AFI Normal: ≥ 5 cm (or MVP ≥ 2 cm)",False), ("AFI Low: < 5 cm → Oligohydramnios",False),("",False), ("False-negative rate: 0.8 / 1,000",True), ("As effective as full BPP — preferred first-line",False),] fy=y6+46 for t,b in mbpp_lines: txt(draw,(PAD+14,fy),t,font(13,b),NAVY if b else DGRAY); fy+=17 bx=PAD+HALF_W+20 rrect(draw,[bx,y6,bx+HALF_W,y6+BPP_H],fill="#F9F0FF",ol=PURPLE,w=2) hdr(draw,bx,y6,HALF_W,36,"BPP COMPONENTS (each = 2 points)",PURPLE) bpp_comps=[("NST","≥ 2 accel ≥15 bpm × ≥15 sec in 30 min"), ("Breathing","≥ 30 sec sustained fetal breathing"), ("Movement","≥ 3 discrete body / limb movements"), ("Tone","Extension + return to flexion of extremity"), ("AFV","≥ 1 amniotic fluid pocket ≥ 2×2 cm"),] fy=y6+46 for comp,crit in bpp_comps: txt(draw,(bx+14,fy),f"· {comp}:",font(13,True),PURPLE) txt(draw,(bx+120,fy),crit,font(13),DGRAY); fy+=20 draw.line([(bx+14,fy+4),(bx+HALF_W-14,fy+4)],fill=BORDER,width=1); fy+=14 for sc,meaning,col in [("8–10","Reassuring — continue plan",GREEN), ("6","Suspicious — re-evaluate urgently",AMBER), ("≤ 4","Ominous — immediate intervention",RED)]: txt(draw,(bx+14,fy),f"Score {sc}:",font(13,True),DGRAY) txt(draw,(bx+110,fy),meaning,font(13),col); fy+=18 # ═══════════ ROW 7 — VAS + Tips ═══════════ y7=y6+BPP_H+16; VAS_H=210 rrect(draw,[PAD,y7,PAD+HALF_W,y7+VAS_H],fill="#E0F7FA",ol=TEAL,w=2) hdr(draw,PAD,y7,HALF_W,36,"VIBROACOUSTIC STIMULATION (VAS)",TEAL) vas_lines=["· Handheld electro-larynx on maternal abdomen", "· 1–3 sec burst: vibration + sound (0.1–10 kHz)", "· Goal: quiet sleep → active sleep (state change)", "· Vibration sense developed by 22–24 weeks GA", "· Auditory response from ~25–26 weeks onward", "· Significantly shortens total monitoring time", "· VAS accelerations = equal predictive value to", " spontaneous accelerations (outcome equivalent)", "· Rare: fetal bradycardia reported", "· Avoid if fetus already in active state",] fy=y7+46 for ln in vas_lines: txt(draw,(PAD+14,fy),ln,font(13),"#004D40"); fy+=17 tx=PAD+HALF_W+20 rrect(draw,[tx,y7,tx+HALF_W,y7+VAS_H],fill="#FFFDE7",ol=AMBER,w=2) hdr(draw,tx,y7,HALF_W,36,"CLINICAL TIPS & PITFALLS",AMBER) tips_lines=["⚑ Semi-recumbent — NEVER flat supine", "⚑ Extend to 40–90 min before labelling nonreactive", "⚑ Fetal sleep is the MOST COMMON cause of NR NST", "⚑ Review all maternal medications before interpreting", "⚑ Variable decels in reactive NST → always check AFI", "⚑ NR NST in IUGR: PPV 69% — escalate quickly", "⚑ NR NST post-term: PPV 15% — extend before acting", "⚑ NST + AFI (modified BPP) gives best accuracy", "⚑ Always document baseline FHR, variability,", " accelerations, decelerations, fetal movement marks",] fy=y7+46 for ln in tips_lines: txt(draw,(tx+14,fy),ln,font(13),"#4E3100"); fy+=17 # ═══════════ FOOTER ═══════════ yf=H-46; draw.rectangle([0,yf,W,H],fill=NAVY) txt(draw,(W//2,yf+8), "Sources: Textbook of Family Medicine 9e · Creasy & Resnik's Maternal-Fetal Medicine · Pfenninger & Fowler's Procedures for Primary Care, 3rd ed.", font(11),"#90CAF9","mm") txt(draw,(W//2,yf+27), "For educational use only — clinical decisions must be individualised based on current guidelines, gestational age, and clinical context.", font(11),"#90CAF9","mm") out="/tmp/workspace/nst-guide/NST_Clinical_Reference_Guide.png" img.save(out,"PNG",dpi=(200,200)) print(f"Saved {out} {W}x{H}") PYEOF

Reading File
Running Command

python3 - <<'PYEOF' from PIL import Image img = Image.open("/tmp/workspace/nst-guide/NST_Clinical_Reference_Guide.png") # Crop to remove blank space at bottom - tips end around y=1950 cropped = img.crop((0, 0, 1654, 1970)) cropped.save("/tmp/workspace/nst-guide/NST_Clinical_Reference_Guide.png", "PNG", dpi=(200,200)) print(f"Cropped to: {cropped.size}") PYEOF

Your NST Clinical Reference Guide is ready:

NST Clinical Reference Guide

Image · PNG

The poster is 1654 × 1970 px at 200 DPI (prints cleanly on A3 or US tabloid). It covers:
SectionContents
What is the NST / IndicationsProcedure basics, start timing, clinical contexts
Interpretation CriteriaReactive · Nonreactive · Unsatisfactory — colour-coded green/amber/red
Causes of Nonreactive NST5-column breakdown: sleep, prematurity, hypoxia, medications, anomalies
Management AlgorithmStep-by-step flowchart from order → reactive/nonreactive → VAS → BPP
Performance Metrics4 key stats: FNR reactive NST, FNR modified BPP, PPV post-term, PPV IUGR
Modified BPPNST + AFI, AFI thresholds, false-negative rate
BPP ComponentsAll 5 components with normal criteria + scoring key
VASDevice, mechanism, developmental milestones, evidence note
Clinical Tips & Pitfalls10 key bedside reminders

What are deceleration?

Reading File
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Finding Sources
Reading File
Reading File
Reading File
Loading Image
Loading Image
Loading Image
Excellent - I now have rich textbook content and real CTG tracing images. Here is the comprehensive answer:

Fetal Heart Rate (FHR) Decelerations - Detailed Overview

Definition

A deceleration is a visually apparent, transient decrease in fetal heart rate (FHR) below the established baseline. Decelerations are classified by:
  1. Timing relative to uterine contractions
  2. Onset speed (gradual vs. abrupt)
  3. Clinical significance (benign vs. ominous)
They are quantified by the depth of the nadir (in bpm below baseline) and duration (onset to return to baseline). They are called recurrent when they occur with >50% of uterine contractions in any 20-minute period. - Creasy & Resnik's Maternal-Fetal Medicine

Normal FHR Context

Normal FHR tracing (the baseline from which decelerations are measured):
Normal fetal heart rate pattern - ~130 bpm with normal variability and no periodic changes
Normal fetal heart rate tracing: ~130 bpm, normal variability (~15 bpm amplitude), no decelerations. The lower channel shows uterine contractions (2-3 min apart, 60-70 mmHg). This represents a non-acidemic fetus without hypoxic stress.
  • Normal baseline: 110-160 bpm
  • Normal variability amplitude: 6-25 bpm

Types of Decelerations

1. Early Decelerations

FeatureDetail
TimingOnset, nadir, and recovery coincide with beginning, peak, and end of contraction
OnsetGradual (≥30 sec from onset to nadir)
ShapeMirror image of the contraction - smooth, uniform
DepthUsually shallow
MechanismVagal response to fetal head compression during contraction → transient changes in intracranial pressure and/or cerebral blood flow
Clinical significanceBENIGN - not associated with fetal hypoxia, acidemia, or hypoxic-ischemic neurologic injury
ActionNone required - reassuring finding
"Early decelerations occur secondary to changes in vagal tone after brief hypoxic episodes or head compression and are benign." - The Harriet Lane Handbook, 23rd ed.

2. Late Decelerations

FeatureDetail
TimingOnset, nadir, and recovery all occur AFTER the beginning, peak, and ending of the contraction
OnsetGradual (≥30 sec from onset to nadir)
ShapeSmooth, uniform curve - delayed mirror of contraction
DepthOften shallow but significant regardless of amplitude
MechanismTwo subtypes (see below)
Clinical significanceWORRISOME / OMINOUS - indicate uteroplacental insufficiency and fetal distress
Two Subtypes of Late Decelerations:
A. Reflex Late Decelerations (with normal variability)
  • Mechanism: Acute reduction in uterine blood flow → relatively deoxygenated blood reaches fetal chemoreceptors → vagal discharge → transient FHR decrease
  • Fetal CNS intact; vital organs physiologically compensated
  • Seen with: maternal hypotension (e.g., after epidural/spinal), acute uterine hyperstimulation
B. Nonreflex Late Decelerations (with absent variability)
  • Mechanism: Chronically deoxygenated fetus → direct myocardial depression + vagal activity during contractions
  • Signifies fetal decompensation - inadequate cerebral AND myocardial oxygenation
  • Seen with: preeclampsia, IUGR, prolonged hypoxic stress
  • Scalp pH often acidemic (e.g., 7.07)
Reflex late decelerations - note FHR dips after each contraction peak, but variability is preserved:
Reflex late decelerations after maternal hypotension from caudal anesthesia
Nonreflex late decelerations - late dips with virtually absent variability - fetal scalp pH = 7.07:
Nonreflex late decelerations with absent variability and acidemia pH 7.07
"Late decelerations result from uteroplacental insufficiency and indicate fetal distress." - Harriet Lane Handbook, 23rd ed.

3. Variable Decelerations

FeatureDetail
TimingNo uniform temporal relationship to contractions - onset, depth, and duration vary with successive contractions
OnsetAbrupt (<30 sec from onset to beginning of nadir)
Depth≥15 bpm below baseline
Duration≥15 seconds but <2 minutes (baseline to baseline)
MechanismUmbilical cord compression → acute increase in fetal blood pressure → baroreceptor-mediated vagal response
AppearanceCharacteristic "V" or "W" shape; may have "shoulders" (brief acceleration before and after)
Clinical significanceVariable - depends on depth, duration, and FHR variability between decelerations
Severity classification of variable decelerations:
  • Mild: Brief, shallow, or lasting <30 sec regardless of depth
  • Moderate: Mixed features
  • Severe: FHR drops to <60 bpm for ≥60 seconds with slow recovery to baseline
"Variable decelerations represent umbilical cord compression... considered severe when heart rate drops to <60 bpm for about 60 seconds, with a slow recovery to baseline." - Harriet Lane Handbook, 23rd ed.
Concerning features in otherwise variable decelerations (Category II markers):
  • Slow return to baseline
  • Overshoot (rebound acceleration after)
  • Loss of shoulders
  • Accompanied by minimal or absent baseline variability

4. Prolonged Decelerations

FeatureDetail
DefinitionAbrupt decrease ≥15 bpm below baseline lasting ≥2 minutes but <10 minutes
If ≥10 minutesReclassified as a baseline change (bradycardia)
MechanismSame physiologic mechanisms as late or variable decelerations but sustained longer - interrupting fetal oxygenation for an extended period
Clinical significanceAssociated with increasing acidemia risk as duration extends; if bradycardia develops → increased risk of NICU admission

Summary Comparison Table

FeatureEarlyLateVariableProlonged
Onset speedGradualGradualAbrupt (<30s)Abrupt
Timing vs. contractionCoincidentAfter peak (delayed)Variable / irregularAny time
MechanismHead compression / vagalUteroplacental insufficiencyCord compressionLate/variable sustained
VariabilityNormalNormal (reflex) or Absent (nonreflex)Often normal betweenVaries
Clinical dangerBenignWorrisome/OminousDepends on severityConcerning
ActionNoneUrgent evaluationContext-dependentUrgent evaluation

The Three-Tier FHR Classification System (NICHD 2008)

Decelerations are interpreted within the overall CTG context using the NICHD three-tier system:

Category I (Normal - reassuring)

  • Baseline 110-160 bpm
  • Moderate variability
  • Late or variable decelerations: ABSENT
  • Early decelerations: present or absent
  • Accelerations: present or absent

Category II (Indeterminate - requires surveillance)

  • Recurrent variable decelerations with minimal/moderate variability
  • Prolonged deceleration >2 min but <10 min
  • Recurrent late decelerations with moderate variability
  • Variable decelerations with slow return, overshoots, or shoulders
  • Does NOT predict abnormal fetal acid-base status but requires close monitoring

Category III (Abnormal - requires prompt action)

  • Absent baseline variability WITH any of:
    • Recurrent late decelerations
    • Recurrent variable decelerations
    • Bradycardia
  • OR Sinusoidal pattern
  • Requires immediate evaluation and intervention
Source: 2008 NICHD Workshop Report on Electronic Fetal Monitoring, as cited in Creasy & Resnik's Maternal-Fetal Medicine

Key Clinical Points

  • Variability is the most important modifier - a deceleration with normal variability is far less concerning than the same deceleration with absent variability
  • Context matters - a single variable deceleration in an otherwise normal tracing is very different from recurrent late decelerations in an IUGR patient
  • EFM has a high false-positive rate for predicting fetal compromise - the PPV of a nonreassuring FHR tracing for cerebral palsy is only ~0.14% (false-positive rate >99%)
  • Recurrent = occurring with >50% of contractions in any 20-minute window
This is a shared conversation. Sign in to Orris to start your own chat.