📖 CardioAnnotate Help

A Toolkit for ECG Peak Labeling and Processing

📑 Table of Contents

🏠 1. Overview & Getting Started

CardioAnnotate is a browser-based ECG annotation toolkit that allows you to:

💡 100% Privacy-Preserving

All processing happens in your browser using Pyodide (Python in WebAssembly). No data is uploaded to any server.

Peak Types

R-peaks — QRS complex maxima (with beat classification)
P-peaks — Atrial depolarization
Q-waves — QRS onset (minima)
S-waves — QRS termination (minima)
T-peaks — Ventricular repolarization
U-waves — Late repolarization

📂 2. Loading ECG Data

Option A: Load WFDB Files

  1. Click "Choose Files" in the Load section
  2. Select both .hea (header) and .dat or .mat (signal) files with the same basename
    • Example: 100.hea + 100.dat
  3. The signal loads automatically after the file selection is complete

💡 Where to Find WFDB Files

Download ECG databases from PhysioNet (MIT-BIH, LUDB, INCART, etc.)

Loading Status

A small spinner appears next to the signal file input while a signal is loading. It disappears automatically when loading finishes, whether the file loads successfully or an error is reported.

Option B: Load CSV Files

  1. Click "Load" button in the "Import from CSV" row
  2. Select a CSV file containing ECG data

CSV Requirements:

Option C: Load NPZ Bundle

  1. Click "Load" button in the "Import bundle (.npz)" row
  2. Select an NPZ file previously exported from CardioAnnotate

💡 NPZ Bundles

NPZ bundles are a convenient format that packages the processed signal data, sampling frequency, channel names, and all peak annotations into a single compressed file. Use a full session export when you need to restore the complete workspace, including settings, view window, corrections, and computed results.

Option D: Load Full Session

  1. Click the session import control in the Session section
  2. Select a CardioAnnotate session file in .json, .mat, or .npz format
  3. The file format is detected automatically, and the workspace is restored from the saved session state

Full Session Restore

Session import restores the signal viewer, annotations, RR corrections, processing settings, detector settings, selected channel, zoom window, and saved analysis results. SQI, HRV, and stationarity panels are recomputed after import so that the displayed values reproduce the saved session.

📁 3. File Formats (Input & Output)

3.1 Input File Formats

WFDB Format (.hea + .dat/.mat)

The PhysioNet standard format for physiological signals, consisting of two files:

Header File (.hea) Structure:

record_name n_signals sampling_frequency n_samples
filename format gain baseline units resolution 0 0 channel_name
filename format gain baseline units resolution 0 0 channel_name
...
# Optional comment lines starting with #

Example .hea file:

100 2 360 650000
100.dat 212 200 1024 mV 11 0 0 MLII
100.dat 212 200 1024 mV 11 0 0 V5
# Age: 69  Sex: M

Field descriptions:

FieldDescriptionExample
record_nameBase name of the record100
n_signalsNumber of channels2
sampling_frequencySamples per second (Hz)360
n_samplesTotal samples per channel650000
filenameSignal data file name100.dat
formatStorage format (212=12-bit, 16=16-bit, 80=8-bit offset)212
gainADC units per physical unit200
baselineADC zero value1024
unitsPhysical unitsmV
resolutionADC resolution in bits11
channel_nameSignal/lead nameMLII

Signal File (.dat) Structure:

  • Binary file containing interleaved signal samples
  • Format 212: 12-bit samples, 2 samples packed in 3 bytes (little-endian)
  • Format 16: 16-bit signed integers, 2 bytes per sample (little-endian)
  • Format 80: 8-bit unsigned with offset 128
  • Physical value = (ADC_value − baseline) / gain

Format 212 Byte Layout

Two 12-bit samples (A, B) stored in 3 bytes:

Byte0 = A[7:0], Byte1 = B[3:0]|A[11:8], Byte2 = B[11:4]

CSV Format (.csv)

Comma-separated values with a header row.

Structure:

fs,time_s,MLII,V1,V5
360,0.000000,0.123,-0.045,0.089
360,0.002778,0.125,-0.043,0.091
360,0.005556,0.128,-0.041,0.094
360,0.008333,0.132,-0.038,0.098
...

Required columns (one of the following):

Column Name(s)Description
fs, sampling_rate, or sampling_frequencySampling rate in Hz (same value repeated each row)
time_s or timeTime in seconds (fs calculated from Δtime)

Signal columns:

  • Any numeric column not in the exclude list becomes a channel
  • Excluded column names: fs, sampling_rate, sampling_frequency, time_s, time, index, sample
  • Values should be in physical units (e.g., mV)

NPZ Bundle Format (.npz)

Compressed NumPy archive containing signal data and metadata.

Structure:

{
  "signals": numpy.ndarray,     // Shape: (n_channels, n_samples), dtype: float64
  "meta": JSON string           // Metadata as serialized JSON
}

Meta JSON structure:

