Understanding Annotations

library(EGM)
#> 
#> Attaching package: 'EGM'
#> The following object is masked from 'package:stats':
#> 
#>     window

Electrocardiogram Annotations

WFDB Annotation Standards

The WFDB (Waveform Database) software package provides a standardized system for annotating cardiac signals, predominately surface electrocardiogram (ECG) data. Annotations are polymorphic, meaning multiple annotation sets can be applied to a single signal dataset. The limitation is that these are constrained to physiological events associated with surface ECGs.

Annotation Table Structure

All WFDB-compatible annotations in the {EGM} package are stored as annotation_table objects. The columns are type-specific, which allows them to be easily stored. These tables contain the following required columns:

  • annotator: Name of the annotation function or creator (e.g., “sqrs”, “ecgpuwave”, “manual”)
  • time: Time stamp constructed from sample number and sampling frequency (format: HH:MM:SS.mmm)
  • sample: Integer index of the annotation position in the signal
  • type: Single character symbol describing the annotation type (see annotation types below)
  • subtype: Single character providing additional classification
  • channel: Integer indicating which signal channel the annotation applies to
  • number: Additional numeric qualifier providing context-specific information
# Create annotations for detected R-peaks
# Example
ann <- annotation_table(
  annotator = "qrs",
  sample = c(100, 350, 600, 850),
  type = "N",
  frequency = 250,
  channel = 0
)

# Print
ann
#> <annotation_table: 4 `qrs` annotations>
#>          time sample   type subtype channel number
#>        <char>  <num> <char>  <char>   <num>  <int>
#> 1: 00:00:00.4    100      N               0      0
#> 2: 00:00:01.4    350      N               0      0
#> 3: 00:00:02.4    600      N               0      0
#> 4: 00:00:03.4    850      N               0      0

Standard ECG Annotation Types

The WFDB standard defines 41 annotation types for surface electrocardiograms. These annotations can be broadly grouped into several categories:

Beat Annotations

Beat annotations mark individual cardiac cycles and their characteristics:

# Load internal annotation data
beat_symbols <- c("N", "L", "R", "a", "V", "F", "J", "A", "S", "E", "j", "/", "Q", "~")
beat_labels <- EGM:::.surface_annotations[EGM:::.surface_annotations$symbol %in% beat_symbols, ]

knitr::kable(
  beat_labels[, c("symbol", "mnemonic", "description")],
  col.names = c("Symbol", "Mnemonic", "Description"),
  row.names = FALSE,
  caption = "Beat Annotation Types"
)
Beat Annotation Types
Symbol Mnemonic Description
N NORMAL Normal beat
L LBBB Left bundle branch block beat
R RBBB Right bundle branch block beat
a ABERR Aberrated atrial premature beat
V PVC Premature ventricular contraction
F FUSION Fusion of ventricular and normal beat
J NPC Nodal (junctional) premature beat
A APC Atrial premature contraction
S SVPB Premature or ectopic supraventricular beat
E VESC Ventricular escape beat
j NESC Nodal (junctional) escape beat
/ PACE Paced beat
Q UNKNOWN Unclassifiable beat
~ NOISE Signal quality change

Key beat types:

  • N (NORMAL): Normal sinus beat
  • V (PVC): Premature ventricular contraction
  • A (APC): Atrial premature contraction
  • L/R (LBBB/RBBB): Bundle branch block beats
  • F (FUSION): Fusion of ventricular and normal beat
  • / (PACE): Paced beat

Waveform Boundary Annotations

These mark the beginning, peak, and end of ECG waveforms:

wave_symbols <- c("p", "t", "u", "(", ")")
wave_labels <- EGM:::.surface_annotations[EGM:::.surface_annotations$symbol %in% wave_symbols, ]

