I thought I'd share the app I've been working up to determine QTc as captured by the Olimex EKG/EMG Shield. I have shamelessly assembled this from pieces and parts of pubic domain code. If you see something of yours, Thank You!!
Explanation of QTc: http://en.wikipedia.org/wiki/QT_interval (http://en.wikipedia.org/wiki/QT_interval)
This is rough around the edges, but I thought it would be helpful to offer more open source code examples for working with this lovely little shield.
Note, a single shield offers three leads, optimum positioning for a three lead EKG measurement appears to be left and right arm (wrist) and right leg (ankle).
Since I'm using a single shield(channel) unit, the (Duemilanove) Arduino code is stripped down for speed. It simply returns the analog value as measured every .0039 sec (as opposed to the multi-byte packet defined in the demo code for ElectricGuru):
#include <compat/deprecated.h>
#include <FlexiTimer2.h>
#define SAMPFREQ 256 // ADC sampling rate 256
#define TIMER2VAL (1024/(SAMPFREQ)) // Set 256Hz sampling frequency
volatile unsigned char CurrentCh=0; //Current channel being sampled.
volatile unsigned int ADC_Value = 0; //ADC current value
volatile unsigned int smpCounter;
void setup() {
noInterrupts(); // Disable all interrupts before initialization
smpCounter = 0; // setup sample counter
FlexiTimer2::set(TIMER2VAL, Timer2_Overflow_ISR);
FlexiTimer2::start();
Serial.begin(57600);
interrupts(); // Enable all interrupts after initialization has been completed
}
void Timer2_Overflow_ISR()
{
ADC_Value = analogRead(CurrentCh);
Serial.println(ADC_Value); // print sample
}
void loop() {
__asm__ __volatile__ ("sleep");
}
The Arduino code's data is caught by the following Python3 code. It does the following;
- Finds available com ports (should be cross platform, I lifted the detection from the miniterm code, but I have only tested on windows)
- Opens a dialog to allow you to pick a com port, which it tests.
- Opens a window which allows you to capture signal (each time you toggle through a capture session, it starts a new one rather than appending)
- Allows you to move marker lines in the plot window, via the mouse, to mark R1, R2, Q, T (see link above)
- Allows you to click the calc button and calculate; Heartrate, RR, QT and QTc (see link above)
- Left clicking on the plot window allows you to pan the plot
- Right clicking on the plot window allows you to export the data via image or CSV, zoom and more (thanks pyqtgraph!)
Obligatory Notice:
This software is public domain and for demonstration purposes only. Do not rely on the calculated values for any aspect of your cardiac or regular health. Consult a doctor if you want to get a real EKG.
After zooming in (with some nasty 60Hz!):
(http://slamdanz.com/images/screenshot.png)
from PyQt4 import QtGui, QtCore
import pyqtgraph as pg
from pyqtgraph.ptime import time
import serial
import numpy as np
import math
import sys
import glob
##------------------------------------------------------------
class main_window(QtGui.QWidget):
def __init__(self):
super(main_window, self).__init__()
self.initUI()
def initUI(self):
layout = QtGui.QGridLayout()
self.p = pg.PlotWidget()
r1_pen = QtGui.QPen(QtCore.Qt.red, 0, QtCore.Qt.SolidLine)
self.r1_line = self.p.addLine(x=.5, pen=r1_pen, movable=True)
self.r1_line.sigPositionChangeFinished.connect(self.r1_move)
r2_pen = QtGui.QPen(QtCore.Qt.darkYellow, 0 , QtCore.Qt.SolidLine)
self.r2_line = self.p.addLine(x=1, pen=r2_pen, movable=True)
self.r2_line.sigPositionChangeFinished.connect(self.r2_move)
q_pen = QtGui.QPen(QtCore.Qt.green, 0, QtCore.Qt.SolidLine)
self.q_line = self.p.addLine(x=1.5, pen=q_pen, movable=True)
self.q_line.sigPositionChangeFinished.connect(self.q_move)
t_pen = QtGui.QPen(QtCore.Qt.blue, 0, QtCore.Qt.SolidLine)
self.t_line = self.p.addLine(x=2, pen=t_pen, movable=True)
self.t_line.sigPositionChangeFinished.connect(self.t_move)
timer_btn = QtGui.QPushButton('Collect Data',self)
timer_btn.clicked.connect(self.timer_toggle)
calc_btn = QtGui.QPushButton('Calc',self)
calc_btn.clicked.connect(self.calc_qtc)
quit_btn = QtGui.QPushButton('Quit',self)
quit_btn.clicked.connect(self.quit_button)
self.timer_lbl = QtGui.QLabel('Waiting to Capture')
r1_lbl = QtGui.QLabel('R1 = ')
self.r1_val_lbl = QtGui.QLabel('0')
r1_palette = QtGui.QPalette()
r1_palette.setColor(QtGui.QPalette.Foreground,QtCore.Qt.red)
r1_lbl.setPalette(r1_palette)
r2_lbl = QtGui.QLabel('R2 = ')
self.r2_val_lbl = QtGui.QLabel('0')
r2_palette = QtGui.QPalette()
r2_palette.setColor(QtGui.QPalette.Foreground,QtCore.Qt.darkYellow)
r2_lbl.setPalette(r2_palette)
q_lbl = QtGui.QLabel('Q = ')
self.q_val_lbl = QtGui.QLabel('0')
q_palette = QtGui.QPalette()
q_palette.setColor(QtGui.QPalette.Foreground,QtCore.Qt.green)
q_lbl.setPalette(q_palette)
t_lbl = QtGui.QLabel('T = ')
self.t_val_lbl = QtGui.QLabel('0')
t_palette = QtGui.QPalette()
t_palette.setColor(QtGui.QPalette.Foreground,QtCore.Qt.blue)
t_lbl.setPalette(t_palette)
rr_lbl = QtGui.QLabel('RR = ')
self.rr_val_lbl = QtGui.QLabel('0')
h_lbl = QtGui.QLabel('HeartRate = ')
self.h_val_lbl = QtGui.QLabel('0')
qt_lbl = QtGui.QLabel('QT = ')
self.qt_val_lbl = QtGui.QLabel('0')
qtc_lbl = QtGui.QLabel('QTc = ')
self.qtc_val_lbl = QtGui.QLabel('0')
layout.addWidget(timer_btn, 0, 0)
layout.addWidget(self.timer_lbl, 0, 1)
layout.addWidget(r1_lbl, 1, 0, QtCore.Qt.AlignRight)
layout.addWidget(self.r1_val_lbl, 1, 1, QtCore.Qt.AlignLeft)
layout.addWidget(r2_lbl, 2, 0, QtCore.Qt.AlignRight)
layout.addWidget(self.r2_val_lbl, 2, 1, QtCore.Qt.AlignLeft)
layout.addWidget(q_lbl, 3, 0, QtCore.Qt.AlignRight)
layout.addWidget(self.q_val_lbl, 3, 1, QtCore.Qt.AlignLeft)
layout.addWidget(t_lbl, 4, 0, QtCore.Qt.AlignRight)
layout.addWidget(self.t_val_lbl, 4, 1, QtCore.Qt.AlignLeft)
layout.addWidget(calc_btn, 5, 0)
layout.addWidget(h_lbl, 6, 0, QtCore.Qt.AlignRight)
layout.addWidget(self.h_val_lbl, 6, 1, QtCore.Qt.AlignLeft)
layout.addWidget(h_lbl, 7, 0, QtCore.Qt.AlignRight)
layout.addWidget(self.h_val_lbl, 7, 1, QtCore.Qt.AlignLeft)
layout.addWidget(qt_lbl, 8, 0, QtCore.Qt.AlignRight)
layout.addWidget(self.qt_val_lbl, 8, 1, QtCore.Qt.AlignLeft)
layout.addWidget(qtc_lbl, 9, 0, QtCore.Qt.AlignRight)
layout.addWidget(self.qtc_val_lbl, 9, 1, QtCore.Qt.AlignLeft)
layout.addWidget(quit_btn, 10, 0)
layout.addWidget(self.p, 0, 2, 10, 5)
self.setLayout(layout)
def calc_qtc(self):
r1 = self.r1_line.value()
r2 = self.r2_line.value()
q = self.q_line.value()
t = self.t_line.value()
rr = r2-r1
self.rr_val_lbl.setText(str("%.3f" % rr))
h = 60/(r2-r1)
self.h_val_lbl.setText(str("%.3f" % h))
qt = (t - q)
self.qt_val_lbl.setText(str("%.3f" % qt))
qtc = (t - q)/math.sqrt(r2-r1)
self.qtc_val_lbl.setText(str("%.3f" % qtc))
def quit_button(self):
timer.stop()
self.close()
def timer_toggle(self):
global ptr, data, tline
if (timer.isActive()):
timer.stop()
self.timer_lbl.setText('Captured ' + str("%.3f" % ptr) + ' sec')
ydata = np.array(data,dtype='float64')
xdata = np.array(tline,dtype='float64')
curve.setData(xdata,ydata)
ptr = 0
data = [512]
tline = [0]
app.processEvents()
else:
self.timer_lbl.setText('Capturing')
timer.start(0)
def r1_move(self):
r1 = self.r1_line.value()
self.r1_val_lbl.setText(str("%.3f" % r1))
def r2_move(self):
r2 = self.r2_line.value()
self.r2_val_lbl.setText(str("%.3f" % r2))
def q_move(self):
q = self.q_line.value()
self.q_val_lbl.setText(str("%.3f" % q))
def t_move(self):
t = self.t_line.value()
self.t_val_lbl.setText(str("%.3f" % t))
##------------------------------------------------------------
class ListPortsDialog(QtGui.QDialog):
def __init__(self, parent=None):
super(ListPortsDialog, self).__init__(parent)
self.setWindowTitle('List of serial ports')
self.ports_list = QtGui.QListWidget()
self.tryopen_button = QtGui.QPushButton('Try to open')
self.tryopen_button.clicked.connect(self.on_tryopen)
quit_btn = QtGui.QPushButton('Continue',self)
quit_btn.clicked.connect(self.quit_button)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.ports_list)
layout.addWidget(self.tryopen_button)
layout.addWidget(quit_btn)
self.setLayout(layout)
self.fill_ports_list()
def quit_button(self):
self.close()
def on_tryopen(self):
global port_name
cur_item = self.ports_list.currentItem()
if cur_item is not None:
port_name = str(cur_item.text())
##print(port_name)
try:
ser = serial.Serial(port_name, 57600)
ser.close()
QtGui.QMessageBox.information(self, 'Success',
'Opened %s successfully' % cur_item.text())
except SerialException as e:
QtGui.QMessageBox.critical(self, 'Failure',
'Failed to open %s:\n%s' % (
cur_item.text(), e))
def fill_ports_list(self):
try:
from serial.tools.list_ports import comports
except ImportError:
comports = None
if comports:
sys.stderr.write('\n--- Available ports:\n')
for port in sorted(comports()):
print(port)
self.ports_list.addItem(port[0])
##------------------------------------------------------------
def readData():
global data, ptr, port, tline
if (port.inWaiting() > 0):
ptr += .0039
line = float(port.readline().strip())
if (line >= 1024.0):
line = 1024.0
port.flush();
data.append(line)
tline.append(ptr)
##------------------------------------------------------------
if __name__ == '__main__':
global curve, data, ptr, port, tline, port_name
ser_app = QtGui.QApplication(sys.argv)
form = ListPortsDialog()
form.show()
ser_app.exec()
ptr = 0
data = [512]
tline = [0]
app = QtGui.QApplication(sys.argv)
ex = main_window()
ex.show()
curve = ex.p.plot()
port = serial.Serial(port_name, 57600)
timer = QtCore.QTimer()
timer.timeout.connect(readData)
sys.exit(app.exec_())
port.close()
Cheers! Hope that helps folks working with this board!
--Tim Deagan