{
  "fs": 360,                              // Sampling frequency in Hz
  "channelNames": ["MLII", "V1", "V5"],   // Array of channel names
  "units": ["mV", "mV", "mV"],            // Units per channel (optional)
  "recordBase": "100",                    // Original record name (optional)
  "annotations": {                        // Peak annotations
    "R": {
      "0": [                              // Channel index as string key
        {"index": 77, "type": "N"},       // R-peaks: objects with index and type
        {"index": 370, "type": "N"},
        {"index": 662, "type": "V"}
      ],
      "1": [{"index": 79, "type": "N"}]
    },
    "P": {
      "0": [30, 320, 610],                // Other peaks: arrays of sample indices
      "1": [32, 322]
    },
    "Q": {"0": [70, 363, 655]},
    "S": {"0": [85, 378, 670]},
    "T": {"0": [180, 475, 765]},
    "U": {"0": []}
  }
}

Legacy bundles that use peaks for the annotation map may still be loaded when supported by the importer.

Annotation JSON Format (.json)

JSON file containing peak annotations only (no signal data).

Structure:

{
  "fs": 360,
  "channelNames": ["MLII", "V1"],
  "annotations": {
    "R": {
      "0": [
        {"index": 77, "type": "N"},
        {"index": 370, "type": "N"},
        {"index": 662, "type": "V"},
        {"index": 955, "type": "A"}
      ],
      "1": [
        {"index": 79, "type": "N"},
        {"index": 372, "type": "N"}
      ]
    },
    "P": {
      "0": [30, 320, 610, 905],
      "1": [32, 322, 612]
    },
    "Q": {
      "0": [70, 363, 655, 948],
      "1": []
    },
    "S": {
      "0": [85, 378, 670, 963],
      "1": []
    },
    "T": {
      "0": [180, 475, 765, 1060],
      "1": [182, 477]
    },
    "U": {
      "0": [],
      "1": []
    }
  }
}

Key points:

  • The top-level annotation map is named annotations, following the ecg-annotator-json-v2 schema
  • R-peaks: Array of objects with {"index": sample_number, "type": "beat_code"}
  • P, Q, S, T, U peaks: Arrays of sample indices (integers)
  • Channel indices are string keys ("0", "1", "2", ...)
  • JSON files that use the legacy top-level peaks key are also supported for import
  • Legacy format (R-peaks as simple integer arrays) is also supported for import

Annotation CSV Format (.csv)

Tabular format for peak annotations.

Structure:

channel_index,label,index,time_s,beat_type
0,R,77,0.214,N
0,R,370,1.028,N
0,R,662,1.839,V
0,R,955,2.653,A
0,P,30,0.083,
0,P,320,0.889,
0,Q,70,0.194,
0,S,85,0.236,
0,T,180,0.500,
1,R,79,0.219,N
1,P,32,0.089,

Column descriptions:

ColumnRequiredDescription
channel_indexYesZero-indexed channel number
labelYesPeak type: R, P, Q, S, T, or U
indexYes*Sample index (integer)
time_sYes*Time in seconds (alternative to index)
beat_typeNoWFDB beat code for R-peaks (N, V, A, L, R, etc.)

*Either index or time_s is required.

WFDB Annotation Import Files (.atr / wave annotations)

WFDB annotation files can be imported after a signal is loaded. The file picker accepts all file extensions so that non-standard WFDB annotation extensions remain selectable.

Supported import forms:

File TypeImport Behavior
.atrSymbol-based import. Beat annotation codes such as N, L, V, A, and related WFDB beat codes are imported as R-peaks with the beat type preserved. Wave symbols are imported as p → P, t → T, u → U, q → Q, and s → S.
.qrsAll annotations in the file are imported as R-peaks.
.pwaveAll annotations in the file are imported as P-peaks.
.twaveAll annotations in the file are imported as T-peaks.
.uwaveAll annotations in the file are imported as U-waves.
.qwaveAll annotations in the file are imported as Q-waves.
.swaveAll annotations in the file are imported as S-waves.

Recognized extension variants are mapped by their extension name. Imported WFDB annotations are merged into all loaded channels.

3.2 Output File Formats

Annotation JSON Export

Same structure as input JSON, with added export timestamp and the annotations top-level key.

Structure:

{
  "exportedAt": "2026-01-15T10:30:00.000Z",
  "fs": 360,
  "channelNames": ["MLII", "V1"],
  "annotations": {
    "R": {
      "0": [
        {"index": 77, "type": "N"},
        {"index": 370, "type": "V"}
      ]
    },
    "P": {"0": [30, 320]},
    "Q": {"0": [70, 363]},
    "S": {"0": [85, 378]},
    "T": {"0": [180, 475]},
    "U": {"0": []}
  }
}

Annotation CSV Export

Tabular export with full metadata per peak.

Structure:

peak_type,channel_index,channel_name,sample_index,time_s,beat_type
R,0,MLII,77,0.214,N
R,0,MLII,370,1.028,V
R,0,MLII,662,1.839,A
P,0,MLII,30,0.083,
P,0,MLII,320,0.889,
Q,0,MLII,70,0.194,
S,0,MLII,85,0.236,
T,0,MLII,180,0.500,
R,1,V1,79,0.219,N

Column descriptions:

ColumnDescription
peak_typeR, P, Q, S, T, or U
channel_indexZero-indexed channel number
channel_nameHuman-readable channel label
sample_indexPeak location in samples
time_sPeak time = sample_index / fs
beat_typeWFDB code (R-peaks only, empty for others)

ECG Signal CSV Export

Exports the current (possibly processed) signal data.

