Tutorial 1: QC report for ecg data

This tutorial demonstrates the QC report capability of niphlem. For simplicity, we focus on the cardiac signal from ecg for one subject from one of our internal projects.

Define filepaths for physiological data (ECG and info logs):

[1]:
info_log = "./data/demo/physio/Physio_20210322_140315_89a222d1-4c24-4caf-a898-f06c6bfd2342_Info.log"
ecg_log = "./data/demo/physio/Physio_20210322_140315_89a222d1-4c24-4caf-a898-f06c6bfd2342_ECG.log"

Load data

First load the info file, which provides scanner timing using the niphlem.input_data.load_cmrr_info function.

[2]:
from niphlem.input_data import load_cmrr_info

print(load_cmrr_info.__doc__)

    Load information log files from CMRR sequences.

    Parameters
    ----------
    filename : str, pathlike
        Path to Information Log file.

    Returns
    -------
    traces : ndarray
        Time ticks of the scanner.
    meta_info : dict
        Dictionary with meta information about the info log file.


[3]:
time_traces, meta_info = load_cmrr_info(info_log)

Next load our ECG data using the corresponding niphlem.input_data.load_cmrr_data function. We specify the type of signal as an input.

[4]:
from niphlem.input_data import load_cmrr_data

print(load_cmrr_data.__doc__)

    Load data log files from CMRR sequences.

    Parameters
    ----------
    filename : str, pathlike
        Path to recording log file..
    sig_type : str
        Type of signal for use in dictionary
    info_dict : dict
        Dictionary with the meta information of the Info log file. It needs
        to be compute before by using the function load_cmrr_info.
    sync_scan : bool, optional
        Whether we want to resample the signal to be synchronized
        with the scanner times. The default is True.

    Returns
    -------
    signal : ndarray
        The recording signal, where the number of columns corresponds
        to the number of channels (ECG: 4, PULS: 1, RESP: 1) and the rows to
        observations.
    info_dict : dict
        Updated meta info of the physiological recording.


[5]:
ecg_signal, meta_info = load_cmrr_data(ecg_log, info_dict=meta_info, sig_type="ECG")

Run QC

To run QC on our ECG data, we use the niphlem.report.make_ecg_report function.

[6]:
from niphlem.report import make_ecg_report

print(make_ecg_report.__doc__)

    Generate QC report for ECG data.
    Parameters
    ----------
    ecg_signal : array-like of shape (n_physio_samples, ),
        or (n_physio_samples, n_channels).
        ECG Signal, where each column corresponds to a recording.
    fs : float
        Sampling frequency of ECG recording.
    delta: float
        minimum separation (in physio recording units) between
        events in signal to be considered peaks
    peak_rise: float
        relative height with respect to the 20th tallest events in signal
        to consider events as peak. The default is 0.75.
    ground : integer, optional
        Column in the input signal to be considered as a ground channel.
        This signal will be then substracted from the other channels.
        The default is None.
    high_pass : float, optional
        High-pass filtering frequency (in Hz). Only if filtering option
        is not None. The default is 0.6.
    low_pass : float, optional
        Low-pass filtering frequency (in Hz). Only if filtering option
        is not None. The default is 5.0.
    outpath : string, optional
        If provided, Path where report the HTML report,
        averaged filtered signal and corrected peaks will be saved.
        The default is None.
    Returns
    -------
    report : html file
        HTML report.
    output_dict : dict
        Dictionary with the filtered signal and (corrected) peak locations.

As we can see, this function takes a number of inputs:

  • our ECG signal

  • sampling frequency of data signal

  • minimum separation between signal events

  • relative height of signal events (default 0.75)

  • ground signal channel (optional, default None)

  • high-pass filter frequency (default 0.6 Hz)

  • low-pass filter frequency (default 5 Hz)

  • output filepath (optional, default None)

Let’s set these input values:

[7]:
fs = 400
delta = 200
peak_rise = 0.75
ground = 0
high_pass = 0.6
low_pass = 5.0
outpath = "./"

Now we can run the function. There are three outputs:

  1. The HTML report object, which contains the output of the quality control in html form (including graphs and statistics). If outpath is provided, this will be saved as ecg_qc.html ecg_qc.html.

  2. A dictionary with some interesting information about the processed signal:

    • An array with he timeseries of the average filtered ECG signal. If outpath is provided, this will be saved as transformed_signal_ecg.txt.

    • An array that contains the onset times of each detected peak of the signal - in the case of ECG, this is the R component of the QRS waveform. If outpath is provided, this will be saved as corrected_peaks_ecg.txt.

[8]:
ecg_report, output_dict = make_ecg_report(ecg_signal,
                                          fs=fs,
                                          delta=delta,
                                          ground=ground,
                                          high_pass=high_pass,
                                          low_pass=low_pass,
                                          outpath=outpath)
