{"spec_id":"ecg-twelve-lead","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\necg-twelve-lead: ECG/EKG 12-Lead Waveform Display\nLibrary: matplotlib 3.11.0 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-06-17\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport matplotlib.ticker as ticker\nimport numpy as np\n\n\n# Theme-adaptive chrome (Imprint palette + theme tokens)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# ECG trace — Imprint brand green (position 1), the classic cardiac-monitor hue\nTRACE = \"#009E73\"\n# ECG paper grid — domain-standard red ruling, dialed for each surface\nGRID_MINOR = \"#ECB7AD\" if THEME == \"light\" else \"#3E2A27\"\nGRID_MAJOR = \"#D9907F\" if THEME == \"light\" else \"#5E3D37\"\n\nnp.random.seed(42)\n\n# Data — synthetic ECG using a Gaussian-based P-QRS-T model\nsampling_rate = 1000\nduration = 2.5\nt = np.linspace(0, duration, int(sampling_rate * duration), endpoint=False)\n\n# Beat parameters: (amplitude mV, center within beat s, width s)\nwave_params = [\n    (0.15, 0.16, 0.025),  # P wave\n    (-0.10, 0.24, 0.008),  # Q wave\n    (1.00, 0.26, 0.012),  # R wave\n    (-0.20, 0.28, 0.008),  # S wave\n    (0.25, 0.40, 0.040),  # T wave\n]\n\nbeat_period = 0.8  # ~75 bpm\nn_beats = int(np.ceil(duration / beat_period)) + 1\n\n\ndef build_signal(time_axis):\n    \"\"\"Sum Gaussian P-QRS-T peaks across beats, plus baseline wander + noise.\"\"\"\n    n = int(np.ceil(time_axis[-1] / beat_period)) + 1\n    sig = np.zeros_like(time_axis)\n    for i in range(n):\n        ts = time_axis - i * beat_period\n        for amp, center, width in wave_params:\n            sig += amp * np.exp(-((ts - center) ** 2) / (2 * width**2))\n    sig += 0.02 * np.sin(2 * np.pi * 0.3 * time_axis)\n    sig += np.random.normal(0, 0.01, len(time_axis))\n    return sig\n\n\nsignal_template = build_signal(t)\n\n# Lead-specific scaling (scale_factor, invert_flag) for realistic morphology\nlead_config = {\n    \"I\": (0.8, False),\n    \"II\": (1.0, False),\n    \"III\": (0.6, False),\n    \"aVR\": (0.7, True),\n    \"aVL\": (0.4, False),\n    \"aVF\": (0.8, False),\n    \"V1\": (0.5, True),\n    \"V2\": (0.9, False),\n    \"V3\": (1.1, False),\n    \"V4\": (1.2, False),\n    \"V5\": (1.0, False),\n    \"V6\": (0.8, False),\n}\n\nleads = {}\nfor name, (scale, invert) in lead_config.items():\n    sig = signal_template * scale\n    sig += np.random.normal(0, 0.005, len(t))\n    leads[name] = -sig if invert else sig\n\n# Rhythm strip: 10 s of Lead II\nt_long = np.linspace(0, 10.0, int(sampling_rate * 10.0), endpoint=False)\nsignal_long = build_signal(t_long)\n\n# Standard clinical 3x4 grid layout\ngrid_layout = [[\"I\", \"aVR\", \"V1\", \"V4\"], [\"II\", \"aVL\", \"V2\", \"V5\"], [\"III\", \"aVF\", \"V3\", \"V6\"]]\n\n# Plot — landscape 3200x1800 (figsize 8x4.5 @ dpi 400), no bbox_inches=\"tight\"\nfig = plt.figure(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\ngs = fig.add_gridspec(4, 4, hspace=0.10, wspace=0.06, left=0.025, right=0.99, top=0.90, bottom=0.04)\n\n\ndef setup_ecg_grid(ax, x_min, x_max, y_min=-1.5, y_max=1.8):\n    \"\"\"ECG paper grid via matplotlib's native tick/grid system.\"\"\"\n    ax.set_facecolor(PAGE_BG)\n    ax.set_xlim(x_min, x_max)\n    ax.set_ylim(y_min, y_max)\n\n    # Major grid at 5 mm (0.2 s / 0.5 mV); minor grid at 1 mm (0.04 s / 0.1 mV)\n    ax.xaxis.set_major_locator(ticker.MultipleLocator(0.2))\n    ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5))\n    ax.xaxis.set_minor_locator(ticker.MultipleLocator(0.04))\n    ax.yaxis.set_minor_locator(ticker.MultipleLocator(0.1))\n\n    ax.grid(which=\"minor\", color=GRID_MINOR, linewidth=0.35)\n    ax.grid(which=\"major\", color=GRID_MAJOR, linewidth=0.7)\n\n    ax.set_xticklabels([])\n    ax.set_yticklabels([])\n    ax.tick_params(axis=\"both\", length=0)\n    for spine in ax.spines.values():\n        spine.set_linewidth(0.5)\n        spine.set_color(GRID_MAJOR)\n\n\nfor row_idx, row_leads in enumerate(grid_layout):\n    for col_idx, lead_name in enumerate(row_leads):\n        ax = fig.add_subplot(gs[row_idx, col_idx])\n        setup_ecg_grid(ax, 0, duration)\n        ax.plot(t, leads[lead_name], color=TRACE, linewidth=1.4)\n        ax.text(\n            0.025,\n            0.94,\n            lead_name,\n            transform=ax.transAxes,\n            fontsize=11,\n            fontweight=\"bold\",\n            color=INK,\n            va=\"top\",\n            bbox={\"boxstyle\": \"square,pad=0.15\", \"facecolor\": PAGE_BG, \"edgecolor\": \"none\", \"alpha\": 0.85},\n        )\n\n# Rhythm strip (Lead II, full width)\nax_rhythm = fig.add_subplot(gs[3, :])\nsetup_ecg_grid(ax_rhythm, 0, 10.0)\nax_rhythm.plot(t_long, signal_long, color=TRACE, linewidth=1.4)\nax_rhythm.text(\n    0.004,\n    0.94,\n    \"II (rhythm)\",\n    transform=ax_rhythm.transAxes,\n    fontsize=11,\n    fontweight=\"bold\",\n    color=INK,\n    va=\"top\",\n    bbox={\"boxstyle\": \"square,pad=0.15\", \"facecolor\": PAGE_BG, \"edgecolor\": \"none\", \"alpha\": 0.85},\n)\n\n# 1 mV calibration pulse\nax_rhythm.plot([0.0, 0.0, 0.2, 0.2], [-1.2, -0.2, -0.2, -1.2], color=INK, linewidth=1.6)\nax_rhythm.text(0.10, 0.02, \"1 mV\", fontsize=9, ha=\"center\", color=INK_SOFT, fontweight=\"medium\")\n\n# Heart-rate annotation\nax_rhythm.text(\n    0.996,\n    0.94,\n    \"HR: ~75 bpm\",\n    transform=ax_rhythm.transAxes,\n    fontsize=9,\n    ha=\"right\",\n    va=\"top\",\n    color=INK_MUTED,\n    fontstyle=\"italic\",\n)\n\n# Title + metadata\nfig.suptitle(\n    \"ecg-twelve-lead · python · matplotlib · anyplot.ai\",\n    fontsize=12,\n    fontweight=\"medium\",\n    x=0.025,\n    y=0.965,\n    ha=\"left\",\n    color=INK,\n)\nfig.text(0.99, 0.962, \"25 mm/s · 10 mm/mV · Normal Sinus Rhythm\", fontsize=9, ha=\"right\", va=\"top\", color=INK_SOFT)\n\n# Save — theme-suffixed PNG, bbox_inches stays default (None)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}