CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-wfdb

Python package for reading, writing, and processing physiologic signals and annotations in WFDB format.

Overview
Eval results
Files

plotting.mddocs/

Plotting and Visualization

Tools for plotting physiological signals and annotations with customizable styling, multiple display options, and publication-quality output. This module provides matplotlib-based visualization specifically designed for biomedical signal data and WFDB records.

Capabilities

Record and Annotation Plotting

High-level functions for plotting WFDB records and annotations with automatic formatting and styling.

def plot_wfdb(record: Record = None, annotation: Annotation = None, 
              plot_sym: bool = False, time_units: str = 'seconds', 
              title: str = None, sig_style: List[str] = [''], ann_style: List[str] = ['r*'],
              ecg_grids: List[int] = [], figsize: Tuple[float, float] = None,
              return_fig: bool = False, sharex: str = "auto") -> Union[None, matplotlib.figure.Figure]:
    """
    Plot WFDB record and annotation objects with automatic formatting.
    
    Parameters:
    - record: Record, WFDB record object to plot
    - annotation: Annotation, annotation object to overlay
    - plot_sym: bool, whether to plot annotation symbols (default: False)
    - time_units: str, x-axis units ('samples', 'seconds', 'minutes', 'hours')
    - title: str, plot title
    - sig_style: list of str, signal line styles for each channel
    - ann_style: list of str, annotation marker styles
    - ecg_grids: list of int, channels to apply ECG grid styling
    - figsize: tuple, figure size (width, height) in inches
    - return_fig: bool, return figure object
    - sharex: str, x-axis sharing mode ("auto", "all", "none")
    
    Returns:
    None or Figure object based on return_fig parameter
    """

def plot_items(signal: Union[np.ndarray, List[np.ndarray]] = None, 
               ann_samp: List[Union[int, np.ndarray]] = None,
               ann_sym: List[List[str]] = None, fs: float = None,
               time_units: str = "samples", sig_name: List[str] = None,
               sig_units: List[str] = None, xlabel: str = None, ylabel: str = None,
               title: str = None, sig_style: List[str] = [""], 
               ann_style: List[str] = ["r*"], ecg_grids: List[int] = [],
               figsize: Tuple[float, float] = None, sharex: bool = False,
               sharey: bool = False, return_fig: bool = False,
               return_fig_axes: bool = False, sampling_freq: float = None,
               ann_freq: List[float] = None) -> Union[None, matplotlib.figure.Figure, Tuple[matplotlib.figure.Figure, List[matplotlib.axes.Axes]]]:
    """
    Plot signals and annotations with detailed customization options.
    
    Parameters:
    - signal: ndarray or list of ndarray, signals to plot
    - ann_samp: list of array-like, annotation sample locations for each subplot
    - ann_sym: list of list of str, annotation symbols for each subplot
    - fs: float, sampling frequency for time conversion
    - time_units: str, x-axis time units ('samples', 'seconds', 'minutes', 'hours')
    - sig_name: list of str, signal names for subplot titles
    - sig_units: list of str, signal units for y-axis labels
    - xlabel: str, x-axis label (overrides time_units)
    - ylabel: str, y-axis label for single subplot
    - title: str, main plot title
    - sig_style: list of str, line styles for each signal
    - ann_style: list of str, marker styles for annotations
    - ecg_grids: list of int, subplot indices for ECG grid styling
    - figsize: tuple, figure size (width, height) in inches
    - sharex: bool, share x-axis across subplots
    - sharey: bool, share y-axis across subplots
    - return_fig: bool, return figure object
    - return_fig_axes: bool, return both figure and axes objects
    - sampling_freq: float, deprecated, use fs parameter
    - ann_freq: list of float, sampling frequencies for annotations
    
    Returns:
    None, Figure object, or (Figure, Axes) tuple based on return parameters
    """

def plot_all_records(directory: str = "") -> None:
    """
    Plot all WFDB records in a directory.
    
    Parameters:
    - directory: str, directory path containing WFDB records (default: current)
    """

Usage Examples

Basic Record Plotting

import wfdb
import matplotlib.pyplot as plt

# Read record and annotations
record = wfdb.rdrecord('100', pn_dir='mitdb')
annotation = wfdb.rdann('100', 'atr', pn_dir='mitdb')

# Simple plot with default settings
wfdb.plot.plot_wfdb(record=record, annotation=annotation)
plt.show()

# Plot with time units in seconds
wfdb.plot.plot_wfdb(record=record, annotation=annotation, 
                    time_units='seconds', title='MIT-BIH Record 100')
plt.show()

# Plot subset of data (first 10 seconds)
record_subset = wfdb.rdrecord('100', pn_dir='mitdb', 
                             sampfrom=0, sampto=3600)  # 10 sec at 360 Hz
wfdb.plot.plot_wfdb(record=record_subset, 
                    time_units='seconds', title='First 10 seconds')
plt.show()

Advanced Plotting with Custom Styling

import wfdb
import matplotlib.pyplot as plt
import numpy as np

# Read multi-channel record
record = wfdb.rdrecord('100', pn_dir='mitdb')
annotation = wfdb.rdann('100', 'atr', pn_dir='mitdb')

# Custom styling
fig, axes = wfdb.plot.plot_wfdb(
    record=record, 
    annotation=annotation,
    time_units='minutes',
    title='ECG with Custom Styling',
    sig_style='b-',           # Blue solid line
    ann_style=['ro', 'g^'],   # Red circles, green triangles
    ecg_grids=[0, 1],         # Apply ECG grid to both channels
    figsize=(12, 8),
    return_fig_axes=True
)

# Further customize the plot
for ax in axes:
    ax.grid(True, alpha=0.3)
    ax.set_xlim(0, 5)  # First 5 minutes

