├── .gitignore ├── LICENSE ├── README.md ├── data ├── Untitled.ipynb ├── __init__.py ├── aha │ ├── 0001.atr │ ├── 0001.dat │ ├── 0001.hea │ ├── 0201.atr │ ├── 0201.dat │ └── 0201.hea ├── covert.py ├── mit-bih-arrhythmia │ ├── 100.atr │ ├── 100.dat │ ├── 100.hea │ ├── 101.atr │ ├── 101.dat │ ├── 101.hea │ ├── 105.atr │ ├── 105.dat │ ├── 105.hea │ ├── 114.atr │ ├── 114.dat │ ├── 114.hea │ ├── 200.atr │ ├── 200.dat │ ├── 200.hea │ ├── 200.xws │ ├── 203.atr │ ├── 203.dat │ └── 203.hea ├── mit-bih-noise │ ├── 118e00.atr │ ├── 118e00.dat │ ├── 118e00.hea │ ├── 118e00.xws │ ├── 118e06.atr │ ├── 118e06.dat │ ├── 118e06.hea │ ├── 118e06.xws │ ├── 118e12.atr │ ├── 118e12.dat │ ├── 118e12.hea │ ├── 118e12.xws │ ├── 118e18.atr │ ├── 118e18.dat │ ├── 118e18.hea │ ├── 118e18.xws │ ├── 118e24.atr │ ├── 118e24.dat │ ├── 118e24.hea │ ├── 118e24.xws │ ├── 118e_6.atr │ ├── 118e_6.dat │ ├── 118e_6.hea │ └── 118e_6.xws ├── paf │ ├── n01.dat │ ├── n01.hea │ ├── n01.qrs │ ├── n01c.dat │ ├── n01c.hea │ └── n01c.qrs ├── read_data.py └── wfdb_read.py ├── docs └── __init__.py ├── ecg ├── __init__.py ├── basic_alg │ ├── __init__.py │ ├── delay_coordinate_mapping.py │ └── pan_tompkin.py └── deep_learning ├── img ├── img1.jpg ├── img2.jpg ├── img3.jpg ├── img4.jpg ├── img5.jpg ├── img6.png └── img7.png ├── paper ├── A Real Time QRS Detection Using Delay-Coordinate Mapping for the Microcontroller Implementation.pdf ├── A Wavelet-Based ECG Delineator Evaluation on Standard Databases.pdf ├── An Efficient Algorithm for Automatic Peak Detection in Noisy Periodic and Quasi-Periodic Signals.pdf ├── Unsupervised Detection and Classification of Motor Unit Action Potentials in Intramuscular Electromyography Signals.pdf ├── __init__.py └── paper_list.md ├── tests ├── __init__.py └── basic_alg_demo │ ├── QRS_detected_demo.py │ └── __init__.py └── utils ├── 1.jpg ├── __init__.py ├── base_maths.py ├── ecg_display.py └── signal_process.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # pyCharm 132 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 DFQX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 1 ecg-algorithm 2 | 使用python编写心电相关的算法,包括滤波,R波识别,心率计算,特征提取,心率失常分类,房颤,伪差干扰研究等相关算法,以及较好的可视化功能,和一些测试工程 3 | 4 | # 2 已经实现的程序 5 | ## 2.1 数据读取 6 | ### 2.1.1 Format212 数据格式读取 7 | - 以MIT-BIH心电数据库中100.dat为例 8 | ![img.png](img/img1.jpg) 9 | ![img.png](img/img2.jpg) 10 | ### 2.1.2 Format16 数据格式读取 11 | - PAF数据库n01为例 12 | ![img.png](img/img3.jpg) 13 | ![img_1.png](img/img4.jpg) 14 | ## 2.2 Pan-Tompkins算法 15 | ### 2.2.1 简介 16 | Pan-Tompkins算法最早在1985年发表,它是一种高效实用的QRS波检测算法,至今仍然被许多心电监护仪、 17 | Holter和一些穿戴式设备所实用或者借鉴算法思路进行优化。 18 | ### 2.2.2 代码使用 19 | - 路径:ecg/basic_alg/pan_tompin.py 20 | - 直接运行代码即可 21 | - 输出图片 22 | ![img_1.png](img/img5.jpg) 23 | ## 2.3 相位重构算法 24 | ### 2.3.1 简介 25 | 通过非线性动力学进行的相位重构算法,应用在ECG的R波识别上,具有较高的准确率,并对干扰环境中 26 | 也保持良好的性能。 27 | ### 2.3.2 代码使用 28 | - 路径:ecg/base_alg/delay_coordinate_mapping.py 29 | - 直接运行代码即可 30 | - 图片,一些图片需要打开注释才可以 31 | ![img_1.png](img/img6.png) 32 | ![img7.png](img/img7.png) 33 | -------------------------------------------------------------------------------- /data/Untitled.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "3ec640f4", 6 | "metadata": {}, 7 | "source": [ 8 | "$$f(x,y,z) = 3y^2z \\left( 3+\\frac{7x+5}{1+y^2} \\right)$$\n", 9 | "$$ \\left. \\frac{{\\rm d}u}{{\\rm d}x} \\right| _{x=0} $$" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "id": "8b72c5f3", 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [] 19 | } 20 | ], 21 | "metadata": { 22 | "kernelspec": { 23 | "display_name": "Python 3", 24 | "language": "python", 25 | "name": "python3" 26 | }, 27 | "language_info": { 28 | "codemirror_mode": { 29 | "name": "ipython", 30 | "version": 3 31 | }, 32 | "file_extension": ".py", 33 | "mimetype": "text/x-python", 34 | "name": "python", 35 | "nbconvert_exporter": "python", 36 | "pygments_lexer": "ipython3", 37 | "version": "3.8.8" 38 | } 39 | }, 40 | "nbformat": 4, 41 | "nbformat_minor": 5 42 | } 43 | -------------------------------------------------------------------------------- /data/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | ''' 4 | @Project :ecg-algorithm 5 | @File :__init__.py.py 6 | @Author :dell 7 | @Date :2022/8/10 17:59 8 | @Description: 9 | ''' 10 | -------------------------------------------------------------------------------- /data/aha/0001.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/aha/0001.atr -------------------------------------------------------------------------------- /data/aha/0001.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/aha/0001.dat -------------------------------------------------------------------------------- /data/aha/0001.hea: -------------------------------------------------------------------------------- 1 | 0001 2 250 2700000 2 | 0001.dat 212 400 12 0 -83 -16218 0 ECG 3 | 0001.dat 212 400 12 0 -21 6575 0 ECG 4 | -------------------------------------------------------------------------------- /data/aha/0201.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/aha/0201.atr -------------------------------------------------------------------------------- /data/aha/0201.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/aha/0201.dat -------------------------------------------------------------------------------- /data/aha/0201.hea: -------------------------------------------------------------------------------- 1 | 0201 2 250 525000 2 | 0201.dat 212 400 12 0 -42 23134 0 ECG 3 | 0201.dat 212 400 12 0 -33 -29466 0 ECG 4 | -------------------------------------------------------------------------------- /data/covert.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Project : 3 | @File :covert.py 4 | @Author :DFQX 5 | @Date :2022/9/18 14:50 6 | @Description: 一些转换工具 7 | """ 8 | import numpy as np 9 | 10 | 11 | def covert_freq(ori_freq, tar_freq, data): 12 | """ 13 | 将信号频率进行转换, 只能进行降采样,这里不做插值 14 | 即tar_freq<=ori_freq 15 | :param ori_freq: int, 原始信号频率 16 | :param tar_freq: int, 转换后信号频率 17 | :param data: list, 信号数据 18 | :return: list, 返回后的信号 19 | """ 20 | if len(data) == 0: 21 | raise Exception('数据长度为0') 22 | if ori_freq < tar_freq: 23 | raise Exception('原始频率应该大于目标频率!') 24 | ori_time = np.arange(0, len(data), 1 / ori_freq) # 原始数据坐标时间 25 | tar_time = np.arange(0, int(len(data) * tar_freq // ori_freq), 1 / tar_freq) # 转换数据坐标时间 26 | tar_data, j = [], 0 27 | for idx, v in enumerate(ori_time): 28 | if idx >= len(data): 29 | break 30 | if tar_time[j] <= ori_time[idx]: 31 | tar_data.append(data[idx]) 32 | j += 1 33 | return tar_data 34 | -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/100.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-arrhythmia/100.atr -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/100.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-arrhythmia/100.dat -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/100.hea: -------------------------------------------------------------------------------- 1 | 100 2 360 650000 2 | 100.dat 212 200 11 1024 995 -22131 0 MLII 3 | 100.dat 212 200 11 1024 1011 20052 0 V5 4 | # 69 M 1085 1629 x1 5 | # Aldomet, Inderal 6 | -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/101.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-arrhythmia/101.atr -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/101.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-arrhythmia/101.dat -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/101.hea: -------------------------------------------------------------------------------- 1 | 101 2 360 650000 2 | 101.dat 212 200 11 1024 955 29832 0 MLII 3 | 101.dat 212 200 11 1024 992 19589 0 V1 4 | # 75 F 1011 654 x1 5 | # Diapres 6 | -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/105.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-arrhythmia/105.atr -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/105.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-arrhythmia/105.dat -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/105.hea: -------------------------------------------------------------------------------- 1 | 105 2 360 650000 2 | 105.dat 212 200 11 1024 935 -24679 0 MLII 3 | 105.dat 212 200 11 1024 1076 -15565 0 V1 4 | # 73 F 1624 1629 x1 5 | # Digoxin, Nitropaste, Pronestyl 6 | # The PVCs are uniform. The predominant feature of this tape is 7 | # high-grade noise and artifact. 8 | -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/114.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-arrhythmia/114.atr -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/114.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-arrhythmia/114.dat -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/114.hea: -------------------------------------------------------------------------------- 1 | 114 2 360 650000 2 | 114.dat 212 200 11 1024 1015 -11371 0 V5 3 | 114.dat 212 200 11 1024 1027 -13230 0 MLII 4 | # 72 F 750 1629 x1 5 | # Digoxin 6 | # The PVCs are uniform. 7 | -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/200.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-arrhythmia/200.atr -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/200.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-arrhythmia/200.dat -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/200.hea: -------------------------------------------------------------------------------- 1 | 200 2 360 650000 2 | 200.dat 212 200 11 1024 1094 12838 0 MLII 3 | 200.dat 212 200 11 1024 1045 -25533 0 V1 4 | # 64 M 1953 3655 x1 5 | # Digoxin, Quinidine 6 | # The PVCs are multiform. There are occasional bursts of high-frequency 7 | # noise in the upper channel, and severe noise and artifact in the lower 8 | # channel. 9 | -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/200.xws: -------------------------------------------------------------------------------- 1 | -r 200 2 | -a atr 3 | -p http:// 4 | -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/203.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-arrhythmia/203.atr -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/203.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-arrhythmia/203.dat -------------------------------------------------------------------------------- /data/mit-bih-arrhythmia/203.hea: -------------------------------------------------------------------------------- 1 | 203 2 360 650000 2 | 203.dat 212 200 11 1024 972 5391 0 MLII 3 | 203.dat 212 200 11 1024 1037 28451 0 V1 4 | # 43 M 1878 356 x1 5 | # Coumadin, Digoxin, Heparin, Hygroton, Lasix 6 | # The PVCs are multiform. There are QRS morphology changes in the upper 7 | # channel due to axis shifts. There is considerable noise in both channels, 8 | # including muscle artifact and baseline shifts. This is a very difficult 9 | # record, even for humans! 10 | -------------------------------------------------------------------------------- /data/mit-bih-noise/118e00.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-noise/118e00.atr -------------------------------------------------------------------------------- /data/mit-bih-noise/118e00.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-noise/118e00.dat -------------------------------------------------------------------------------- /data/mit-bih-noise/118e00.hea: -------------------------------------------------------------------------------- 1 | 118e00 2 360 650000 2 | 118e00.dat 16 200(1024) 11 0 -167 29505 0 MLII 3 | 118e00.dat 16 200(1024) 11 0 -103 -16890 0 V1 4 | # Created by `nst' from records 118 and em (SNR = 0 dB) 5 | -------------------------------------------------------------------------------- /data/mit-bih-noise/118e00.xws: -------------------------------------------------------------------------------- 1 | -r 118e00 2 | -a atr 3 | -p http:// 4 | -------------------------------------------------------------------------------- /data/mit-bih-noise/118e06.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-noise/118e06.atr -------------------------------------------------------------------------------- /data/mit-bih-noise/118e06.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-noise/118e06.dat -------------------------------------------------------------------------------- /data/mit-bih-noise/118e06.hea: -------------------------------------------------------------------------------- 1 | 118e06 2 360 650000 2 | 118e06.dat 212 200(1024) 11 0 -167 25488 0 MLII 3 | 118e06.dat 212 200(1024) 11 0 -103 -30272 0 V1 4 | # Created by `nst' from records 118 and em (SNR = 6 dB) 5 | -------------------------------------------------------------------------------- /data/mit-bih-noise/118e06.xws: -------------------------------------------------------------------------------- 1 | -r 118e06 2 | -a atr 3 | -p http:// 4 | -------------------------------------------------------------------------------- /data/mit-bih-noise/118e12.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-noise/118e12.atr -------------------------------------------------------------------------------- /data/mit-bih-noise/118e12.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-noise/118e12.dat -------------------------------------------------------------------------------- /data/mit-bih-noise/118e12.hea: -------------------------------------------------------------------------------- 1 | 118e12 2 360 650000 2 | 118e12.dat 212 200(1024) 11 0 -167 -10416 0 MLII 3 | 118e12.dat 212 200(1024) 11 0 -103 -23139 0 V1 4 | # Created by `nst' from records 118 and em (SNR = 12 dB) 5 | -------------------------------------------------------------------------------- /data/mit-bih-noise/118e12.xws: -------------------------------------------------------------------------------- 1 | -r 118e12 2 | -a atr 3 | -p http:// 4 | -------------------------------------------------------------------------------- /data/mit-bih-noise/118e18.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-noise/118e18.atr -------------------------------------------------------------------------------- /data/mit-bih-noise/118e18.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-noise/118e18.dat -------------------------------------------------------------------------------- /data/mit-bih-noise/118e18.hea: -------------------------------------------------------------------------------- 1 | 118e18 2 360 650000 2 | 118e18.dat 212 200(1024) 11 0 -167 25073 0 MLII 3 | 118e18.dat 212 200(1024) 11 0 -103 -3026 0 V1 4 | # Created by `nst' from records 118 and em (SNR = 18 dB) 5 | -------------------------------------------------------------------------------- /data/mit-bih-noise/118e18.xws: -------------------------------------------------------------------------------- 1 | -r 118e18 2 | -a atr 3 | -p http:// 4 | -------------------------------------------------------------------------------- /data/mit-bih-noise/118e24.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-noise/118e24.atr -------------------------------------------------------------------------------- /data/mit-bih-noise/118e24.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-noise/118e24.dat -------------------------------------------------------------------------------- /data/mit-bih-noise/118e24.hea: -------------------------------------------------------------------------------- 1 | 118e24 2 360 650000 2 | 118e24.dat 212 200(1024) 11 0 -167 -13451 0 MLII 3 | 118e24.dat 212 200(1024) 11 0 -103 32392 0 V1 4 | # Created by `nst' from records 118 and em (SNR = 24 dB) 5 | -------------------------------------------------------------------------------- /data/mit-bih-noise/118e24.xws: -------------------------------------------------------------------------------- 1 | -r 118e24 2 | -a atr 3 | -p http:// 4 | -------------------------------------------------------------------------------- /data/mit-bih-noise/118e_6.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-noise/118e_6.atr -------------------------------------------------------------------------------- /data/mit-bih-noise/118e_6.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/mit-bih-noise/118e_6.dat -------------------------------------------------------------------------------- /data/mit-bih-noise/118e_6.hea: -------------------------------------------------------------------------------- 1 | 118e_6 2 360 650000 2 | 118e_6.dat 16 200(1024) 11 0 -167 1467 0 MLII 3 | 118e_6.dat 16 200(1024) 11 0 -103 26893 0 V1 4 | # Created by `nst' from records 118 and em (SNR = -6 dB) 5 | -------------------------------------------------------------------------------- /data/mit-bih-noise/118e_6.xws: -------------------------------------------------------------------------------- 1 | -r 118e_6 2 | -a atr 3 | -p http:// 4 | -------------------------------------------------------------------------------- /data/paf/n01.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/paf/n01.dat -------------------------------------------------------------------------------- /data/paf/n01.hea: -------------------------------------------------------------------------------- 1 | n01 2 128 230400 2 | n01.dat 16 200/mV 12 0 -3 20217 0 ECG0 3 | n01.dat 16 200/mV 12 0 0 -5775 0 ECG1 4 | -------------------------------------------------------------------------------- /data/paf/n01.qrs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/paf/n01.qrs -------------------------------------------------------------------------------- /data/paf/n01c.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/data/paf/n01c.dat -------------------------------------------------------------------------------- /data/paf/n01c.hea: -------------------------------------------------------------------------------- 1 | n01c 2 128 38400 2 | n01c.dat 16 200/mV 12 0 -4 7381 0 ECG0 3 | n01c.dat 16 200/mV 12 0 1 -10192 0 ECG1 4 | -------------------------------------------------------------------------------- /data/paf/n01c.qrs: -------------------------------------------------------------------------------- 1 | RSRRRQPRQOQPOPOQPPOPPPOPPOPPQQPQQQRRRQRQRQSRRRRRQRRQQQQRQQQQQRRQRQQRRRRRRQSQRRRQRRRRRRQQSSRSRRSSTSSRRSSQSRRRRSRRR!0SSSQSRSTTSSSSSRSSRRQRSRRRQRRRRRPQQRQRQQQRRRQQQOONNLMNNNMNMOOONPPOQQRQQPQSSRSSSSSTSSSSSRSSSRSSTSSSSSTSSRRQSSSRSRRTSSSSRSSRSRRSSTTSSSSSTSTSSTSTTSTSUTUTTTUUUTTTSUTTTTTUTVTTTTTTTTRTTUUUUTUUUUTTUUVVUUTUUUUTTUTUUUTTUUUUUUTVVVVVVVWWVVVUWVWUVUUVUUUTTUTUTTUUUUUUTVUVUVUUUWUUVUUVWVUVUVVVUVVVWVVVUUVVUVTUVUUTTTUUUTTTTTUUTTTTUVUTTUUUUUTTUUUUTTUUUSUUTTTTTTSTTV -------------------------------------------------------------------------------- /data/read_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | @Project :ecg-algorithm 5 | @File :read_data.py 6 | @Author :DFQX 7 | @Date :2022/8/10 17:59 8 | @Description: 读取ECG数据 9 | """ 10 | import os 11 | import matplotlib.pyplot as plt 12 | 13 | 14 | def read_hea(atr_path, hea_name): 15 | """ 16 | 读取注释文件'.hea'的内容 17 | :param atr_path: str, 注释文件路径 18 | :param hea_name: str, 注释文件名 19 | :return: dict 20 | """ 21 | data = {} 22 | with open(os.path.join(atr_path, hea_name), 'r') as file: 23 | line1 = file.readline().strip().split(' ') 24 | data['filename'] = line1[0] 25 | data['lead_num'] = int(line1[1]) 26 | data['sample_freq'] = int(line1[2]) 27 | data['sample_num'] = int(line1[3]) 28 | # 分别是信号格式,增益,采样精度,零值,第一个值(用于偏差校验) 29 | sformat, gain, bit_res, zero_value, first_value = [0] * data['lead_num'] \ 30 | , [0] * data['lead_num'], [0] * data['lead_num'] \ 31 | , [0] * data['lead_num'], [0] * data['lead_num'] 32 | for lead_idx in range(data['lead_num']): # 每个导联进行读取 33 | line = file.readline().strip().split(' ') 34 | sformat[lead_idx], gain[lead_idx], bit_res[lead_idx] = int(line[1]) \ 35 | , int(line[2]) if '/' not in line[2] else int(line[2][:-3]), int(line[3]) 36 | zero_value[lead_idx], first_value[lead_idx] = int(line[4]), int(line[5]) 37 | data['sformat'] = sformat 38 | data['gain'] = gain 39 | data['bit_res'] = bit_res 40 | data['zero_value'] = zero_value 41 | data['first_value'] = first_value 42 | return data 43 | 44 | 45 | def read_f212(file_path, file_name): 46 | """ 47 | 读取 format 212 格式文件 '.dat' 48 | :param file_path: str, 数据路径 49 | :param file_name: str, 数据文件名 50 | :return: 51 | """ 52 | data = [] 53 | with open(os.path.join(file_path, file_name), 'rb') as file: 54 | data = file.read() # 读取二进制文件 55 | ecg_data_lead1, ecg_data_lead2 = [], [] 56 | for i in range(0, len(data), 3): 57 | lead1 = ((data[i + 1] & 0x0f) << 8) + (data[i]) # 导联1数据读取 58 | lead2 = ((data[i + 1] & 0xf0) << 4) + (data[i + 2]) # 导联2数据读取 59 | if lead1 > 2047: # 由于加载的数据是无符号型,转换为二进制补码形式:value>2^11-1 为负值 60 | lead1 -= 4096 61 | if lead2 > 2047: 62 | lead2 -= 4096 63 | ecg_data_lead1.append(lead1) 64 | ecg_data_lead2.append(lead2) 65 | # plot_data(ecg_data_lead2) # 打印导联1数据 66 | return ecg_data_lead1, ecg_data_lead2 67 | 68 | 69 | def read_f16(file_path, file_name): 70 | """ 71 | 读取format 16格式的文件 72 | :param file_path: str, 文件路径 73 | :param file_name: str, 文件名 74 | :return: list, list, 导联1和导联2的数据 75 | """ 76 | ecg_data = [] 77 | with open(os.path.join(file_path, file_name), 'rb') as file: 78 | ecg_data = file.read() 79 | ecg_data_lead1, ecg_data_lead2 = [], [] 80 | for idx in range(0, len(ecg_data), 4): 81 | lead1 = (ecg_data[idx + 1] << 8) + ecg_data[idx] 82 | lead2 = (ecg_data[idx + 3] << 8) + ecg_data[idx + 2] 83 | if lead1 > 2 ** 15 - 1: 84 | lead1 -= 2 ** 16 85 | if lead2 > 2 ** 15 - 1: 86 | lead2 -= 2 ** 16 87 | ecg_data_lead1.append(lead1) 88 | ecg_data_lead2.append(lead2) 89 | # plot_data(ecg_data_lead1) # 绘制导联1的心电图 90 | # plot_data(ecg_data_lead2) # 绘制导联2的心电图 91 | return ecg_data_lead1, ecg_data_lead2 92 | 93 | 94 | def read_atr(atr_path, atr_name): 95 | """ 96 | 读取注释文件 97 | :param atr_path: str, 文件路径 98 | :param atr_name: str, 文件名 99 | :return: list, list, 注释, 时间间隔 100 | annotation的相关信息参见:https://www.physionet.org/physiotools/wag/annot-5.htm 101 | """ 102 | data = [] 103 | with open(os.path.join(atr_path, atr_name), 'rb') as file: 104 | data = file.read() 105 | atr_time, annot = [], [] 106 | atr_info, atr_info1, atr_info2 = [], [], [] # 存储两行按照字节读取的信息 107 | for idx, v in enumerate(data): # 数据按照[[1,3,5,...],[2,4,6,...]]进行存储 108 | if idx % 2 == 0: 109 | atr_info1.append(v) 110 | else: 111 | atr_info2.append(v) 112 | atr_info.append(atr_info1) # 存到一个列表中 113 | atr_info.append(atr_info2) 114 | idx = 0 115 | while idx < len(atr_info[0]): # 根据MIT-BIH format来解析数据 116 | annoth = atr_info[1][idx] >> 2 117 | if annoth == 59: 118 | annot.append(atr_info[1][idx + 3] >> 2) 119 | atr_time.append(atr_info[0][idx + 2] 120 | + (atr_info[1][idx + 2] << 8) 121 | + (atr_info[0][idx + 1] << 16) 122 | + (atr_info[1][idx + 1] << 24)) 123 | idx += 3 124 | elif annoth == 60: 125 | pass 126 | elif annoth == 61: 127 | pass 128 | elif annoth == 62: 129 | pass 130 | elif annoth == 63: 131 | hilfe = ((atr_info[1][idx] & 3) << 8) + atr_info[0][idx] 132 | hilfe = hilfe + hilfe % 2 133 | idx += hilfe // 2 134 | else: 135 | atr_time.append(((atr_info[1][idx] & 0x3) << 8) + atr_info[0][idx]) # 低10位为时间间隔 136 | annot.append(atr_info[1][idx] >> 2) # 高6位为注释 137 | idx += 1 138 | return annot, atr_time 139 | 140 | def read_qrs(qrs_path, qrs_name): 141 | """ 142 | 读取.qrs文件,比如PAF数据库中,xxx.qrs文件 143 | :param qrs_path: str, 文件路径 144 | :param qrs_name: str, 文件名 145 | :return: 146 | """ 147 | data = [] 148 | with open(os.path.join(qrs_path, qrs_name), 'rb') as file: 149 | data = file.read() 150 | print(data[:8]) 151 | 152 | 153 | def plot_data(data): 154 | plt.figure(figsize=(12, 4)) 155 | plt.plot(data) 156 | plt.show() 157 | 158 | 159 | def plot_ecg_and_annot(data, annot, atr_time): 160 | """ 161 | 绘制心电信号和注释 162 | :param data: list, 心电信号 163 | :param annot: list, 注释 164 | :param atr_time: list, 注释和上一个注释之间的间隔 165 | :return: 无 166 | """ 167 | plt.figure(figsize=(12, 4)) 168 | plt.plot(data) 169 | for idx, v in enumerate(atr_time): 170 | if idx == 0: 171 | continue 172 | atr_time[idx] = atr_time[idx - 1] + atr_time[idx] 173 | r_point = [data[v] for v in atr_time] 174 | plt.plot(atr_time, r_point, 'r*') 175 | plt.show() 176 | 177 | 178 | if __name__ == '__main__': 179 | path = './paf' 180 | dat_name = 'n01c.dat' 181 | hea_name = 'n01c.hea' 182 | atr_name = 'n01c.qrs' 183 | atr_info = read_hea(path, hea_name) 184 | ecg_data1, ecg_data2 = read_f16(path, dat_name) 185 | # 第一值校验 186 | if ecg_data1[0] != atr_info['first_value'][0] or ecg_data2[0] != atr_info['first_value'][1]: 187 | raise Exception('inconsistency in the first bit values') 188 | # # 减0值后除以增益,转换成mV单位 189 | ecg_data1 = [(v - atr_info['zero_value'][0]) / atr_info['gain'][0] for v in ecg_data1] 190 | ecg_data2 = [(v - atr_info['zero_value'][1]) / atr_info['gain'][1] for v in ecg_data2] 191 | # annot, atr_time = read_qrs(path, atr_name) 192 | annot, atr_time = read_atr(path, atr_name) 193 | plot_data(ecg_data1) 194 | # plot_ecg_and_annot(ecg_data1[5:], annot, atr_time) # 打印ecg图和R波位置 195 | 196 | 197 | # if __name__ == '__main__': 198 | # path = './mit-bih-arrhythmia' 199 | # dat_name = '100.dat' 200 | # hea_name = '100.hea' 201 | # atr_name = '100.atr' 202 | # atr_info = read_hea(path, hea_name) 203 | # ecg_data1, ecg_data2 = read_f212(path, dat_name) 204 | # # 第一值校验 205 | # if ecg_data1[0] != atr_info['first_value'][0] or ecg_data2[1] != atr_info['first_value'][1]: 206 | # raise Exception('inconsistency in the first bit values') 207 | # # 减0值后除以增益,转换成mV单位 208 | # ecg_data1 = [(v - atr_info['zero_value'][0]) / atr_info['gain'][0] for v in ecg_data1] 209 | # ecg_data2 = [(v - atr_info['zero_value'][1]) / atr_info['gain'][1] for v in ecg_data2] 210 | # annot, atr_time = read_atr(path, atr_name) 211 | # print(annot[:15]) 212 | # plot_ecg_and_annot(ecg_data1, annot, atr_time) 213 | -------------------------------------------------------------------------------- /data/wfdb_read.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Project : 3 | @File :wfdb_read.py 4 | @Author :DFQX 5 | @Date :2022/9/17 16:06 6 | @Description: 使用wfdb进行读取ecg信号 7 | """ 8 | import wfdb 9 | # './paf/n01'是下载来的PAF数据库的文件, channels是选择的导联 10 | record = wfdb.rdrecord('./aha/0201') 11 | annotation = wfdb.rdann('./aha/0201', 'atr') 12 | wfdb.plot_wfdb(record=record, annotation=annotation, title='PAF n01', time_units='seconds') 13 | 14 | -------------------------------------------------------------------------------- /docs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | ''' 4 | @Project :ecg-algorithm 5 | @File :__init__.py.py 6 | @Author :dell 7 | @Date :2022/8/10 17:57 8 | @Description: 9 | ''' 10 | -------------------------------------------------------------------------------- /ecg/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/ecg/__init__.py -------------------------------------------------------------------------------- /ecg/basic_alg/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | ''' 4 | @Project :ecg-algorithm 5 | @File :__init__.py.py 6 | @Author :dell 7 | @Date :2022/8/10 17:46 8 | @Description: 9 | ''' 10 | -------------------------------------------------------------------------------- /ecg/basic_alg/delay_coordinate_mapping.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Project : 3 | @File :delay_coordinate_mapping.py 4 | @Author :DFQX 5 | @Date :2022/11/15 22:45 6 | @Description: 使用delay-coordinate mapping算法 7 | 算法来源:A Real Time QRS Detection Using Delay-Coordinate Mapping for the 8 | Microcontroller Implementation 9 | """ 10 | 11 | import utils.ecg_display as dp 12 | import utils.signal_process as sig 13 | import numpy as np 14 | import data.read_data as rd 15 | 16 | 17 | def inc_func(i, max_val): 18 | """ 19 | 自增函数 20 | """ 21 | return i + 1 if i + 1 < max_val else 0 22 | 23 | 24 | def dec_func(i, max_val): 25 | """ 26 | 自减函数 27 | """ 28 | return i - 1 if i - 1 >= 0 else max_val - 1 29 | 30 | 31 | def det_3(array): 32 | """ 33 | 计算3阶矩阵的行列式 34 | :param array: list(list), 方阵 35 | :return: 结果值 36 | """ 37 | if len(array[0]) != 3 and 3 != len(array): 38 | raise '矩阵应该为3阶矩阵!' 39 | sum_arr, size = 0, len(array) 40 | for i in range(size): 41 | integral1, integral2 = 1.0, 1.0 42 | ai, aj, az = 0, i, i 43 | for j in range(size): 44 | integral1 *= array[ai][aj] 45 | integral2 *= array[ai][az] 46 | ai = inc_func(ai, size) # 等价于 i + 1 if i + 1 < size else 0 47 | aj = inc_func(aj, size) 48 | az = dec_func(az, size) # 等价于 i -1 if i-1 >= 0 else size-1 49 | sum_arr += integral1 - integral2 50 | return sum_arr 51 | 52 | 53 | def det_2(array): 54 | """ 55 | 2阶矩阵行列式 56 | """ 57 | if len(array) != 2 and len(array[0]) != 2: 58 | raise '矩阵应该为2阶矩阵!' 59 | return array[0][0] * array[1][1] - array[0][1] * array[1][0] 60 | 61 | 62 | def find_max(arr): 63 | """ 64 | 一维列表中最大值和下标 65 | :param arr: 一维数组 66 | :return: 最大值, 最大值下标 67 | """ 68 | m_v, m_i = 0, 0 69 | for idx, val in enumerate(arr): 70 | if val > m_v: 71 | m_v = val 72 | m_i = idx 73 | return m_v, m_i 74 | 75 | 76 | def diff(data): 77 | result = [] 78 | for idx in range(1, len(data)): 79 | result.append(data[idx] - data[idx - 1]) 80 | return result 81 | 82 | 83 | def delay_cor(ecg_data, delay_ms=9, fs=250): 84 | """ 85 | 延迟坐标算法 86 | :param delay_ms: int, 延时时长 87 | :param ecg_data: list, 心电数据 88 | :param fs: int, 采样率 89 | :return: 90 | """ 91 | delay_n = int(delay_ms * fs / 1000 + 0.5) # 延迟坐标点 92 | 93 | # ----------------滤波------------------- 94 | y_n = sig.bandpass_filter(ecg_data, fs, 1, 20) 95 | # -------------延迟坐标存储---------------- 96 | x, y = [], [] 97 | # -------------行列式存储---------------- 98 | d = [] # d(n)矩阵 99 | v = [] # v(n)行列式值 100 | v_count = 0 # v计数 101 | # -----------阈值初始化(2秒)--------------- 102 | thrs1, thrs2 = 0, 0 # eta_1 eta_2 103 | thrs1_arr, thrs2_arr = [], [] 104 | # ------------llv和rlv存储---------------- 105 | llv, rlv = [], [] 106 | llv_q, rlv_q = [0 for val in range(5)], [0 for val in range(5)] 107 | q_i = 0 # llv_q 和 rlv_q的下标 108 | thrs_min_regions = [] 109 | llv_sum, rlv_sum = [], [] 110 | # --------------qrs波坐标---------------- 111 | qrs, qrs_i = [], [] # r波波峰, r波坐标 112 | new_qrs = False 113 | refra_count, refra_tag = 1, False # 不应期 114 | 115 | for i in range(delay_n, len(y_n)): 116 | x.append(y_n[i - delay_n]) 117 | y.append(y_n[i]) 118 | 119 | 120 | # -----------计算d(n)----------- 121 | idx = i - delay_n 122 | if idx >= 1: # 最少有xi(0),xi(1) 123 | d.append([y[idx] - y[idx - 1], x[idx] - x[idx - 1]]) 124 | if idx >= 2: # len(d)>=2 125 | v.append(det_2(d[-2:])) # np.linalg.det(d) 126 | v_count += 1 127 | 128 | # ------计算llv(n)和rlv(n)------- 129 | if idx >= 4: # 最少需要3个v值 130 | llv.append(v[-2] + v[-3]) # llv和rlv会比v长度少2 131 | rlv.append(v[-2] + v[-1]) 132 | 133 | # ---------阈值判断------------- 134 | if idx >= 6: 135 | llv_sum.append((llv[-1] + llv[-2] + llv[-3])/5) # llv_sum和rlv_sum会比v长度少4 136 | rlv_sum.append((rlv[-1] + rlv[-2] + rlv[-3])/5) 137 | # ------使用两秒来进行初始化阈值---------- 138 | if idx // fs < 2: 139 | thrs1 = 0.25 * max(llv_sum) 140 | thrs2 = thrs1 141 | if idx // fs < 1: # 前一秒不进行阈值判断 142 | continue 143 | 144 | if not new_qrs and not refra_tag and len(llv_sum) > 2: 145 | if abs(llv_sum[-1]) >= thrs1 and abs(rlv_sum[-1]) >= thrs1: # case i 146 | qrs.append(v[-1]) 147 | qrs_i.append(v_count - 1) 148 | new_qrs = True 149 | thrs1_arr.append(thrs1) 150 | thrs2_arr.append(thrs2) 151 | elif abs(llv_sum[-1]) >= thrs2 or abs(rlv_sum[-1]) >= thrs2: # case ii 152 | qrs.append(v[-1]) 153 | qrs_i.append(v_count - 1) 154 | new_qrs = True 155 | thrs1_arr.append(thrs1) 156 | thrs2_arr.append(thrs2) 157 | else: 158 | new_qrs = False 159 | 160 | # --------------回找------------------ 161 | if idx >= 6 and len(qrs_i) > 1 and not refra_tag: 162 | rr_i = qrs_i if len(qrs_i) <= 9 else qrs_i[-9:] # 计算rr间期均值,一般8个rr间期的 163 | rr = diff(rr_i) 164 | rr_avg = sum(rr) / len(rr) 165 | if v_count - 1 - qrs_i[-1] > rr_avg * 1.6: # 与上一次r波波峰位置大于最近的rr间期的1.5倍 166 | refra = int(0.2 * fs + 0.5) # 不应期 167 | start = qrs_i[-1] + refra 168 | l_v, l_i = find_max(llv_sum[start:-1]) 169 | r_v = max(rlv_sum[start:-1]) 170 | r_i = start + l_i 171 | if l_v > thrs1 * 0.8 and r_v > thrs1 * 0.8: 172 | qrs.append(v[r_i]) 173 | qrs_i.append(r_i) 174 | new_qrs = True 175 | thrs1_arr.append(thrs1 * 0.8) 176 | thrs2_arr.append(thrs2 * 0.5) 177 | elif l_v > thrs2 * 0.5 or r_v > thrs2 * 0.5: 178 | qrs.append(v[r_i]) 179 | qrs_i.append(r_i) 180 | new_qrs = True 181 | thrs1_arr.append(thrs1 * 0.8) 182 | thrs2_arr.append(thrs2 * 0.5) 183 | else: 184 | new_qrs = False 185 | 186 | # --------------更新阈值------------------ 187 | if new_qrs and not refra_tag: 188 | llv_q[q_i] = llv_sum[-1] 189 | rlv_q[q_i] = rlv_sum[-1] 190 | q_i = inc_func(q_i, len(llv_q)) 191 | thrs_min = min(min(llv_q), min(rlv_q)) 192 | thrs_max = max(max(llv_q), max(rlv_q)) 193 | thrs_min_region = (thrs_max + thrs_min) * 0.75 194 | thrs_min_regions.append(thrs_min_region) 195 | # 两个阈值最少需要两个或者三个thrs_min_region值 196 | if len(thrs_min_regions) >= 5: 197 | thrs1 = sum(thrs_min_regions[-5:]) / 5 198 | thrs2 = sum(thrs_min_regions[-5:]) / 5 199 | else: 200 | thrs1 = sum(thrs_min_regions) / (len(thrs_min_regions)) 201 | thrs2 = thrs1 202 | refra_tag = True 203 | new_qrs = 0 204 | 205 | # -----------------不应期限制------------- 206 | if refra_tag: 207 | refra_count += 1 208 | if refra_count >= 0.2 * fs: # 200ms不应期 209 | refra_tag = False 210 | refra_count = 1 211 | return v, llv_sum, rlv_sum, qrs, qrs_i, thrs1_arr, thrs2_arr 212 | 213 | 214 | def d_cor(ecg_data, delay=9, fs=250): 215 | """ 216 | 坐标延迟,用作绘图研究 217 | """ 218 | x_data, y_data = [], [] 219 | d_point = int(delay * fs / 1000 + 0.5) 220 | y_data = ecg_data[0:-d_point] 221 | x_data = ecg_data[d_point:] 222 | dp.plot_ecg_delay(x_data, y_data) 223 | return x_data, y_data 224 | 225 | 226 | def d_cor_3d(ecg_data, delay=9, fs=250): 227 | """ 228 | 坐标延迟,用作绘图研究 229 | """ 230 | x_data, y_data, z_data = [], [], [] 231 | d_point = int(delay * fs / 1000 + 0.5) 232 | z_data = ecg_data[0:-d_point * 2] 233 | y_data = ecg_data[d_point:-d_point] 234 | x_data = ecg_data[d_point * 2:] 235 | dp.plot_ecg_delay_3d(x_data, y_data, z_data) 236 | return x_data, y_data, z_data 237 | 238 | 239 | def v_cal(ecg_data, delay, fs): 240 | delay_n = int(delay * fs / 1000 + 0.5) # 延迟坐标点 241 | x, y, d, v = [], [], [], [] 242 | for i in range(delay_n, len(ecg_data)): 243 | x.append(ecg_data[i - delay_n]) 244 | y.append(ecg_data[i]) 245 | idx = i - delay_n 246 | if idx >= 1: # 最少有xi(0),xi(1) 247 | d.append([y[idx] - y[idx - 1], x[idx] - x[idx - 1]]) 248 | if idx >= 2: # len(d)>=2 249 | v.append(det_2(d[-2:])) # np.linalg.det(d) 250 | return v 251 | 252 | 253 | def left_padding(data, num=4): 254 | """ 255 | 左填充0 256 | :param data: 数据 257 | :param num: 默认4 258 | :return: list 259 | """ 260 | return [0 for v in range(2)] + data 261 | 262 | 263 | def batch_v_plot(ecg_data, delay_ms_arr, fs, high=2): 264 | v_all = [] 265 | for delay in delay_ms_arr: 266 | v_all.append(v_cal(ecg_data, delay, fs)) 267 | from matplotlib import pyplot as plt 268 | plt.figure(figsize=(10, len(v_all) * high)) 269 | plt.subplot(len(v_all) + 1, 1, 1) 270 | plt.plot(ecg_data) 271 | plt.title('(a) Bandpass Filter') 272 | for i in range(len(v_all)): 273 | plt.subplot(len(v_all) + 1, 1, i + 2) 274 | plt.tight_layout() 275 | plt.subplots_adjust(hspace=1, wspace=0.2) 276 | plt.plot(v_all[i]) 277 | plt.title('({}) {}ms delay'.format(chr(ord('a') + 1 + i), delay_ms_arr[i])) 278 | plt.show() 279 | 280 | 281 | if __name__ == '__main__': 282 | # path = '../../data/mit-bih-arrhythmia' 283 | # dat_name = '100.dat' 284 | # fs = 360 285 | path = '../../data/mit-bih-arrhythmia' 286 | dat_name = '114.dat' 287 | fs = 360 288 | # ecg_data1, ecg_data2 = rd.read_f16(path, dat_name) 289 | ecg_data1, ecg_data2 = rd.read_f212(path, dat_name) 290 | ecg = ecg_data1[290000:310000] 291 | ecg1 = sig.bandpass_filter(ecg, fs, 1, 20) # 单独滤波 292 | # d_cor(ecg, 50, fs) # 绘制坐标延迟 293 | # d_cor_3d(ecg, 9, fs) # 绘制3D坐标 294 | 295 | # batch_v_plot(ecg1, [8, 16, 25, 33, 42, 50], fs) # 打印多个V(n)向量图 296 | 297 | v = v_cal(ecg1, 8, fs) # 单独计算V(n) 298 | 299 | # dp.plot_simple_comp(ecg, ecg1, 'Row', 'Bandpass Filter') # 滤波前后数据对比 300 | # dp.plot_simple_comp(ecg, v, 'Row ECG', 'V(n)') # 原始数据和V(n)的对比 301 | # dp.plot_peak_dot(v, qrs_i, qrs) 302 | 303 | v, llv_sum, rlv_sum, qrs, qrs_i, thrs1_arr, thrs2_arr = delay_cor(ecg, 8, fs) 304 | dp.plot_peak_dot_llv_rlv(v, qrs_i, qrs, left_padding(llv_sum), left_padding(rlv_sum), thrs1_arr, thrs2_arr) 305 | # dp.plot_peak_dot_llv_rlv(v, qrs_i, qrs, llv_sum, rlv_sum, thrs1_arr, thrs2_arr) 306 | # dp.plot_peak_dot(v, qrs_i, qrs) 307 | dp.subplot_peaks(ecg1, v, qrs_i, qrs, 'ECG', 'R peaks') 308 | -------------------------------------------------------------------------------- /ecg/basic_alg/pan_tompkin.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Project : 3 | @File :pan_tompkin.py 4 | @Author :DFQX 5 | @Date :2022/10/11 22:34 6 | @Description: 7 | """ 8 | 9 | # !/usr/bin/env python 10 | # -*- coding: UTF-8 -*- 11 | """ 12 | @Project :BioSigAlg 13 | @File :pan-tompinks.py 14 | @Author :dell 15 | @Date :2022/10/12 14:03 16 | @Description: 经典的pan-tompinks算法 17 | """ 18 | from scipy import signal 19 | import data.read_data as rd 20 | from utils import ecg_display as display 21 | 22 | 23 | def high_pass_filter(data): 24 | pass 25 | 26 | 27 | def low_pass_filter(data): 28 | pass 29 | 30 | 31 | def bandpass_filter(data, fs, low=5, high=15): 32 | """ 33 | 带通滤波 34 | :param data: list, 输入信号数据 35 | :param fs: int, 采样率 36 | :param low: int, 截止频率1 37 | :param high: int, 截止频率2 38 | :return: list, 滤波后信号 39 | """ 40 | low = 2 * low / fs 41 | high = 2 * high / fs 42 | b, a = signal.butter(3, [low, high], 'bandpass') 43 | filter = signal.filtfilt(b, a, data) # data为要过滤的信号 44 | return filter 45 | 46 | 47 | def inc_func(i, max_val): 48 | """ 49 | 自增函数 50 | """ 51 | return i + 1 if i + 1 < max_val else 0 52 | 53 | 54 | def dec_func(i, max_val): 55 | """ 56 | 自减函数 57 | """ 58 | return i - 1 if i - 1 >= 0 else max_val - 1 59 | 60 | 61 | def conv(data, coefficient): 62 | """ 63 | 卷积求和运算 64 | :param data: 一维数据 65 | :param coefficient: 系数 66 | :return: 67 | """ 68 | temp, result = [data[0] for i in coefficient], [] 69 | idx = 0 70 | for i in range(len(data)): 71 | temp[idx] = data[i] 72 | sum = 0 73 | for val in coefficient: 74 | sum += temp[idx] * val # 卷积求和 75 | idx = dec_func(idx, len(coefficient)) 76 | idx = inc_func(idx, len(coefficient)) 77 | result.append(sum) 78 | return result 79 | 80 | 81 | def diff(data): 82 | result = [0] 83 | for idx, val in enumerate(data[1:]): 84 | result.append(data[idx] - data[idx - 1]) 85 | return result 86 | 87 | 88 | def derivative(data): 89 | """ 90 | 求导函数H(z) = (1/8T)(-z^(-2) - 2z^(-1) + 2z + z^(2)) 91 | :param data: 数据 92 | :return: list 93 | """ 94 | coef = [-1, -2, 0, 2, 1] 95 | der_data = conv(data, coef) 96 | return der_data 97 | 98 | 99 | def mean(data): 100 | if len(data) == 0: 101 | raise '被除数为0' 102 | return sum(data) / len(data) 103 | 104 | 105 | def square(data): 106 | """ 107 | 求平方 108 | :param data: 数据 109 | :return: list 110 | """ 111 | res = [val ** 2 for val in data] 112 | return res 113 | 114 | 115 | def moving_window_average(data, fs=250): 116 | """ 117 | 移动窗口积分均值 118 | y(nT) = (1/N)[x(nT-(N - 1)T)+ x(nT - (N - 2)T)+...+x(nT)] 119 | :param fs: 采样频率 120 | :param data: 数据 121 | :return: list 122 | """ 123 | win_width = int(0.15 * fs + 0.5) # 150ms的窗宽 124 | temp = conv(data, [1 for i in range(win_width)]) 125 | res = [val / win_width for val in temp] 126 | return res 127 | 128 | 129 | def findpeaks(data, min_distance, fs=250): 130 | """ 131 | 寻找峰值位置peak, 返回其峰值和位置 132 | :param fs: 采样率 133 | :param data: 数据 134 | :param min_distance: 两个峰值之间最小的距离, 0.1*fs 135 | :return: list, list 136 | """ 137 | min_distance = int(0.1 * fs + 0.5) # fs 为250 138 | inc_cout = 0 139 | last_val = 0 140 | temp_val = 0 141 | temp_idx = 0 142 | peaks, locs = [], [] 143 | for idx, val in enumerate(data): 144 | if inc_cout > 0: # 如果已经开始计数了,便计数,防止一致是下降的曲线也进行统计 145 | inc_cout += 1 146 | if val > last_val and val > temp_val: 147 | temp_val = val 148 | temp_idx = idx 149 | inc_cout = 1 150 | elif val < (temp_val // 2) or inc_cout > min_distance: # 下降到一半或者大于最小距离 151 | peaks.append(temp_val) 152 | locs.append(temp_idx) 153 | temp_val = 0 154 | inc_cout = 0 155 | last_val = val 156 | return peaks, locs 157 | 158 | 159 | def find_max(vector): 160 | """ 161 | 找到一个数据中的最大值以及位置 162 | :param vector: 数据 163 | :return: 164 | """ 165 | if len(vector) == 0: 166 | print('数据长度为0') 167 | return 0, 0 168 | max_v, max_i = vector[0], 0 169 | for idx, val in enumerate(vector): 170 | if val > max_v: 171 | max_v = val 172 | max_i = idx 173 | return max_v, max_i 174 | 175 | 176 | def judge_rule(ecg_filter, ecg_win, peaks, locs, fs=250): 177 | """ 178 | 自适应阈值处理 179 | :param ecg_filter: list, 带通滤波后的数据 180 | :param ecg_win: list, 积分窗后的数据 181 | :param peaks: list, 通过积分窗后找到的峰值 182 | :param locs: list, peaks所对应的下标 183 | :param fs: int, 采样率 184 | :return: list, list, list, list 185 | """ 186 | # 存错积分窗和带通滤波数据的信号 187 | qrs_amp_win = [] # 积分窗的qrs峰值 188 | qrs_idx_win = [] # 积分窗的qrs下标 189 | qrs_amp_flt = [] # bandpass filter的qrs峰值 190 | qrs_idx_flt = [] # bandpass filter的qrs下标 191 | # 信号和噪声 192 | thrs_win1, thrs_win2 = [], [] # 积分窗的高、低阈值 193 | thrs_flt1, thrs_flt2 = [], [] # 滤波的高、低阈值 194 | # 积分窗数据起始两秒的初始化,包括信号阈值和噪声阈值 195 | PEAKI, PEAKI_IDX = 0, 0 196 | THRESHOLD_I1 = 0.25 * max(ecg_win[:2 * fs]) 197 | THRESHOLD_I2 = 0.5 * mean(ecg_win[:2 * fs]) 198 | SPKI = THRESHOLD_I1 199 | NPKI = THRESHOLD_I2 200 | # 带通滤波数据起始两秒的初始化,包括信号阈值和噪声阈值 201 | PEAKF, PEAKF_IDX = 0, 0 202 | THRESHOLD_F1 = 0.25 * max(ecg_filter[:2 * fs]) 203 | THRESHOLD_F2 = 0.5 * mean(ecg_filter[:2 * fs]) 204 | SPKF = THRESHOLD_F1 205 | NPKF = THRESHOLD_F2 206 | # RR间期和均值 207 | RR_AVERAGE1 = 0 208 | RR_AVERAGE2 = 0 209 | rr_recent_limit = [0 for i in range(8)] # 在限制范围内的RR间期 210 | rr_limit_idx, rr_limit_count = 0, 0 # 指针, 计数 211 | # 标志 212 | is_t_wave = False 213 | is_first_win = False 214 | is_new_qrs = False 215 | # 阈值自适应以及检测规则 216 | for i in range(len(peaks)): 217 | PEAKI, PEAKI_IDX = peaks[i], locs[i] # 按照公式进行命名 218 | # 带通滤波数据中定位峰值, 在与peak有0.15秒的延迟范围内进行定位 219 | if PEAKI_IDX - int(0.15 * fs + 0.5) >= 1 and PEAKI_IDX <= len(ecg_filter): 220 | # 通过积分窗找到的peak,和原始数据之间的peak有延迟 221 | PEAKF, PEAKF_IDX = find_max(ecg_filter[PEAKI_IDX - int(0.15 * fs + 0.5):PEAKI_IDX]) 222 | else: 223 | if i == 0: 224 | PEAKF, PEAKF_IDX = find_max(ecg_filter[:PEAKI_IDX]) 225 | is_first_win = 1 226 | elif PEAKI_IDX >= len(ecg_filter): 227 | PEAKF, PEAKF_IDX = find_max(ecg_filter[PEAKI_IDX - int(0.15 * fs + 0.5):]) 228 | 229 | # 更新心率 230 | if len(qrs_idx_win) >= 9: 231 | rr_interval = diff(qrs_idx_win[-9:]) 232 | RR_AVERAGE1 = mean(rr_interval) 233 | latest_rr = qrs_idx_win[-1] - qrs_idx_win[-2] 234 | if rr_limit_count == 0: 235 | RR_AVERAGE2 = RR_AVERAGE1 236 | elif rr_limit_count < 8: 237 | RR_AVERAGE2 = mean(rr_recent_limit[:rr_limit_count]) 238 | else: 239 | RR_AVERAGE2 = mean(rr_recent_limit) 240 | # 异常RR,更新阈值规则 241 | if latest_rr <= 0.92 * RR_AVERAGE2 or latest_rr >= 1.16 * RR_AVERAGE2: 242 | THRESHOLD_I1 *= 0.5 243 | THRESHOLD_F1 *= 0.5 244 | elif is_new_qrs: # 正常RR, 且有新QRS波更新 245 | rr_recent_limit[rr_limit_idx] = latest_rr 246 | rr_limit_idx = inc_func(rr_limit_idx, 8) 247 | rr_limit_count += 1 248 | is_new_qrs = False 249 | 250 | # 回找策略 251 | if RR_AVERAGE2 != 0: # 8秒后才会有值,所以前8秒不会回找,当然这里是可以优化的 252 | if locs[i] - qrs_idx_win[-1] >= int(1.66 * RR_AVERAGE2 + 0.5): # 回找条件 253 | PEAKI, PEAKI_IDX = find_max( 254 | ecg_win[qrs_idx_win[-1] + int(0.2 * fs + 0.5):locs[i] - int(0.2 * fs + 0.5)]) 255 | # 将相对位置转换为绝对位置 256 | PEAKI_IDX = qrs_idx_win[-1] + int(0.2 * fs + 0.5) + PEAKI_IDX - 1 257 | if PEAKI > THRESHOLD_I2: # 如果比低阈值高,则认为pks_temp为QRS 258 | qrs_amp_win.append(PEAKI) 259 | qrs_idx_win.append(PEAKI_IDX) 260 | SPKI = 0.25 * PEAKI + 0.75 * SPKI # 积分窗回找时SPKI的更新 261 | is_new_qrs = True 262 | # 定位带通滤波数据的QRS 263 | if PEAKI_IDX <= len(ecg_filter): 264 | find_range = ecg_filter[PEAKI_IDX - int(0.15 * fs + 0.5):PEAKI_IDX] 265 | else: 266 | find_range = ecg_filter[PEAKI_IDX - int(0.15 * fs + 0.5):] 267 | PEAKF_B, PEAKF_B_IDX = find_max(find_range) # search back, 滤波数据 268 | # 是否满足带通滤波数据的阈值限制 269 | if PEAKF_B > THRESHOLD_F2: 270 | qrs_amp_flt.append(PEAKF_B) # 保存滤数据中检出的R波 271 | # 保存滤波数据中检出R波的下标 272 | qrs_idx_flt.append(PEAKI_IDX - int(0.15 * fs + 0.5) + PEAKF_B_IDX) 273 | SPKF = 0.25 * PEAKF_B + 0.75 * SPKF # 滤波数据回找时SPKF的更新 274 | 275 | # 寻找噪声和QRS峰值 276 | if peaks[i] > THRESHOLD_I1: # 满足高阈值 277 | if len(qrs_idx_win) >= 3: # 最少3个R波,两个RR间期才能对比 278 | # 200ms到360ms范围内 279 | if int(0.20 * fs + 0.5) < locs[i] - qrs_idx_win[-1] <= int(0.36 * fs + 0.5): 280 | cur_slope = mean(diff(ecg_win[locs[i] - int(0.075 * fs + 0.5):locs[i]])) 281 | pre_slope = mean( 282 | diff(ecg_win[qrs_idx_win[-1] - int(0.075 * fs + 0.5):qrs_idx_win[-1]])) 283 | if abs(cur_slope) < 0.5 * abs(pre_slope): # 检出T波的条件 284 | PEAKI, PEAKI_IDX = peaks[i], locs[i] # 防止被改,重新赋值 285 | is_t_wave = True 286 | # NPKI和NPKF规则更新 287 | NPKF = 0.125 * PEAKF + 0.875 * NPKF 288 | NPKI = 0.125 * PEAKI + 0.875 * NPKI 289 | else: 290 | is_t_wave = False 291 | 292 | # 正常情况下以及不是T波的时候 293 | if not is_t_wave: 294 | # 存储积分窗的QRS波峰和位置 295 | PEAKI, PEAKI_IDX = peaks[i], locs[i] # 防止被改,重新赋值 296 | qrs_amp_win.append(PEAKI) 297 | qrs_idx_win.append(PEAKI_IDX) 298 | is_new_qrs = True 299 | SPKI = 0.125 * PEAKI + 0.875 * SPKI 300 | # 带通滤波检测阈值 301 | if PEAKF >= THRESHOLD_F2: 302 | # 存储滤波后的QRS波峰和位置 303 | if is_first_win: 304 | qrs_idx_flt.append(PEAKF_IDX) 305 | else: 306 | qrs_idx_flt.append(PEAKI_IDX - int(0.15 * fs + 0.5) + PEAKF_IDX) 307 | qrs_amp_flt.append(PEAKF) 308 | SPKF = 0.125 * PEAKF + 0.875 * SPKF 309 | 310 | elif THRESHOLD_I2 <= PEAKI < THRESHOLD_I1: # 在高阈值和低阈值之间 311 | NPKF = 0.125 * PEAKF + 0.875 * SPKF # 滤波数据中, 噪声peak 312 | NPKI = 0.125 * PEAKI + 0.875 * SPKI # 积分窗数据中, 噪声peak 313 | 314 | elif PEAKI < THRESHOLD_I2: # 小于低阈值 315 | NPKF = 0.125 * PEAKF + 0.875 * SPKF # 滤波数据中,噪声peak 316 | NPKI = 0.125 * PEAKI + 0.875 * SPKI # 积分窗数据中, 噪声peak 317 | 318 | # 自适应信号和噪声阈值,即高、低阈值 319 | if NPKI != 0 or SPKI != 0: 320 | THRESHOLD_I1 = NPKI + 0.25 * abs(SPKI - NPKI) 321 | THRESHOLD_I2 = 0.5 * THRESHOLD_I1 322 | if NPKF != 0 or SPKF != 0: 323 | THRESHOLD_F1 = NPKF + 0.25 * abs(SPKF - NPKF) 324 | THRESHOLD_F2 = 0.5 * THRESHOLD_F1 325 | 326 | # 记录信号阈值(积分窗) 327 | thrs_win1.append(THRESHOLD_I1) 328 | thrs_win2.append(THRESHOLD_I2) 329 | # 记录信号阈值(滤波后) 330 | thrs_flt1.append(THRESHOLD_F1) 331 | thrs_flt2.append(THRESHOLD_F2) 332 | # 重置参数 333 | is_t_wave = False 334 | is_first_win = 0 335 | return qrs_amp_win, qrs_idx_win, qrs_amp_flt, qrs_idx_flt, thrs_win1, thrs_win2, thrs_flt1\ 336 | , thrs_flt2 337 | 338 | 339 | if __name__ == '__main__': 340 | path = '../../data/mit-bih-arrhythmia' 341 | dat_name = '105.dat' 342 | # fs = 250 343 | fs = 360 344 | # ecg_data1, ecg_data2 = rd.read_f16(path, dat_name) 345 | ecg_data1, ecg_data2 = rd.read_f212(path, dat_name) 346 | ecg = ecg_data1[:] # 原始信号 347 | ecg1 = bandpass_filter(ecg, fs, 5, 15) # 滤波 348 | ecg2 = derivative(ecg1) # 差分 349 | ecg3 = square(ecg2) # 平方 350 | ecg4 = moving_window_average(ecg3) # 积分窗 351 | peaks, locs = findpeaks(ecg4, int(0.2 * fs + 0.5)) # 基准peak 352 | qrs_amp_win, qrs_idx_win, qrs_amp_flt, qrs_idx_flt, thrs_win1, thrs_win2, thrs_flt1, thrs_flt2 \ 353 | = judge_rule(ecg1, ecg4, peaks, locs, fs) 354 | # display.plot_simple_comp(ecg, ecg1, 'Raw ECG', 'Filterd ECG') 355 | # display.plot_simple_comp(ecg1, ecg2, 'Filterd ECG', 'Derivative') 356 | # display.plot_simple_comp(ecg2, ecg3, 'Derivative', 'Square') 357 | # display.plot_simple_comp(ecg3, ecg4, 'Square', 'Moving Window Integration') 358 | # display.plot_peaks(ecg4, locs, peaks) 359 | display.plot_peak_sig_and_noise_for_win_and_filter(ecg4, ecg1, qrs_idx_win, qrs_amp_win, 360 | qrs_idx_flt, 361 | qrs_amp_flt, locs, thrs_win1, thrs_win2, 362 | thrs_flt1, thrs_flt2, title1='Moving Window', 363 | title2='Bandpass Filter') 364 | -------------------------------------------------------------------------------- /ecg/deep_learning: -------------------------------------------------------------------------------- 1 | import tensorflow 2 | -------------------------------------------------------------------------------- /img/img1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/img/img1.jpg -------------------------------------------------------------------------------- /img/img2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/img/img2.jpg -------------------------------------------------------------------------------- /img/img3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/img/img3.jpg -------------------------------------------------------------------------------- /img/img4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/img/img4.jpg -------------------------------------------------------------------------------- /img/img5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/img/img5.jpg -------------------------------------------------------------------------------- /img/img6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/img/img6.png -------------------------------------------------------------------------------- /img/img7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/img/img7.png -------------------------------------------------------------------------------- /paper/A Real Time QRS Detection Using Delay-Coordinate Mapping for the Microcontroller Implementation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/paper/A Real Time QRS Detection Using Delay-Coordinate Mapping for the Microcontroller Implementation.pdf -------------------------------------------------------------------------------- /paper/A Wavelet-Based ECG Delineator Evaluation on Standard Databases.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/paper/A Wavelet-Based ECG Delineator Evaluation on Standard Databases.pdf -------------------------------------------------------------------------------- /paper/An Efficient Algorithm for Automatic Peak Detection in Noisy Periodic and Quasi-Periodic Signals.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/paper/An Efficient Algorithm for Automatic Peak Detection in Noisy Periodic and Quasi-Periodic Signals.pdf -------------------------------------------------------------------------------- /paper/Unsupervised Detection and Classification of Motor Unit Action Potentials in Intramuscular Electromyography Signals.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/paper/Unsupervised Detection and Classification of Motor Unit Action Potentials in Intramuscular Electromyography Signals.pdf -------------------------------------------------------------------------------- /paper/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | ''' 4 | @Project :ecg-algorithm 5 | @File :__init__.py.py 6 | @Author :dell 7 | @Date :2022/8/10 17:58 8 | @Description: 9 | ''' 10 | -------------------------------------------------------------------------------- /paper/paper_list.md: -------------------------------------------------------------------------------- 1 | ## R波识别算法 2 | - Pan-Tompkins算法 3 | https://www.robots.ox.ac.uk/~gari/teaching/cdt/A3/readings/ECG/Pan+Tompkins.pdf 4 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | ''' 4 | @Project :ecg-algorithm 5 | @File :__init__.py.py 6 | @Author :dell 7 | @Date :2022/8/10 17:57 8 | @Description: 9 | ''' 10 | -------------------------------------------------------------------------------- /tests/basic_alg_demo/QRS_detected_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | ''' 4 | @Project :ecg-algorithm 5 | @File :QRS_detected_demo.py 6 | @Author :DFQX 7 | @Date :2022/8/10 17:54 8 | @Description: 9 | ''' 10 | 11 | 12 | from ecg.basic_alg import QRS_detected as qrs 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/basic_alg_demo/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | ''' 4 | @Project :ecg-algorithm 5 | @File :__init__.py.py 6 | @Author :DFQX 7 | @Date :2022/8/10 17:53 8 | @Description: 9 | ''' 10 | -------------------------------------------------------------------------------- /utils/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFQX/ecg-algorithm/dc1bf253b9af8dfdfb8c94c88ae330d5acd3f0b7/utils/1.jpg -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | ''' 4 | @Project :ecg-algorithm 5 | @File :__init__.py.py 6 | @Author :dell 7 | @Date :2022/8/10 17:58 8 | @Description: 9 | ''' 10 | -------------------------------------------------------------------------------- /utils/base_maths.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Project : 3 | @File :base_maths.py 4 | @Author :DFQX 5 | @Date :2022/11/16 23:19 6 | @Description: 基础数学方法 7 | """ 8 | -------------------------------------------------------------------------------- /utils/ecg_display.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Project : 3 | @File :ecg_display.py 4 | @Author :DFQX 5 | @Date :2022/9/18 13:09 6 | @Description: 使用matplotlib打印心电图 7 | """ 8 | 9 | from matplotlib import pyplot as plt 10 | import data.read_data as rd 11 | import data.covert as cov 12 | 13 | ''' 14 | 函数:plt_ecg 15 | 描述:用于绘制只有一条导联数据的心电图,并且时长不能太长 16 | 输入:data:心电数据;fs:采样率;gain:增益; path:保存图片路径,默认不保存 17 | 输出:一张心电图,没有保存为文件,如果需要,使用plt.savefig() 18 | ''' 19 | 20 | 21 | def plt_ecg(data, fs=250, gain=1, path=''): # 打印ECG图纸,一个心电图 22 | """ 23 | 用于绘制只有一条导联数据的心电图,并且时长不能太长 24 | :param data: list, 心电数据 25 | :param fs: int, 采样率 26 | :param gain: int, 增益 27 | :param path: str, 默认为空,不用保存,设置路径后保存图片 28 | :return: 无 29 | """ 30 | x = [i / fs for i in range(0, len(data))] # 计算x轴时间 31 | y = [val / gain for val in data] # 根据增益计算幅值 32 | plt.figure(figsize=(len(data) * 25 // (fs * 14), 4)) # 画板大小固定,不要更改 33 | plt.xlabel("Time: s", fontsize=14) 34 | plt.ylabel("Voltage: mV", fontsize=14) 35 | plt.margins(x=0) 36 | ax = plt.gca() 37 | # 心电图纸:横坐标一般小格0.04s,大格0.2s; 纵坐标小格0.1mv,大格0.5mv 38 | ax.xaxis.set_major_locator(plt.MultipleLocator(0.2)) # 设置x轴主刻度 39 | ax.xaxis.set_minor_locator(plt.MultipleLocator(0.04)) # 设置x轴次刻度 40 | ax.yaxis.set_major_locator(plt.MultipleLocator(0.5)) # 设置y轴主刻度 41 | ax.yaxis.set_minor_locator(plt.MultipleLocator(0.1)) # 设置y轴次刻度 42 | # 绘制大格和小格的线条 43 | ax.grid(which='major', axis="both", linewidth=0.75, linestyle='-', color='r') 44 | ax.grid(which='minor', axis="both", linewidth=0.25, linestyle='-', color='r') 45 | plt.ylim([-2.5, 2.5]) # y轴值为-2.5~2.5mV之间 46 | plt.plot(x, y, 'black', linewidth=0.9) # 心电图形绘制 47 | plt.savefig(path) if path.strip() != '' else None 48 | plt.show() 49 | 50 | 51 | def plot_peaks(data, locs, vals, title=''): 52 | """ 53 | 打印波峰位置 54 | :param data: list, 信号数据 55 | :param locs: list, 位置数据 56 | :param vals: list, peak的值 57 | :param title: str, 图形的标题, 默认为空 58 | :return: None 59 | """ 60 | plt.figure(figsize=(13, 3)) 61 | plt.plot(data) 62 | plt.plot(locs, vals, 'r*') 63 | plt.title(title) 64 | plt.show() 65 | 66 | 67 | def subplot_peaks(data1, data2, locs, vals,title1='', title2=''): 68 | """ 69 | 打印信号2波峰位置, 同时将两条数据进行比对 70 | :param title2: str, 图形的标题, 默认为空 71 | :param title1: str, 图形的标题, 默认为空 72 | :param data1: list, 信号1数据 73 | :param data2: list, 信号2数据 74 | :param locs: list, 位置数据 75 | :param vals: list, peak的值 76 | :return: None 77 | """ 78 | plt.figure(figsize=(13, 5)) 79 | plt.subplot(211) 80 | plt.plot(data1) 81 | plt.title(title1) 82 | plt.subplot(212) 83 | plt.plot(data2) 84 | plt.plot(locs, vals, 'r*') 85 | plt.title(title2) 86 | plt.tight_layout() 87 | plt.show() 88 | 89 | 90 | def plot_simple_comp(data1, data2, title1='', title2=''): 91 | """ 92 | 将两个信号进行简单的对比 93 | :param data1: 信号1 94 | :param data2: 信号2 95 | :param title1: 标题1 96 | :param title2: 标题2 97 | :return: None 98 | """ 99 | plt.figure(figsize=(12, 4)) 100 | plt.subplot(211) 101 | plt.plot(data1) 102 | plt.title(title1) 103 | plt.subplot(212) 104 | plt.plot(data2) 105 | plt.title(title2) 106 | plt.tight_layout() 107 | plt.show() 108 | 109 | 110 | def plot_peak_sig_and_noise_for_win_and_filter(data1, data2, x, y, x1, y1, locs1, th1, th2, th3, 111 | th4, title1='', title2=''): 112 | """ 113 | 绘制peak值的点 114 | :param data: 数据 115 | :param x: locs, 坐标位置 116 | :param y: peak值 117 | :return: None 118 | """ 119 | plt.figure(figsize=(12, 6)) 120 | plt.subplot(211) 121 | plt.title(title1) 122 | plt.plot(data1) 123 | plt.plot(x, y, '*r') 124 | plt.plot(locs1, th1) 125 | plt.plot(locs1, th2) 126 | plt.subplot(212) 127 | plt.title(title2) 128 | plt.plot(data2) 129 | plt.plot(x1, y1, 'og') 130 | plt.plot(locs1, th3) 131 | plt.plot(locs1, th4) 132 | plt.tight_layout() 133 | plt.show() 134 | 135 | 136 | def plot_peak_dot(data, x, y): 137 | """ 138 | 绘制peak值的点,以及两个阈值的点 139 | :param data: 数据 140 | :param x: locs, 坐标位置 141 | :param y: peak值 142 | :return: None 143 | """ 144 | plt.figure(figsize=(12, 4)) 145 | plt.plot(data) 146 | plt.plot(x, y, '*r') 147 | plt.show() 148 | 149 | def plot_peak_dot_th1_th2(data, x, y, th1, th2): 150 | """ 151 | 绘制peak值的点,以及两个阈值的点 152 | :param data: 数据 153 | :param x: locs, 坐标位置 154 | :param y: peak值 155 | :return: None 156 | """ 157 | plt.figure(figsize=(12, 4)) 158 | plt.plot(data) 159 | plt.plot(x, y, '*r') 160 | plt.plot(x, th1) 161 | plt.plot(x, th2) 162 | plt.show() 163 | 164 | 165 | def plot_peak_dot_llv_rlv(data, x, y, LLV, RLV, th1, th2): 166 | """ 167 | 绘制peak值的点,以及两个阈值的点 168 | :param data: 数据 169 | :param x: locs, 坐标位置 170 | :param y: peak值 171 | :return: None 172 | """ 173 | plt.figure(figsize=(12, 4)) 174 | plt.plot(data) 175 | plt.plot(x, y, '*r') 176 | plt.plot(LLV) 177 | plt.plot(RLV) 178 | plt.plot(x, th1) 179 | plt.plot(x, th2) 180 | plt.show() 181 | 182 | 183 | def plot_ecg_delay(x, y): 184 | """ 185 | 绘制ecg的延迟图 186 | """ 187 | plt.figure(figsize=(5, 4)) 188 | plt.plot(x, y) 189 | plt.show() 190 | 191 | 192 | def plot_ecg_delay_3d(x, y, z): 193 | """ 194 | 绘制ecg的延迟图 195 | """ 196 | from mpl_toolkits.mplot3d import Axes3D 197 | fig = plt.figure(figsize=(5, 4)) 198 | ax = Axes3D(fig) 199 | ax.plot(x, y, z) 200 | ax.tick_params(axis='x', colors='g') 201 | ax.tick_params(axis='y', colors='g') 202 | ax.tick_params(axis='z', colors='g') 203 | plt.show() 204 | 205 | 206 | if __name__ == '__main__': 207 | path = '../data/paf' 208 | dat_name = 'n01.dat' 209 | ecg_data1, ecg_data2 = rd.read_f16(path, dat_name) 210 | ecg_data1 = cov.covert_freq(ori_freq=250, tar_freq=200, data=ecg_data1) 211 | plt_ecg(ecg_data1[:1001], fs=200, gain=200, path='1.jpg') 212 | 213 | if __name__ == '__main1__': 214 | plt.figure(figsize=(10, 4)) # 画板大小固定,不要更改 215 | ax = plt.gca() 216 | # 心电图纸:横坐标一般小格0.04s,大格0.2s; 纵坐标小格0.1mv,大格0.5mv 217 | ax.xaxis.set_major_locator(plt.MultipleLocator(0.2)) # 设置x轴主刻度 218 | ax.xaxis.set_minor_locator(plt.MultipleLocator(0.04)) # 设置x轴次刻度 219 | ax.yaxis.set_major_locator(plt.MultipleLocator(0.5)) # 设置y轴主刻度 220 | ax.yaxis.set_minor_locator(plt.MultipleLocator(0.1)) # 设置y轴次刻度 221 | # 绘制大格和小格的线条 222 | ax.grid(which='major', axis="both", linewidth=0.75, linestyle='-', color='r') 223 | ax.grid(which='minor', axis="both", linewidth=0.25, linestyle='-', color='r') 224 | plt.ylim([-2.5, 2.5]) # 纵坐标范围 225 | plt.xlim([0, 5]) # 横坐标范围 226 | plt.show() 227 | -------------------------------------------------------------------------------- /utils/signal_process.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Project : 3 | @File :signal_process.py 4 | @Author :DFQX 5 | @Date :2022/11/15 22:55 6 | @Description: 信号处理相关方法 7 | """ 8 | from scipy import signal 9 | 10 | 11 | def bandpass_filter(data, fs, low=5, high=15): 12 | """ 13 | 带通滤波 14 | :param data: list, 输入信号数据 15 | :param fs: int, 采样率 16 | :param low: int, 截止频率1 17 | :param high: int, 截止频率2 18 | :return: list, 滤波后信号 19 | """ 20 | low = 2 * low / fs 21 | high = 2 * high / fs 22 | b, a = signal.butter(3, [low, high], 'bandpass') 23 | filter = signal.filtfilt(b, a, data) # data为要过滤的信号 24 | return filter 25 | --------------------------------------------------------------------------------