Structure:

fs,time_s,MLII,V1,V5
360,0.000000,0.123,-0.045,0.089
360,0.002778,0.125,-0.043,0.091
360,0.005556,0.128,-0.041,0.094
360,0.008333,0.132,-0.038,0.098
...

Notes:

  • fs column contains the sampling frequency (repeated each row)
  • time_s is computed as sample_index / fs
  • Signal columns contain values after any processing (filtering, resampling)

NPZ Bundle Export

Signal-and-annotation bundle with processed signals and all annotations.

Structure:

numpy.savez_compressed("export.npz",
  signals=numpy.array(...),  // Shape: (n_channels, n_samples), dtype: float64
  meta=json.dumps({
    "fs": 360,
    "channelNames": ["MLII", "V1"],
    "units": ["mV", "mV"],
    "recordBase": "100",
    "annotations": { ... }   // Same structure as annotation JSON
  })
)

3.3 Full Session Format

A full session file stores the complete state of an analysis in a single, self-contained container. Use this format when an analysis must be paused and resumed, archived, shared with collaborators, or reopened exactly as it was left.

Recorded Session State

State CategoryWhat Is Stored
Signal dataThe current processed waveform for every channel and the original unmodified signal, together with sampling frequency, channel names, physical units, and record identifier. Retaining both processed and original signals keeps filtering, resampling, inversion, and reset available after reload.
Annotations and correctionsAll fiducial annotations, R-peak beat-type labels, RR intervals flagged by quality control, and any RR series corrected by Gaussian Process regression.
Processing and analysis settingsFiltering and resampling parameters, per-peak-type detector settings for Find Peaks and Pan-Tompkins, RR quality-control rules, selected GP kernel and hyperparameters, SQI parameters, and interface-control state.
View and selectionThe active channel and current zoom window. Because SQI and HRV are evaluated on the visible segment, saving the window fixes the exact sample range used for segment-level analysis.
Computed resultsA provenance snapshot of reported measures, including pSQI, kSQI, basSQI, lineSQI; time- and frequency-domain HRV metrics; the ADF stationarity test; and summary statistics.

Session Container Formats

Sessions can be exported as .json, .mat, or .npz. All three contain the same analysis state. In the .mat and .npz containers, signal arrays are stored as native numeric arrays for direct use in MATLAB or Python, while annotations, settings, view state, corrections, and results are embedded as a JSON record in the same container.

Session JSON structure (conceptual):

{
  "sessionVersion": "full-state-v1",
  "exportedAt": "2026-01-15T10:30:00.000Z",
  "signals": {
    "processed": [[...]],
    "original": [[...]],
    "fs": 360,
    "channelNames": ["MLII", "V1"],
    "units": ["mV", "mV"],
    "recordBase": "100"
  },
  "annotations": { ... },
  "corrections": {
    "flaggedRR": [...],
    "gpCorrectedRR": [...]
  },
  "settings": { ... },
  "view": {
    "activeChannel": 0,
    "xRange": [0.0, 10.5]
  },
  "results": {
    "sqi": { ... },
    "hrv": { ... },
    "adf": { ... },
    "summary": { ... }
  }
}

Export Type Coverage

State Annotations
(JSON/CSV)
Signal Bundle
(NPZ)
Session
(full state)
Signal data (processed + original)
Peaks and beat-type labels
Flagged / GP-corrected RR
Processing and detector settings
View / segment selection
Computed results (SQI, HRV, ADF)

💡 When to Use a Full Session

Use annotation exports for peak labels only, NPZ bundles for signal-plus-annotation exchange, and full sessions when the workspace must reopen with the same processing pipeline, correction state, view window, and analysis panels.

WFDB Export

PhysioNet-compatible format with header, signal, and annotation files.

Files created:

Header file (record.hea):

export 2 360 650000
export.dat 16 200(0) 0 16 0 0 0 MLII
export.dat 16 200(0) 0 16 0 0 0 V1

Signal file (record.dat):

  • Format 16: 16-bit signed integers, little-endian
  • Gain: 200 ADC units per mV (fixed)
  • Baseline: 0
  • Channels interleaved sample-by-sample

Annotation file(s) - Single file mode (.atr):

Binary WFDB annotation format containing:
- Sample number (delta-encoded)
- Annotation type code
- Subtype (beat classification for R-peaks)
- Channel number

Peak type to WFDB code mapping:

Peak TypeWFDB CodeDescription
R-peaksN, V, A, L, R, etc.Uses assigned beat type
P-peakspP-wave peak
T-peakstT-wave peak
U-wavesuU-wave
Q-waves(Waveform onset
S-waves)Waveform end

Annotation files - Separate files mode:

File ExtensionContents
record.atrR-peaks with beat type annotations
record.pwaveP-peaks
record.qwaveQ-waves
record.swaveS-waves
record.twaveT-peaks
record.uwaveU-waves

✏️ 4. Peak Annotation

Basic Annotation Workflow

  1. Select a Channel from the dropdown
  2. Use the mouse scroll wheel or plot controls to zoom into a region of interest
  3. Select an annotation Mode:
    • Navigate — Pan and zoom only
    • Add R (Normal) — Click to add R-peaks (default type: Normal)
    • Add P — Click to add P-peaks
    • Add T — Click to add T-peaks
    • Add U — Click to add U-waves
    • Add Q — Click to add Q-waves
    • Add S — Click to add S-waves
    • Remove nearest — Click near a peak to delete it
  4. Click on the signal where you want to place peaks