plt.tight_layout()
plt.show()

Plotting Individual Signals

import wfdb
import numpy as np
import matplotlib.pyplot as plt

# Read signals only
signals, fields = wfdb.rdsamp('100', pn_dir='mitdb', channels=[0, 1])
fs = fields['fs']

# Create sample annotations (e.g., detected peaks)
qrs_inds = wfdb.processing.xqrs_detect(signals[:, 0], fs=fs)

# Plot using plot_items for more control
wfdb.plot.plot_items(
    signal=signals.T,         # Transpose for subplot per signal
    ann_samp=[qrs_inds, []],  # Annotations only on first channel
    ann_sym=[['R'] * len(qrs_inds), []],  # Symbol for each annotation
    fs=fs,
    time_units='seconds',
    sig_name=['MLI', 'MLII'],
    sig_units=['mV', 'mV'],
    title='ECG Signals with QRS Detection',
    sig_style=['b-', 'r-'],
    ann_style=['ko'],
    figsize=(15, 6)
)
plt.show()

Plotting Multiple Records

import wfdb
import matplotlib.pyplot as plt

# Plot multiple records for comparison
records = ['100', '101', '102']
fig, axes = plt.subplots(len(records), 1, figsize=(12, 10), sharex=True)

for i, record_name in enumerate(records):
    record = wfdb.rdrecord(record_name, pn_dir='mitdb', 
                          sampfrom=0, sampto=1800)  # 5 seconds
    
    # Plot each on separate subplot
    plt.sca(axes[i])
    wfdb.plot.plot_wfdb(record=record, time_units='seconds',
                        title=f'Record {record_name}')

plt.tight_layout()
plt.show()

ECG Grid Styling

import wfdb
import matplotlib.pyplot as plt

# Read ECG record
record = wfdb.rdrecord('100', pn_dir='mitdb', channels=[0])

# Plot with ECG-specific grid styling
wfdb.plot.plot_wfdb(
    record=record,
    time_units='seconds', 
    title='ECG with Standard Grid',
    ecg_grids=[0],            # Apply ECG grid to channel 0
    figsize=(15, 6)
)
plt.show()

# Alternative: manual ECG grid setup
fig, ax = plt.subplots(figsize=(15, 6))
time_vec = np.arange(record.sig_len) / record.fs
ax.plot(time_vec, record.p_signal[:, 0], 'b-', linewidth=0.8)

# Standard ECG grid (0.04s major, 0.008s minor)
ax.grid(True, which='major', alpha=0.5, linestyle='-', linewidth=0.8)
ax.grid(True, which='minor', alpha=0.3, linestyle='-', linewidth=0.4)
ax.set_xlabel('Time (seconds)')
ax.set_ylabel('Amplitude (mV)')
ax.set_title('ECG with Manual Grid')
plt.show()

Plotting Large Datasets

import wfdb
import matplotlib.pyplot as plt

# Read long record (will be automatically chunked)
record = wfdb.rdrecord('100', pn_dir='mitdb')  # Full 30-minute record

# Automatic chunking for large datasets
wfdb.plot.plot_wfdb(
    record=record,
    time_units='minutes',
    title='Full 30-minute ECG Record',
    auto_chunk=True,
    chunk_size=50000,  # Adjust chunk size as needed
    figsize=(20, 8)
)
plt.show()

# Plot specific time windows
for start_min in range(0, 30, 5):  # 5-minute windows
    start_samp = int(start_min * 60 * record.fs)
    end_samp = int((start_min + 5) * 60 * record.fs)
    
    record_chunk = wfdb.rdrecord('100', pn_dir='mitdb',
                               sampfrom=start_samp, sampto=end_samp)
    
    wfdb.plot.plot_wfdb(record=record_chunk, time_units='seconds',
                       title=f'Minutes {start_min}-{start_min+5}')
    plt.show()

Annotation Visualization

import wfdb
import matplotlib.pyplot as plt

# Read record with multiple annotation types
record = wfdb.rdrecord('100', pn_dir='mitdb', channels=[0])
annotations = wfdb.rdann('100', 'atr', pn_dir='mitdb')

# Plot with symbol display
wfdb.plot.plot_wfdb(
    record=record,
    annotation=annotations,
    plot_sym=True,           # Show annotation symbols
    time_units='seconds',
    ann_style=['r*'],        # Red asterisks
    title='ECG with Beat Annotations'
)
plt.show()

# Custom annotation display
normal_beats = annotations.sample[annotations.symbol == 'N']
pvc_beats = annotations.sample[annotations.symbol == 'V']

wfdb.plot.plot_items(
    signal=[record.p_signal[:, 0]],
    ann_samp=[normal_beats, pvc_beats],
    ann_sym=[['N'] * len(normal_beats), ['V'] * len(pvc_beats)],
    fs=record.fs,
    time_units='seconds',
    sig_name=['ECG'],
    ann_style=['go', 'r^'],  # Green circles for N, red triangles for V
    title='Normal vs PVC Beats',
    figsize=(15, 6)
)
plt.show()

Saving Plots

import wfdb
import matplotlib.pyplot as plt

# Create plot
record = wfdb.rdrecord('100', pn_dir='mitdb')
fig = wfdb.plot.plot_wfdb(record=record, time_units='seconds',
                         title='ECG Record 100', return_fig=True)

# Save in different formats
fig.savefig('ecg_record_100.png', dpi=300, bbox_inches='tight')
fig.savefig('ecg_record_100.pdf', bbox_inches='tight')
fig.savefig('ecg_record_100.svg', bbox_inches='tight')

plt.close(fig)  # Clean up memory

Install with Tessl CLI

npx tessl i tessl/pypi-wfdb

docs

format-conversion.md

index.md

io-operations.md

plotting.md

signal-processing.md

tile.json