Transformed ECG signal saved in: /home/javi/Documentos/niphlem/examples/transformed_signal_ecg.txt
ECG peaks saved in: /home/javi/Documentos/niphlem/examples/corrected_peaks_ecg.txt
QC report for ECG signal saved in: /home/javi/Documentos/niphlem/examples/ecg_qc.html

We can always inspect directly the report on the jupyter-notebook server by just calling it

[9]:
ecg_report
[9]:

niphlem: ECG signal processing and peak detection QC report

niphlem version 0.0.1

Filtering:

ECG sampling rate: 400
lower bandpass frequency: 0.6
higher bandpass frequency: 5.0
minimum peak separation: 200
relative peak height: 0.75

Plot A) unfiltered and filtered signals, B) power spectrum, C) timeseries snapshot with marked peaks, D) average QRS signal, E) RR interval histogram and F) instantaneous heart rate

Heart rate statistics (bpm):

mean 95% CI (lower bound) 95% CI (upper bound)
0 66.84 66.1 67.58


RR interval statistics (ms):

mean median standard deviation SNR
0 371.14 347.0 91.26 4.07


Correct anomalies/artifacts in instantaneous heart rate

RR interval outliers detected via one-sided Grubb's test (RR intervals that are too large and result in an abnormally slow instantaneous heart rate): [96, 78, 88, 552, 81, 204, 60, 512, 42, 195, 71, 72, 73, 160, 44, 342, 388, 139, 48, 123, 124, 359, 383, 249, 407, 198, 64, 109, 137, 163, 127, 138, 469, 491, 348, 274, 0, 228, 229, 525]

RR interval outliers detected via one-sided Grubb's test (RR intervals that are too small and result in an abnormally fast instantaneous heart rate): []

Plot A) timeseries snapshot with corrected peaks marked, B) corrected average QRS signal, C) corrected RR interval histogram and D) corrected instantaneous heart rate

Corrected heart rate statistics (bpm):

mean 95% CI (lower bound) 95% CI (upper bound)
0 69.05 68.87 69.24

Corrected RR interval statistics (ms):

mean median standard deviation SNR
0 347.94 346.0 11.78 29.55


Compare pre and post corrected graphs

Side-by-side comparison of average QRS (A, B), RR interval histogram (C, D) and instantaneous heart rate (E, F) before (left) and after (right) anomaly/artifact correction
Built using niphlem.
[10]:
# or alternatively

# from IPython.display import IFrame
# IFrame(src='./ecg_qc.html', width=1000, height=600)

Finally, we can always save this report afterwards, without passing a path, by using its method .save_as_html

[11]:
# ecg_report.save_as_html("./the_same_qc_report.html")
# IFrame(src='./the_same_qc_report.html', width=1000, height=600)

As we said above, interesting information about the processed signal is stored and return as a dictionary, along with the report

