前言
本软件设计基于Python编程语言,实现从心电图数据的读取、心电图的绘制、对心电图的操作、相关心电数据的显示以及病症结论的收录及显示。相关算法部分调用自Python专门处理心电数据的第三方库Heartpy。
心电数据的读取
XML解析
由于从老师处得到的心电数据是以XML格式存储的,本文就只介绍如何用Python来解析XML格式的心电数据,至于什么是XML,请自行bd~
可以看到这边收录了几个XML格式的心电数据文件,但是根据文件特性我们不能像打开TXT那样直接使用它。
用浏览器打开是这个样子。
我们调用Python中的LXML库来解析,文件操作用OS和GLOB库。
from lxml import etree#导入lxml库
import os
import glob
path = 'D:\ECG' #设置路径
path_list = os.listdir(path) #获取目录中的内容
path_list.sort(key=lambda x:int(x.split('.')[0])) #整理文件数据及类型
path_file_number = glob.glob('D:\ECG\*.xml') #获取此路径下的所有XML文件并返回一个List
local = 0
sj = open(os.path.join(path,path_list[local]),'rb') #从第一个开始打开文件
tree = etree.parse(sj) #将xml解析为树结构
root = tree.getroot() #获得该树的树根
由于存储的心电数据是基于临床十二导联记录下来的,故XML文件里会有12个类似于<I>,<II>......,<avf>这样的12个节点,我们仅需对这12个节点做操作即可,开头的节点删去。
for child in root[1:2]: #从第二个节点开始操作
lst1 = []
lst1 = child.text
lst1 = lst1.split(" ")
lst1 = lst1[:-1:] #由于发现心电数据中最后一位总是有空格存在,故删掉最后一位。
I = [ int(float(i)) for i in lst1 ] #将心电数据先转化为浮点型再转化为整型,用于绘制心电图
#print(I)
for child in root[2:3]:
lst2 = []
lst2 = child.text
lst2 = lst2.split(" ")
lst2 = lst2[:-1:]
II = [ int(float(i)) for i in lst2 ]
#print(II)
for child in root[3:4]:
lst3 = []
lst3 = child.text
lst3 = lst3.split(" ")
lst3 = lst3[:-1:]
III = [ int(float(i)) for i in lst3 ]
#print(III)
for child in root[4:5]:
lst4 = []
lst4 = child.text
lst4 = lst4.split(" ")
lst4 = lst4[:-1:]
AVF = [ int(float(i)) for i in lst4 ]
#print(AVF)
for child in root[5:6]:
lst5 = []
lst5 = child.text
lst5 = lst5.split(" ")
lst5 = lst5[:-1:]
AVR = [ int(float(i)) for i in lst5 ]
#print(AVR)
for child in root[6:7]:
lst6 = []
lst6 = child.text
lst6 = lst6.split(" ")
lst6 = lst6[:-1:]
AVL = [ int(float(i)) for i in lst6 ]
#print(AVL)
for child in root[7:8]:
lst7 = []
lst7 = child.text
lst7 = lst7.split(" ")
lst7 = lst7[:-1:]
V1 = [ int(float(i)) for i in lst7 ]
#print(V1)
for child in root[8:9]:
lst8 = []
lst8 = child.text
lst8 = lst8.split(" ")
lst8 = lst8[:-1:]
V2 = [ int(float(i)) for i in lst8 ]
#print(V2)
for child in root[9:10]:
lst9 = []
lst9 = child.text
lst9 = lst9.split(" ")
lst9 = lst9[:-1:]
V3 = [ int(float(i)) for i in lst9 ]
#print(V3)
for child in root[10:11]:
lst10 = []
lst10 = child.text
lst10 = lst10.split(" ")
lst10 = lst10[:-1:]
V4 = [ int(float(i)) for i in lst10 ]
#print(V4)
for child in root[11:12]:
lst11 = []
lst11 = child.text
lst11 = lst11.split(" ")
lst11 = lst11[:-1:]
V5 = [ int(float(i)) for i in lst11 ]
#print(V5)
for child in root[12:13]:
lst12 = []
lst12 = child.text
lst12 = lst12.split(" ")
lst12 = lst12[:-1:]
V6 = [ int(float(i)) for i in lst12 ]
#print(V6)
我们截取其中一个节点的信息,可以看到已经成功解析并读取上来。
心电图的绘制
成功得到心电数据之后,我们调用Python中的2D绘图库Matplotlib来进行心电图的绘制。
导入相关模块
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,NavigationToolbar2Tk
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
我们将心电数据中的12个节点依次绘制出心电信号曲线,组成一张完整的十二导联心电图。
a = plt.subplot(12,1,1); #12张中的第一张
plt.plot(np.linspace(0, 100 * np.pi, 5000),I) #5000个数据就给5000个点
plt.ylabel('I')
plt.plot()
plt.grid()
a = plt.subplot(12,1,2);
plt.plot(np.linspace(0, 100 * np.pi, 5000),II)
plt.ylabel('II')
plt.plot()
plt.grid()
a = plt.subplot(12,1,3);
plt.plot(np.linspace(0, 100 * np.pi, 5000),III)
plt.ylabel('III')
plt.plot()
plt.grid()
a = plt.subplot(12,1,4);
plt.plot(np.linspace(0, 100 * np.pi, 5000),AVF)
plt.ylabel('AVF')
plt.plot()
plt.grid()
a = plt.subplot(12,1,5);
plt.plot(np.linspace(0, 100 * np.pi, 5000),AVR)
plt.ylabel('AVR')
plt.plot()
plt.grid()
a = plt.subplot(12,1,6);
plt.plot(np.linspace(0, 100 * np.pi, 5000),AVL)
plt.ylabel('AVL')
plt.plot()
plt.grid()
a = plt.subplot(12,1,7);
plt.plot(np.linspace(0, 100 * np.pi, 5000),V1)
plt.ylabel('V1')
plt.plot()
plt.grid()
a = plt.subplot(12,1,8);
plt.plot(np.linspace(0, 100 * np.pi, 5000),V2)
plt.ylabel('V2')
plt.plot()
plt.grid()
a = plt.subplot(12,1,9);
plt.plot(np.linspace(0, 100 * np.pi, 5000),V3)
plt.ylabel('V3')
plt.plot()
plt.grid()
a = plt.subplot(12,1,10);
plt.plot(np.linspace(0, 100 * np.pi, 5000),V4)
plt.ylabel('V4')
plt.plot()
plt.grid()
a = plt.subplot(12,1,11);
plt.plot(np.linspace(0, 100 * np.pi, 5000),V5)
plt.ylabel('V5')
plt.plot()
plt.grid()
a = plt.subplot(12,1,12);
plt.plot(np.linspace(0, 100 * np.pi, 5000),V6)
plt.ylabel('V6')
plt.plot()
plt.grid()
plt.show()
成功绘制心电图~
Tkinter设计上位机界面
Matplotlib与Tkinter的集成
Python有许多关于上位机界面设计的库,我们采用纯代码设计的Tkinter库,方便又快捷。
导入TK库
import tkinter as tk
利用TK自带的画布及绘图器作画,将心电图摆上去。
Matplotlib中的这个FigureCanvasXAgg渲染器是个很奇怪的东西~至于是什么自行BD
至于组件的摆放有很多种方式,PACK啊什么的,我这里采用Place绝对坐标的方式摆放,就是爱折腾,缺点就是得最大化才能看得清软件全貌。。
window = tk.Tk() #创建窗口
window.title("心电数据")
# set a figure
f = Figure(figsize=(11, 7), dpi=100) #创建一个画布
a = f.add_subplot(12,1,1)
a.plot(np.linspace(0, 100 * np.pi, 5000),I)
a.set_ylabel('I')
plt.ylabel('I')
a.grid()
a = f.add_subplot(12,1,2);
a.plot(np.linspace(0, 100 * np.pi, 5000),II)
a.set_ylabel('II')
plt.ylabel('I')
a.grid()
a = f.add_subplot(12,1,3);
a.plot(np.linspace(0, 100 * np.pi, 5000),III)
a.set_ylabel('III')
plt.ylabel('I')
a.grid()
a = f.add_subplot(12,1,4);
a.plot(np.linspace(0, 100 * np.pi, 5000),AVF)
a.set_ylabel('AVF')
plt.ylabel('I')
a.grid()
a = f.add_subplot(12,1,5);
a.plot(np.linspace(0, 100 * np.pi, 5000),AVR)
a.set_ylabel('AVR')
plt.ylabel('I')
a.grid()
a = f.add_subplot(12,1,6);
a.plot(np.linspace(0, 100 * np.pi, 5000),AVL)
a.set_ylabel('AVL')
plt.ylabel('I')
a.grid()
a = f.add_subplot(12,1,7);
a.plot(np.linspace(0, 100 * np.pi, 5000),V1)
a.set_ylabel('V1')
plt.ylabel('I')
a.grid()
a = f.add_subplot(12,1,8);
a.plot(np.linspace(0, 100 * np.pi, 5000),V2)
a.set_ylabel('V2')
plt.ylabel('I')
a.grid()
a = f.add_subplot(12,1,9);
a.plot(np.linspace(0, 100 * np.pi, 5000),V3)
a.set_ylabel('V3')
plt.ylabel('I')
a.grid()
a = f.add_subplot(12,1,10);
a.plot(np.linspace(0, 100 * np.pi, 5000),V4)
a.set_ylabel('V4')
plt.ylabel('I')
a.grid()
a = f.add_subplot(12,1,11);
a.plot(np.linspace(0, 100 * np.pi, 5000),V5)
a.set_ylabel('V5')
plt.ylabel('I')
a.grid()
a = f.add_subplot(12,1,12);
a.plot(np.linspace(0, 100 * np.pi, 5000),V6)
a.set_ylabel('V6')
plt.ylabel('I')
a.plot()
a.grid()
canvas = FigureCanvasTkAgg(f, master=window)
canvas.draw()
canvas.get_tk_widget().place(x=10,y=200) #采用绝对坐标摆放
toolbar = NavigationToolbar2Tk(canvas, window) #画布底部的操作栏,不要可以删去
toolbar.update()
window.mainloop() #循环显示窗口
至此成功把心电图摆上去啦
心电图操作
切换心电图
def nextstep():
path = 'D:\ECG'
path_list = os.listdir(path)
path_list.sort(key=lambda x:int(x.split('.')[0]))
global local
global path_file_number
path_file_number=glob.glob('D:\ECG\*.xml')
local = local + 1
if local == (len(path_file_number)): #由最后一张心电图切换至第一张
local = 0
fw = open(os.path.join(path,path_list[local]),'rb')
tree = etree.parse(fw)
root = tree.getroot()
for child in root[1:2]:
lst1 = []
lst1 = child.text
lst1 = lst1.split(" ")
lst1 = lst1[:-1:]
I = [ int(float(i)) for i in lst1 ]
#print(I)
#创建按钮绑定函数事件
button = tk.Button(window, text='下一个',bg='sky blue',width=12, height=2,command=nextstep)
button.place(x=980,y=30)
心电数据滤波
心电数据处理部分都调用第三方库Heartpy来处理。
导入心电数据处理模块
import heartpy as hp
三种滤波方式(高通、低通、带通)
#高通滤波
def high_filter():
path = 'D:\ECG'
path_list = os.listdir(path)
path_list.sort(key=lambda x:int(x.split('.')[0]))
#print(path_list)
global local
local = local
fw = open(os.path.join(path,path_list[local]),'rb')
tree = etree.parse(fw)
root = tree.getroot()
for child in root[1:2]:
lst1 = []
lst1 = child.text
lst1 = lst1.split(" ")
lst1 = lst1[:-1:]
I = [ int(float(i)) for i in lst1 ]
I = hp.filter_signal(I, cutoff=0.75, sample_rate=500.0, order=3, filtertype='highpass')
#print(I)
#低通滤波
I = hp.filter_signal(I, cutoff=15, sample_rate=500.0, order=3, filtertype='lowpass')
#带通滤波
I = hp.filter_signal(I, cutoff=[0.75, 15], sample_rate=500.0, order=3, filtertype='bandpass')
正常情况心电图
带通滤波后
心电特性数据读取处理
还是一样利用Heartpy这个专门处理心电数据的第三方库来帮我们处理
I = hp.scale_data(I)
working_data, measures = hp.process(I, 500.0)
print(working_data)
print(measures)
通过输出结果我们可以很轻易地从里面提取到有用的信息,例如R峰的定位、各类波的间期持续时间、以及得到HRV分析的一些常用数据指标。
以输出“心率”这个数值为例
def heart_rate():
path = 'D:\ECG'
global path_list
path_list = os.listdir(path)
path_list.sort(key=lambda x:int(x.split('.')[0]))
global local
global path_file_number
local = local
path_file_number=glob.glob('D:\ECG\*.xml')
fw = open(os.path.join(path,path_list[local]),'rb')
tree = etree.parse(fw)
root = tree.getroot()
for child in root[11:12]:
lst1 = []
lst1 = child.text
lst1 = lst1.split(" ")
lst1 = lst1[:-1:]
I = [ int(float(i)) for i in lst1 ]
I = hp.scale_data(I) #心电数据处理
working_data, measures = hp.process(I, 500.0)
t1.delete("0.0","15.0") #清空text框内的数据
t1.insert("end",measures['bpm']) #提取BPM数值并输入进text框
t1 = tk.Text(window, width=12, height=2) #创建一个text文本框用于显示BPM数值
t1.place(x=1300,y=40)
#创建一个label组件用于单位显示
l2 =tk.Label(window,bg='white',width=10,height=2,text='bpm')
l2.place(x=1400,y=35)
显示结果