💡 Zoom Persists

Your zoom state is preserved when switching channels or annotation modes. The plot remembers where you were working.

Snap to Peak

After placing peaks approximately, you can refine them automatically:

  1. Enter a window size (e.g., 4-10 samples) in the "Snap peaks" field
  2. Click "🎯 Adjust"
  3. All R, P, T, and U peaks will snap to the local maximum within the window

⚠️ Q and S Waves Are Not Snapped

Q and S waves are typically minima or inflection points, so they are excluded from the snap function. These must be placed precisely by hand.

Undo Annotations

↶ Remove Last

Click "↶ Remove Last" to undo the most recent peak annotation. This button removes peaks in the reverse order they were added, acting as a simple undo function.

Clear All Peaks

Click "Clear peaks" to remove all R, P, Q, S, T, and U annotations. A confirmation dialog will appear.

Invert ECG

Click "Invert ECG" to multiply all signal values by -1. Useful for signals with inverted polarity.

4.1 R-Peak Beat Classification

CardioAnnotate supports comprehensive beat-by-beat R-peak classification following WFDB annotation standards. Each R-peak can be classified into one of 19 different beat types for arrhythmia analysis.

How to Classify R-Peaks

Method 1: Quick Classify Buttons

After adding an R-peak, use the quick classify buttons for common types:

  • N — Normal beat
  • V — Premature ventricular contraction (PVC)
  • A — Atrial premature contraction (APC)
  • L — Left bundle branch block (LBBB)
  • R — Right bundle branch block (RBBB)

Simply add an R-peak, then click the appropriate quick classify button.

Method 2: Full Classification Panel

Click "More..." to access the complete classification panel with all 19 beat types. The panel shows:

  • Current beat type and sample position
  • Full list of WFDB annotation codes
  • Beat type descriptions

Method 3: Right-Click Classification

Right-click near any R-peak on the plot to open the classification panel for that specific peak. This is the fastest way to classify previously-added R-peaks.

Supported Beat Types

Code Name Category Description
NNormalNormalNormal beat (default)
LLBBBBundle BranchLeft bundle branch block beat
RRBBBBundle BranchRight bundle branch block beat
BBBBBundle BranchBundle branch block (unspecified)
AAPCSupraventricularAtrial premature contraction
aAberr APCSupraventricularAberrated atrial premature beat
JNPCSupraventricularNodal (junctional) premature beat
SSVPBSupraventricularSupraventricular premature beat
VPVCVentricularPremature ventricular contraction
rR-on-TVentricularR-on-T premature ventricular contraction
FFusionVentricularFusion of ventricular and normal beat
EV EscapeVentricularVentricular escape beat
eA EscapeEscapeAtrial escape beat
jN EscapeEscapeNodal (junctional) escape beat
nSV EscapeEscapeSupraventricular escape beat
/PacedPacedPaced beat
fPaced FusPacedFusion of paced and normal beat
QUnknownUnknownUnclassifiable beat
?LearnUnknownBeat not classified during learning

Visual Feedback

R-peaks are color-coded by beat type on the plot:

  • Red — Normal beats
  • Amber — Bundle branch blocks
  • Emerald — Supraventricular beats
  • Dark Red — Ventricular beats
  • Violet — Escape beats
  • Indigo — Paced beats
  • Gray — Unknown/unclassified

The R-peaks badge in the Stats panel shows a breakdown of counts by beat type (e.g., "N:245, V:12, A:3").

💡 Last R-Peak Info

After adding an R-peak, the interface displays "Last: R@[sample] [type]" to help you track your most recent annotation.

4.2 Keyboard Shortcuts

Speed up your workflow with keyboard shortcuts for R-peak classification:

Key Action
NClassify last R-peak as Normal
VClassify last R-peak as PVC (Ventricular)
AClassify last R-peak as APC (Atrial)
LClassify last R-peak as LBBB
Shift+RClassify last R-peak as RBBB
JClassify last R-peak as Nodal/Junctional
BClassify last R-peak as Bundle Branch Block
FClassify last R-peak as Fusion
EClassify last R-peak as Ventricular Escape
QClassify last R-peak as Unknown
SClassify last R-peak as Supraventricular

💡 Efficient Workflow

The fastest workflow is:

  1. Set mode to "Add R (Normal)"
  2. Click on each R-peak
  3. Immediately press a keyboard shortcut to classify (if not normal)
  4. Continue to next peak

⚠️ Keyboard Shortcuts Only Work on Last R-Peak

Keyboard shortcuts classify the most recently added R-peak. To classify older R-peaks, use the right-click method or the "More..." panel.

4.3 Automatic Peak Detection

CardioAnnotate provides two automatic peak detection algorithms that can be used for any peak type:

Find Peaks (scipy.signal.find_peaks)

A general-purpose peak detection algorithm suitable for all peak types. Configurable parameters:

  • Polarity: Detect maxima, minima, or absolute value maxima
  • Min distance (ms): Minimum distance between consecutive peaks
  • Threshold: Minimum height for peaks (in signal units)
  • Prominence: Minimum vertical prominence required
  • Prom window (ms): Window size for prominence calculation

