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:
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.
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.1Filtering:
ECG sampling rate: 400lower 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])}