knitr::kable(
  wave_labels[, c("symbol", "mnemonic", "description")],
  col.names = c("Symbol", "Mnemonic", "Description"),
  row.names = FALSE,
  caption = "Waveform Boundary Annotations"
)
Waveform Boundary Annotations
Symbol Mnemonic Description
p PWAVE P-wave peak
t TWAVE T-wave peak
u UWAVE U-wave peak
( WFON Waveform onset
) WFOFF Waveform end

Rhythm and Signal Quality Annotations

rhythm_symbols <- c("+", "|", "s", "T", "~", "x")
rhythm_labels <- EGM:::.surface_annotations[EGM:::.surface_annotations$symbol %in% rhythm_symbols, ]

knitr::kable(
  rhythm_labels[, c("symbol", "mnemonic", "description")],
  col.names = c("Symbol", "Mnemonic", "Description"),
  row.names = FALSE,
  caption = "Rhythm and Signal Quality Annotations"
)
Rhythm and Signal Quality Annotations
Symbol Mnemonic Description
~ NOISE Signal quality change
| ARFCT Isolated QRS-like artifact
s STCH ST change
T TCH T-wave change
+ RHYTHM Rhythm change
x NAPC Non-conducted P-wave (blocked APB)

Specialized Annotations

special_symbols <- c("*", "D", "\"", "=", "!", "[", "]", "@", "r", "^", "B", "e", "n", "f")
special_labels <- EGM:::.surface_annotations[EGM:::.surface_annotations$symbol %in% special_symbols, ]

knitr::kable(
  special_labels[, c("symbol", "mnemonic", "description")],
  col.names = c("Symbol", "Mnemonic", "Description"),
  row.names = FALSE,
  caption = "Specialized Annotations"
)
Specialized Annotations
Symbol Mnemonic Description
* SYSTOLE Systole
D DIASTOLE Diastole
NOTE Comment annotation
= MEASURE Measurement annotation
B BBB Left or right bundle branch block
^ PACESP Non-conducted pacer spike
! FLWAV Ventricular flutter wave
[ VFON Start of ventricular flutter/fibrillation
] VFOFF End of ventricular flutter/fibrillation
e AESC Atrial escape beat
n SVESC Supraventricular escape beat
@ LINK Link to external data (aux_note contains URL)
f PFUS Fusion of paced and normal beat
r RONT R-on-T premature ventricular contraction

Common Annotation File Types

When working with WFDB files, annotations are stored with specific file extensions that indicate the annotator used:

Accessing All Standard Annotations

The complete list of standard WFDB annotations can be accessed using:

# View all standard ECG annotation types
wfdb_annotation_labels()

# Filter for specific symbols
wfdb_annotation_labels(symbol = c("N", "V", "A"))

# Decode annotations in an existing annotation table
ann <- annotation_table(
  annotator = "example",
  sample = c(100, 200),
  type = c("N", "V")
)

# Add human-readable descriptions
wfdb_annotation_decode(ann)

Example: Working with ECG Annotations

Here’s a practical example of reading, interpreting, and visualizing ECG annotations:

# Read an ECG with annotations
record_path <- system.file('extdata', package = 'EGM')
ecg <- read_wfdb(
  record = "muse-sinus",
  record_dir = record_path
)

# Read associated annotations (if they exist)
ann <- read_annotation(
   record = "muse-sinus",
   annotator = "ecgpuwave",
   record_dir = record_path
)

# Decode annotation types
ann_decoded <- wfdb_annotation_decode(ann)
head(ann_decoded)

Intracardiac Electrogram Annotations

This is a work in progress, where we aim to define and implement a comprehensive set of annotations for intracardiac electrograms that are compatible with the standard WFDB annotation system.

Examples

ecgpuwave

One of the common annotators used, called ecgpuwave, demonstrates how the annotation system is leveraged.

The standard labels are used:

When using waveform boundary annotations like ( and ), the number column specifies which waveform:

For T-wave annotations (t), the number column describes morphology:

mirror server hosted at Truenetwork, Russian Federation.