Pan-Tompkins Algorithm

A classic QRS detection algorithm optimized for R-peak detection. Parameters:

  • Band-pass (Hz): Frequency range for filtering (default 5–15 Hz)
  • MWI (ms): Moving window integration width
  • Min RR (ms): Minimum allowed RR interval
  • Thresh k: Threshold factor (detection threshold = mean + k·std)
  • Refine ± (ms): Window for refining detected peaks to local maximum

Detection Scope

  • Full signal: Process the entire recording
  • Visible segment: Process only the current zoom window

Options

  • Replace existing (in scope): If checked, removes existing peaks of the target type before detection

💡 Save Settings per Peak Type

Click "💾 Save settings to this peak type" to remember your detection parameters for each peak type. The tool will remember different settings for R, P, T, etc.

💡 Detection Tips

  • For R-peaks, Pan-Tompkins usually works best
  • For P and T waves, use Find Peaks with appropriate polarity and distance settings
  • For Q and S waves, use Find Peaks with "Minima" polarity
  • Run detection on a visible segment first to tune parameters before processing full signal

💾 5. Export & Sharing

Export Annotations (JSON)

Click "Export annotations (JSON)" to save all peak annotations in a structured JSON format:

Export Annotations (CSV)

Click "Export annotations (CSV)" for a tabular format with columns:

Export ECG Signal (CSV)

Click "Export ECG (CSV)" to save the current processed signal with columns:

Export NPZ Bundle

Click "Export bundle (.npz)" to save a signal-and-annotation bundle containing:

This format is useful for exchanging processed signals and annotations in a compact Python-compatible archive. Use full session export when you need exact workspace restoration.

Export Full Session

Click the full session export control to save the complete analysis state in .json, .mat, or .npz format. A session contains:

Import Full Session

Click the full session import control and select a .json, .mat, or .npz session file. The importer detects the format automatically, restores the workspace state, and recomputes the SQI, HRV, and stationarity panels so the displayed values reproduce the saved snapshot.

Full Session vs. Other Exports

Annotation JSON/CSV files store peak labels only. NPZ signal bundles store processed signals and annotations. Full sessions store the complete processing pipeline, interface state, correction state, visible segment, and computed analysis results.

Load Previous Annotations

You can reload previously exported annotations or import compatible WFDB annotation files:

  1. For JSON or CSV annotations, click "📂 Load JSON/CSV" and select the annotation file
  2. For WFDB annotations, use the "Import WFDB annotations" row, select the annotation file, then click "📂 Load .atr / wave"
  3. Annotations will be merged with current peaks (duplicates may occur)
  4. R-peak beat type information is preserved when loading JSON or .atr annotation files

JSON imports accept the current annotations top-level key and the legacy peaks key. WFDB imports accept .atr, .qrs, .pwave, .twave, .uwave, .qwave, .swave, and recognized extension variants. The WFDB file picker accepts all extensions so that non-standard annotation files remain selectable.

Export to WFDB Format

Save the current signal and annotations as WFDB files with enhanced export options:

  1. Enter a record name (default is "export")
  2. Choose an annotation format:
    • All peaks in one file (.atr) — Single annotation file containing all peak types with WFDB codes
    • Separate files — Creates individual annotation files:
      • .atr — R-peaks with beat type annotations
      • .pwave — P-peaks
      • .twave — T-peaks
      • .uwave — U-waves
      • .qwave — Q-waves
      • .swave — S-waves
  3. Click "Export WFDB (.hea/.dat + annotations)"
  4. Files will be downloaded:
    • .hea — Header with metadata
    • .dat — Signal data (16-bit signed, gain 200)
    • Annotation file(s) based on your selection

WFDB Annotation Mapping

When using the "All peaks in one file" mode, peak types are mapped to WFDB annotation codes:

  • R-peaks → Use their assigned beat type code (N, V, A, L, etc.)
  • P-peaksp (P-wave peak)
  • T-peakst (T-wave peak)
  • U-wavesu (U-wave)
  • Q-waves( (waveform onset)
  • S-waves) (waveform end)

💡 Choosing Between Export Modes

Single file mode is best for standard WFDB compatibility and when you need all annotations in one place. Separate files mode is useful when different peak types need independent processing or when working with tools that expect specific annotation file extensions.

💡 Export Summary

After exporting, a status message shows which files were created and the count of each peak type (e.g., "✓ Exported: record.hea, record.dat, record.atr (245 R, 38 P, 41 T)").

🔧 6. Signal Processing

Resampling

Change the sampling frequency of your signal:

  1. Enter the target sampling frequency in Hz
  2. Click "Resample"
  3. The signal will be resampled using scipy.signal.resample_poly
  4. If you have annotations, you'll be warned that they need to be reprojected

⚠️ Important: Reproject Annotations After Resampling

After resampling, your peak annotations will be at incorrect positions. Click "Reproject annotations to new fs" to automatically scale them to the new sampling rate.

Filtering

Apply Butterworth filters (order 4, zero-phase using filtfilt):

Low-Pass Filter

Remove high-frequency noise. Enter cutoff frequency in Hz (e.g., 40 Hz).

High-Pass Filter