[12]:
output_dict
[12]:
{'filtered_signal': array([-48.10100072, -57.92063763, -67.42962   , ...,   9.47558275,
          8.16626142,   6.97466811]),
 'peaks': array([     0,    326,    653,    996,   1360,   1735,   2100,   2472,
          2846,   3211,   3559,   3895,   4250,   4610,   4965,   5319,
          5674,   6046,   6407,   6766,   7127,   7491,   7852,   8216,
          8576,   8926,   9272,   9618,   9970,  10324,  10670,  11015,
         11367,  11720,  12072,  12429,  12776,  13122,  13457,  13788,
         14120,  14445,  14773,  15115,  15475,  15835,  16184,  16537,
         16891,  17241,  17581,  17923,  18273,  18624,  18977,  19330,
         19680,  20029,  20363,  20700,  21041,  21387,  21743,  22106,
         22485,  22846,  23208,  23557,  23904,  24246,  24590,  24934,
         25288,  25640,  25995,  26358,  26713,  27061,  27417,  27773,
         28127,  28482,  28836,  29191,  29540,  29884,  30245,  30598,
         30966,  31334,  31710,  32083,  32447,  32811,  33157,  33497,
         33844,  34201,  34565,  34923,  35290,  35658,  36016,  36360,
         36702,  37045,  37388,  37730,  38066,  38414,  38762,  39110,
         39466,  39821,  40164,  40501,  40839,  41180,  41521,  41864,
         42211,  42564,  42916,  43266,  43610,  43954,  44299,  44640,
         44982,  45323,  45675,  46027,  46376,  46728,  47088,  47444,
         47794,  48144,  48493,  48842,  49192,  49541,  49890,  50235,
         50576,  50917,  51258,  51598,  51937,  52285,  52630,  52975,
         53320,  53666,  54000,  54334,  54677,  55020,  55360,  55701,
         56053,  56405,  56749,  57097,  57440,  57782,  58125,  58463,
         58808,  59156,  59503,  59857,  60222,  60581,  60933,  61286,
         61641,  61984,  62330,  62676,  63021,  63367,  63721,  64076,
         64431,  64776,  65118,  65460,  65794,  66143,  66479,  66816,
         67143,  67487,  67832,  68174,  68519,  68870,  69219,  69551,
         69886,  70225,  70562,  70899,  71235,  71576,  71924,  72277,
         72618,  72962,  73299,  73644,  73990,  74335,  74680,  75033,
         75393,  75746,  76104,  76463,  76823,  77173,  77518,  77862,
         78207,  78547,  78904,  79269,  79630,  79989,  80352,  80715,
         81070,  81424,  81780,  82135,  82492,  82854,  83220,  83574,
         83922,  84272,  84619,  84978,  85351,  85729,  86104,  86466,
         86806,  87146,  87477,  87819,  88182,  88562,  88950,  89344,
         89738,  90121,  90471,  90796,  91121,  91466,  91825,  92204,
         92577,  92944,  93292,  93652,  94017,  94377,  94729,  95092,
         95455,  95813,  96162,  96501,  96848,  97195,  97538,  97885,
         98229,  98573,  98916,  99260,  99600,  99940, 100272, 100599,
        100939, 101285, 101619, 101957, 102296, 102640, 102978, 103318,
        103663, 104014, 104360, 104703, 105047, 105395, 105730, 106066,
        106407, 106750, 107099, 107444, 107791, 108136, 108472, 108802,
        109130, 109465, 109804, 110150, 110495, 110840, 111180, 111517,
        111863, 112218, 112575, 112942, 113309, 113673, 114031, 114384,
        114732, 115072, 115413, 115759, 116111, 116455, 116801, 117149,
        117490, 117832, 118174, 118509, 118839, 119173, 119510, 119860,
        120196, 120537, 120892, 121242, 121578, 121904, 122239, 122572,
        122902, 123242, 123598, 123955, 124307, 124642, 124972, 125314,
        125654, 125988, 126313, 126643, 126991, 127340, 127692, 128045,
        128405, 128757, 129101, 129454, 129807, 130155, 130510, 130857,
        131195, 131532, 131868, 132204, 132551, 132881, 133217, 133562,
        133914, 134261, 134602, 134951, 135300, 135645, 135994, 136343,
        136688, 137026, 137367, 137700, 138033, 138375, 138717, 139061,
        139402, 139749, 140081, 140407, 140727, 141056, 141391, 141738,
        142081, 142431, 142788, 143135, 143474, 143797, 144128, 144476,
        144825, 145187, 145547, 145902, 146250, 146603, 146956, 147316,
        147670, 148013, 148362, 148712, 149057, 149401, 149747, 150107,
        150473, 150832, 151185, 151533, 151884, 152233, 152584, 152931,
        153274, 153620, 153966, 154321, 154684, 155039, 155382, 155724,
        156062, 156395, 156729, 157065, 157405, 157748, 158100, 158453,
        158802, 159147, 159491, 159835, 160179, 160516, 160864, 161203,
        161534, 161867, 162200, 162535, 162875, 163216, 163564, 163905,
        164234, 164571, 164904, 165243, 165577, 165909, 166244, 166583,
        166927, 167275, 167635, 167993, 168339, 168683, 169027, 169356,
        169690, 170038, 170399, 170760, 171134, 171503, 171853, 172200,
        172536, 172878, 173222, 173570, 173915, 174262, 174603, 174947,
        175284, 175621, 175957, 176289, 176624, 176976, 177326, 177671,
        178018, 178367, 178709, 179046, 179394, 179733, 180065, 180402,
        180737, 181075, 181412, 181746, 182090, 182431, 182766, 183103,
        183440, 183776, 184099, 184433, 184766, 185093, 185430, 185762,
        186103, 186443, 186787, 187131, 187466, 187801, 188147, 188493,
        188826, 189167, 189499, 189844, 190192, 190552, 190913, 191286,
        191658, 192012, 192365, 192701, 193034, 193360, 193708, 194074,
        194451, 194826, 195208, 195599, 195970, 196326, 196667, 197011,
        197337, 197664, 197999, 198329, 198669, 198995, 199338, 199684,
        200038, 200391, 200751, 201118, 201495, 201857, 202212, 202579,
        202949, 203318, 203679, 204044, 204410, 204770, 205136, 205503,
        205864, 206213, 206555, 206905, 207249, 207593, 207948, 208311,
        208672, 209044, 209407, 209763, 210112, 210458, 210810, 211170,
        211549])}