Remove baseline wander and low-frequency drift. Enter cutoff frequency in Hz (e.g., 0.5 Hz).

Notch Filter

Remove powerline interference at 50 Hz or 60 Hz. Select from dropdown (Q=30).

Band-Pass Filter

Convenience shortcut that applies high-pass and low-pass in one step. Enter low and high cutoff frequencies.

💡 Typical ECG Filter Settings

Common band-pass: 0.5 Hz (high-pass) to 40 Hz (low-pass). This removes baseline drift while preserving ECG morphology.

Reset to Original

Click "↺ Reset all signals" to discard all signal processing (resampling, filtering, inversion) and return to the originally loaded signal.

⚠️ Filter Order Matters

If you plan to both resample and filter, resample first, then filter. Filters are designed for specific sampling rates.

📊 7. Statistics & Analysis

7.1 Basic ECG Metrics

The Statistics panel automatically computes common ECG metrics based on your peak annotations:

Available Metrics

  • Duration: Total signal length in seconds
  • Avg RR (ms): Average R-R interval duration (time between consecutive R-peaks)
  • HR (bpm): Heart rate calculated from average RR interval (60000 / RR_ms)
  • Avg PR (ms): Average P-R interval (from P-peak to next R-peak)
  • Avg QT (ms): Average Q-T interval (from Q-wave to T-peak, or R-peak to T-peak if no Q annotations)
  • Avg QRS (ms): Average QRS duration (from Q-wave to S-wave if both are annotated, otherwise estimated from R-peak to S-wave)

💡 Statistical Note

All interval statistics require at least 2 peaks of the relevant types. Metrics will show "—" if insufficient data is available.

Badge Counters

The colored badges show total peak counts across all channels:

  • Signal: Current processing state (original, resampled, filtered, modified)
  • R-peaks: Total R-peaks with breakdown by beat type (e.g., "N:245, V:12, A:3")
  • P-peaks: Total P-peaks
  • Q-waves: Total Q-waves
  • S-waves: Total S-waves
  • T-peaks: Total T-peaks
  • U-waves: Total U-waves

R-Peak Breakdown

The R-peaks badge includes a detailed breakdown showing the count of each beat type, making it easy to see the distribution of normal beats versus arrhythmias at a glance.

7.2 Signal Quality Indices (SQI)

Signal quality metrics help assess the reliability of the ECG signal for analysis. These are computed on the currently visible window of the active channel.

Metric Formula Description Interpretation
pSQI Power(5–15 Hz) / Power(5–40 Hz) QRS-band relative power Higher values (0.3–0.7) indicate stronger QRS complexes relative to other content
kSQI Pearson kurtosis Signal "peakiness" Gaussian ≈ 3; higher values indicate prominent QRS spikes; low values suggest noise
basSQI 1 − Power(0–1 Hz) / Power(0–40 Hz) Baseline wander index Higher is better (close to 1); low values indicate significant baseline drift
lineSQI Power(mains ± width) / Power(0–Nyquist) Mains interference ratio Lower is better; high values indicate powerline contamination at 50/60 Hz

💡 Mains Frequency Settings

Adjust the Mains dropdown (50 Hz for Europe/Asia or 60 Hz for Americas) and ± bandwidth (0.5 to 2 Hz) to match your region's power grid for accurate lineSQI calculation.

7.3 Heart Rate Variability (HRV)

HRV metrics are computed from R-peaks within the current zoom window. The segment range is displayed (e.g., "Segment: 0.0–10.5s").

Time-Domain Metrics

Metric Description Clinical Significance
Mean NN / RR (ms) Average of all NN (RR) intervals Inversely related to heart rate
SDNN (ms) Standard deviation of NN intervals Overall HRV; reflects all cyclic components
RMSSD (ms) Root mean square of successive differences Short-term variability; parasympathetic activity
NN50 Count of successive NN intervals differing by >50 ms Parasympathetic indicator
pNN50 (%) NN50 as percentage of total NN intervals Normalized parasympathetic indicator

Frequency-Domain Metrics

Computed using Welch's method (Hann window, 50% overlap):

Metric Frequency Band Physiological Interpretation
ULF power (ms²) 0–0.003 Hz Ultra low frequency; circadian rhythms, thermoregulation
VLF power (ms²) 0.003–0.04 Hz Very low frequency; thermoregulation, hormonal fluctuations
LF power (ms²) 0.04–0.15 Hz Low frequency; mix of sympathetic and parasympathetic
HF power (ms²) 0.15–0.4 Hz High frequency; parasympathetic (vagal) activity, respiratory sinus arrhythmia
LF/HF ratio Sympathovagal balance (interpretation debated)

Nonlinear Metrics

Derived from the Poincaré plot of successive NN intervals (RRn versus RRn+1) and from the sample entropy of the NN series. A Poincaré plot can be opened in a separate window using the Poincaré plot button; the SD1/SD2 dispersion ellipse, centered at the mean NN, is overlaid on the scatter.

Metric Description Interpretation
Poincaré SD1 (ms) Dispersion perpendicular to the line of identity (SD1 = SDSD/√2, where SDSD is the standard deviation of successive NN differences) Short-term variability; closely tracks RMSSD and reflects parasympathetic (vagal) activity
Poincaré SD2 (ms) Dispersion along the line of identity (SD2 = √(2·SDNN² − ½·SDSD²)) Long-term variability; reflects overall HRV and satisfies SD1² + SD2² = 2·SDNN²
SD2/SD1 Ratio of long-term to short-term variability Balance between long- and short-term variability; higher values indicate greater long-term dispersion relative to beat-to-beat variability
SampEn Sample entropy of the NN series (embedding dimension m = 2, tolerance r = 0.2 × SDNN) Regularity and complexity of the RR series; lower values indicate more regular, predictable rhythms, higher values indicate more complex or irregular dynamics

Stationarity (ADF Test)

The Augmented Dickey-Fuller test assesses whether the RR interval series is stationary:

  • Stationary (p < 0.05) — RR intervals show stable statistical properties; HRV metrics are reliable
  • Non-stationary — RR intervals show trends or drifts; interpret HRV metrics with caution

⚠️ HRV Requirements

Time-domain HRV requires ≥2 R-peaks (to compute at least one RR interval). Frequency-domain HRV requires ≥4 RR intervals for reliable spectral estimation.
Nonlinear metrics require ≥3 NN intervals for the Poincaré descriptors; SampEn requires at least m+2 intervals and is reported as "—" when no matching templates exist at the chosen tolerance, so it is most reliable on longer segments with many intervals.

7.4 RR Error Detection & GP Correction

CardioAnnotate includes advanced RR interval quality control using Gaussian Process (GP) regression for detecting and correcting outliers. This is essential for reliable HRV analysis.

Detection Modes

Mode Description Parameters
Absolute bounds Flag RR intervals outside physiological limits Abs min RR: Minimum (default 250 ms)
Abs max RR: Maximum (default 2000 ms)
Rate-of-change bounds Flag intervals with excessive beat-to-beat variation Max rel change α: Maximum relative change (default 0.20 = 20%)
Both Combine absolute and rate-of-change criteria Both sets of parameters apply

Compare to last accepted

When enabled (default), rate-of-change detection compares each interval to the last accepted value rather than the immediate predecessor. This prevents cascading rejections from a single outlier.

Gaussian Process Kernels

Choose a GP kernel for RR interval interpolation:

  • Rational Quadratic (RQ) — Default; flexible, handles multiple length scales. Best general-purpose choice.
  • Matérn 3/2 — Once-differentiable, allows rougher variations
  • Matérn 5/2 — Twice-differentiable, smoother than 3/2
  • Periodic — For regular heartbeat patterns with periodic structure
  • Brownian — For trending RR series (non-stationary)

Default GP Hyperparameters

Kernel defaults use optimized mean values from the Harmonizing Heartbeats RR-correction model. Each kernel applies its variance term, σ², as a scale factor.

KernelDefault Hyperparameters
Rational Quadratic (RQ)ℓ=0.475, α=0.412, σ²=3.06×10⁻⁴
Matérn 3/2ℓ=2.65, σ²=54.93
Matérn 5/2ℓ=0.241, σ²=29.40
Periodicperiod=0.784, ℓ=0.985, σ²=2.90
Brownianσ²=0.0010

GP Hyperparameters Panel

Click "GP hyperparameters ▾" below the GP controls to open the collapsible hyperparameter panel. The visible sliders change with the selected kernel, each slider shows its current value, and GP correction uses the current slider values each time it runs.

Workflow

  1. Configure detection parameters (min/max RR, max change α)
  2. Select a GP kernel and adjust the GP hyperparameters sliders if needed
  3. Click "🧪 Detect RR outliers" to flag implausible intervals
  4. Review the flagged count in the display
  5. Click "✨ GP-correct flagged RR" to estimate corrected values using GP regression
  6. Optionally click "🗑 Remove flagged RR" to delete the R-peaks associated with flagged intervals

⚠️ GP Correction Requirements

GP correction requires at least 3 accepted (non-flagged) RR intervals to train the model, and at least 1 flagged interval to correct. If correction fails, try adjusting thresholds or using a different kernel.

💡 When to Use GP Correction

Run RR outlier detection and GP correction before computing HRV metrics. This removes or corrects ectopic beats, missed beats, and artifacts that would otherwise distort HRV estimates.

🌙 8. Dark Mode

CardioAnnotate supports both light and dark themes for comfortable viewing in any environment.

Toggle Theme

Click the "🌙 Dark" / "☀️ Light" button in the header to switch between themes. Your preference is saved in the browser's local storage and will be remembered for future sessions.

💡 Reduced Eye Strain

Dark mode is recommended for extended annotation sessions, especially in low-light environments. The plot colors, UI elements, and contrast levels are optimized for visibility in both themes.

💡 9. Tips & Best Practices

1. Work at the Right Zoom Level

Zoom in to 2-3 heartbeats for optimal annotation precision. You can see the overall morphology while placing peaks accurately.

2. Annotate R-peaks First, Then Classify

The most efficient workflow is to add all R-peaks first with the default "Normal" type, then use keyboard shortcuts or quick classify buttons to mark arrhythmias as you go. For complex cases, right-click on R-peaks to access the full classification panel.

3. Use Snap-to-Peak for Refinement

Place approximate peaks quickly, then use the snap function with a small window (3-5 samples) to refine them automatically. This works best for R, P, T, and U peaks.

4. Save Your Work Frequently

Export annotations to JSON or a signal bundle to preserve peak labels. Use full session export when you need to preserve the complete workspace, including the original and processed signals, settings, corrections, selected window, and computed results. Since everything runs in-browser, refreshing the page will lose all unsaved work.

5. Process Then Annotate

Apply necessary filtering and resampling before annotating. It's easier to see features in clean, properly-sampled signals.

6. Multi-Lead Consistency

For multi-lead ECGs, annotate the clearest lead first (often MLII), then use those annotations as a temporal reference for other leads.

7. Q and S Wave Annotation

Q and S waves require precise manual placement since they're often minima or inflection points. They are automatically excluded from the "snap to peak" function. For accurate QRS measurements, annotate both Q and S waves.

8. Use "Remove Last" for Quick Corrections

If you make a mistake while annotating, use the "↶ Remove Last" button to undo the most recent annotation. This is faster than switching to "Remove nearest" mode.

9. Choose the Right WFDB Export Mode

For general-purpose exports and maximum compatibility, use the single file mode. Use separate files mode when you need to process different peak types independently or when working with specialized WFDB tools.

10. Use GP Correction Before HRV Analysis

Before computing HRV metrics, run RR outlier detection and GP correction to remove or correct ectopic beats and artifacts. This ensures more reliable HRV estimates.

11. Check Signal Quality First

Review the SQI metrics before detailed annotation. Poor signal quality (low pSQI, high lineSQI) may require filtering or indicate a segment unsuitable for analysis.

12. Use Full Sessions for Reproducibility

When sharing an analysis with collaborators or preparing work for review, export a full session rather than only annotations. The session records the visible segment, detector parameters, GP correction state, and computed SQI, HRV, and ADF results needed to reproduce the workspace.

🔧 10. Troubleshooting

Common Issues

Issue: "Initializing..." never completes

Solution: Check your internet connection. Pyodide and WFDB need to download (~20MB) on first load. Try refreshing the page or clearing your browser cache.

Issue: "Failed to load" WFDB files

Solution: Ensure you have both .hea and .dat (or .mat) files with the same basename. Check that the files aren't corrupted. Try the files with standard WFDB tools first.

Issue: Annotations disappear after resampling

Solution: This is expected behavior. Use the "Reproject annotations to new fs" button to scale peak positions to the new sampling rate.

Issue: CSV import fails

Solution: Verify your CSV has:

  • A header row with fs or sampling_rate column
  • Channel columns with recognizable names
  • Numeric values in all data rows
  • No missing values

Issue: Filter causes error

Solution: Make sure cutoff frequencies are below the Nyquist frequency (sampling_rate / 2). Very low or very high cutoffs can cause numerical issues.

Issue: Peaks not visible on plot

Solution: Zoom out to see the full signal range. Annotations are only visible when the corresponding samples are in the current view window.

Issue: Beat classification not working

Solution: Beat classification only works for R-peaks. Make sure you're in "Add R (Normal)" mode or have recently added an R-peak. Keyboard shortcuts only classify the most recently added R-peak.

Issue: WFDB annotation file is not visible in the file picker

Solution: The WFDB annotation importer accepts all file extensions. Choose the file directly, or switch the operating system file picker to show all files, then select the desired .atr, .qrs, .pwave, .twave, .uwave, .qwave, or .swave annotation file.

Issue: WFDB export creates unexpected files

Solution: Check your selected export mode (single file vs. separate files). The "single file" mode creates one .atr file, while "separate files" mode creates multiple annotation files (.atr, .pwave, .twave, etc.) depending on which peak types you've annotated.

Issue: GP correction fails or returns poor results

Solution: GP correction requires at least 3 accepted (non-flagged) RR intervals. Try adjusting the detection thresholds to flag fewer intervals, or try a different kernel (RQ is usually most robust).

Issue: HRV metrics show "—"

Solution: HRV requires R-peaks in the visible segment. Time-domain metrics need ≥2 R-peaks; frequency-domain metrics need ≥4 RR intervals. Zoom to include more heartbeats.

Issue: NPZ import fails

Solution: Ensure the NPZ file was exported from CardioAnnotate and contains both "signals" and "meta" arrays. Files from other sources may not be compatible.

Issue: Full session import does not restore the expected workspace

Solution: Confirm that the file is a CardioAnnotate full session rather than an annotation-only JSON/CSV file or a signal bundle. Full sessions include signals, annotations, corrections, settings, view state, and results. If the visible segment differs after import, check that the saved session contained the expected zoom window and active channel.

Performance Tips

📝 11. Citation & Credits

If you use CardioAnnotate in your research or clinical work, please cite:

CardioAnnotate: A Toolkit for ECG Peak Labeling and Processing

A browser-based ECG annotation toolkit with integrated signal processing, R-peak beat classification, HRV analysis, and WFDB format support.

Harmonizing heartbeats: RR-interval correction with Gaussian Process for reliable HRV

Mojtahed, Hamed, et al. (2026). Biomedical Signal Processing and Control, 117, 109513. Elsevier.

If you use the RR error detection and Gaussian Process correction features, please also cite this work.

Technical Details

Acknowledgments

This toolkit builds upon the excellent work of: