Software-driven Pneumatic Beating Heart Simulator and ECG Display Vanderbilt University, School of Engineering BME 272-273, Group #9 Submitted on April 27, 2011 Group Members: Jacob Bauer (BME) Nicole Rice (EE) Ashley Whiteside (BME) Advisors: Dr. Jonathan Nesbitt Dr. Paul King
61
Embed
Software-driven Pneumatic Beating Heart Simulator …research.vuse.vanderbilt.edu/srdesign/2010/group9/images/Final...monitor to allow the ... The construction of the heart beat simulator
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Cplusplus.com - The C Resources Network. <http://www.cplusplus.com/reference/>.
CreateFile Function (Windows). 2011 <htpp://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx>.
CreateProcess Function (Windows). 2011 <http://msdn.microsoft.com/en-us/library/ms682425(v=vs.85).aspx>.
Fann, James, et al. "Improvement in coronary anastomoses with cardiac surgery simulation." 2008.SciVerse. 2011 <http://www.sciencedirect.com/science?_ob=ArticleURL&_udi=B6WMF-4TRR92B-3&_user=86629&_coverDate=12%2F31%2F2008&_rdoc=1&_fmt=high&_orig=gateway&_origin=gateway&_sort=d&_docanchor=&view=c&_acct=C000006878&_version=1&_urlVersion=0&_userid=86629&md5=9e008b8c9>.
GetCommandLine Function (Windows). 2011 <http://msdn.microsoft.com/en-us/library/ms683156(v=vs.85).aspx>.
Hicks, GL Jr., et al. "Cardopulmonary bypass simulation at the Boot Camp." May 2010. PubMed.gov. April2011 <http://www.ncbi.nlm.nih.gov/pubmed/20451929>.
Qt-Cross-platform Application and UI Framework. 2011 <http://qt.nokia.com/products/developer-tools/>.
Qwt User's Guide: Qt Widgets for Technical Applications. 2011 <http://qwt.sourceforge.net/>.
Qwt User's Guide: QwtPlot Class Reference. 2011 <http://qwt.sourceforge.net/class_qwt_plot.html>.
Qwt User's Guide: QwtPlotCurve Class Reference. 2011<http://qwt.sourceforge.net/class_qwt_plot_curve.html>.
Valderrama, Amy L, Sandra B Dunbar and George A Mensah. "Atrial Fibrillation: Public HealthImplications." American Journal of Preventative Medicine (2005): 75-80.
WriteFile Function (Windows). <http://msdn.microsoft.com/en-us/library/aa365747(v=vs.85).aspx>.
Yale School of Medicine. Yale Medical Group: EKG/ECG. 2011. 25 April 2011<http://www.yalemedicalgroup.org/stw/Page.asp?PageID=STW026195>.
/*** Filename: Simulation.h** @author Jacob Bauer** Description: This is the header file for the Simulation class.* It is a singleton instance to hold all current variables* pertinent to the running simulation.*/
static HANDLE hrMutex; // Mutext for hRate_ valuestatic HANDLE arMutex; // Mutex for aRhythm_ valuestatic HANDLE ipMutex; // Mutex for instance pointerstatic HANDLE goMutex; // Mutex for go_ valuestatic HANDLE goHMutex; // Mutex for goHeart_ value
class Simulation{public:
/*** Returns pointer to singleton instance of Simulation (instance_)
31
*/static Simulation* instance(void);/*** Destroys Simulation object*/~Simulation();/*** Sets heart rate value (hRate_)*/void setHRate(double heartRate);/*** Returns the current heart rate value (hRate_)*/double getHRate(void);/*** Sets arrhythmia value (aRhythm_)*/void setARhythm(const std::string& arrythmia);/*** Returns current arrhythmia value (aRhythm_)*/std::string getARhythm(void);/*** Sets run value (go_)*/void setRun(bool tF);/*** Returns run value (go_)* The pump driver will run while go_ remains TRUE*/bool getRun();/*** Sets HRun value (goHeart_)*/void setHRun(bool tF);/*** Returns HRun value (goHeart_)* Allows program to halt pump without exitting the pump driver*/bool getHRun();
private:/*** Private Ctor for access control*/Simulation();/*** Contains current heart rate value for use in the pump driver*/double hRate_;/*** Contains current arrrhythmia value for use by the ECG driver*/std::string aRhythm_;/*** Contains current boolean value for controlling the pump driver*/bool go_;/*** Contains current boolean value which allows for halting of pump action* without exiting the pump driver*/bool goHeart_;/*** Static pointer to singleton Simulation instance*/static Simulation* instance_;
};#endif // SIMULATION_H
32
/*** Filename: Simulation.cpp** @author Jacob Bauer** Description: This is the source file for the Simulation class.* It is a singleton instance to hold all current variables* pertinent to the running simulation.*/#ifndef SIMULATION_CPP#define SIMULATION_CPP#include "Simulation.h"#include "SimExceptions.h"#include "mainwindow.h"// Sets initial value of instance to NULLSimulation* Simulation::instance_ = 0;// instanceSimulation* Simulation::instance(void){
if(!instance_){
instance_ = new Simulation();}Simulation* temp;if(WaitForSingleObject(ipMutex, 10000) == WAIT_TIMEOUT){
* @author Jacob Bauer** Description: This is the header file for the mainwindow class.*/#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <windows.h>namespace Ui {
class MainWindow;}class MainWindow : public QMainWindow{
public slots:/*** Handles user input of new heart rate*/void hrPushButtonHandler();/*** Handles IPC of new heart rate*/void hrChangeHandler(double hRate);/*** Handles user input of new heart rhythm*/void rhythmChangeHandler(QString rhythm);/*** Updates current heart rate display*/void hrDisplay(double hRate);/*** Checks to see if passed in double is an acceptable heartrate value for* the currently selected rhythm.*/bool isAccHRate(std::string curRhythm, double hRate);
private:Ui::MainWindow *ui;
};#endif // MAINWINDOW_H
/*** Filename: mainwindow.cpp** @author Jacob Bauer** Description: This is the source code file for the mainwindow class.*/#include "mainwindow.h"#include "ui_mainwindow.h"#include "Simulation.h"#include <tchar.h>#include <iostream>#include <sstream>MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);
}MainWindow::~MainWindow(){
delete ui;}// hrPushButtonHandler
35
void MainWindow::hrPushButtonHandler(){
Simulation* instance = Simulation::instance();double curHRate = (ui->lineEdit->text()).toDouble();// Ensures that any changes to heart rate values or heart rate values// sent to ECG_display are compatible with the currently selected rhythm.// Ensures that no changes to heart rate are made while heart is in "vFib".if(isAccHRate(instance->getARhythm(), curHRate)
instance->setHRate(curHRate);hrChangeHandler(instance->getHRate());// Restarts pump driver loop if it was previously turned offif(instance->getHRun() == false){
instance->setHRun(true);// Sends currently selected rhythm to ECG to replace "zero"// Requires sleep function so that ECG_display has time to read// updated heart rate value before updated rhythm is sentSleep(11);rhythmChangeHandler(ui->comboBox->currentText());
}ui->lineEdit->clear();
}// Handles instance when heart rate is changed to zero. Sets Simulator// heart rate value to zero and sends "zero" to ECG_display.else if(curHRate <= 0){
std::cout<<"In rhythmChange Handler"<<std::endl;Simulation* instance = Simulation::instance();// Ensures that any changes set to the rhythm are viable in relation to// currently selected heart rate. Fails to change rhythm if heart is// currently in ventricular fibrillation, "vFib" is handled later.if(isAccHRate(rhythm.toStdString(), instance->getHRate())
strcpy(buffer,"vFib");//Sets heart rate extremely high to mimic "twitching" seen in vFibinstance->setHRate(200);instance->setARhythm("Ventricular Fibrillation");hrDisplay(200);
}else if (rhythm == "zero"){
// Does not set Simulator rhythm value to "zero", the user must// first raise the heart rate back to an acceptable level to// change rhythmstrcpy(buffer,"zero");
}// Ensures that only changing selected rhythm will remove "vFib" condition.// When changed from vFib a standard heart rate of 100 is set. Requires// Sleep function to ensure ECG_display has enough time to read new// heart rate value before new rhythm value is place on clipboard.else if (instance->getARhythm() == "Ventricular Fibrillation"){
/*** Filename: SimExceptions.h** @author Jacob Bauer** Description: This is the header file containing all the custom excetion* classes for the Simulator program*/#ifndef SIMEXCEPTIONS_H#define SIMEXCEPTIONS_H#include <stdexcept>#include <string>class MutexTimeOutExc : public std::runtime_error{public:
* @author Jacob Bauer** Description: This is the source code file for main.*/#include <QtGui/QApplication>#include "mainwindow.h"#include "Simulation.h"#include "SimExceptions.h"#include <windows.h>#include <process.h>#include <tchar.h>#include <stdio.h>#include <string>#include <iostream>#include <cstdlib>unsigned hrDriverTID;static HANDLE hrDriverThread;static HANDLE arduinoSerial;/*** Standard code for setting up serial port in Windows, altered to interface* correctly with Arduino device.*/void portSetUp(){
std::wstring comPrefix = L"COM";std::wstring userIn;std::wcout<<"Please input desired COM (USB) port (1-9, enter d for default): "<<std::endl;std::wcin>>userIn;if(userIn == L"d"){
throw SerialPortSetupExc("Specified serial port does not exist");}throw SerialPortSetupExc("Unknown error: Serial port setup");
}*/// Setting serial port settingsDCB dcbSerialParams = {0};dcbSerialParams.DCBlength=sizeof(dcbSerialParams);if (!GetCommState(arduinoSerial, &dcbSerialParams)){
//throw SerialPortSetupExc("Exception occured while getting serial port state");}dcbSerialParams.BaudRate=CBR_9600;dcbSerialParams.ByteSize=8;dcbSerialParams.StopBits=ONESTOPBIT;dcbSerialParams.Parity=NOPARITY;if(!SetCommState(arduinoSerial, &dcbSerialParams)){
//throw SerialPortSetupExc("Exception occured while setting serial port state");}
}/*** A helper function which initializes the ECG_display process and* reports errors that occur during CreateProcess().*/void startECGDisplay(){
{// Throws error message with GetLastError() includedchar sysError[16];std::string errorMessage = "CreateProcess() for ECG_display.exe failed with System Error:
// Sets up serial port communicatino with Arduino deviceportSetUp();// Starts ECGDisplaystartECGDisplay();// Shows main windoww.show();// Starts main pump driverhrDriverThread = (HANDLE) _beginthreadex(NULL, 0, hrDriver, (void *) 0, 0, (unsigned *)
/*** Filename: ECG.h** @author Jacob Bauer** Description: This is the header file for the ECG class.*/#ifndef ECG_H#define ECG_H#include <QtGui/QApplication>#include <QCoreApplication>#include <QPainter>#include "ECGExceptions.h"#include "mainwindow.h"#include "qwt_plot.h"#include "qwt_plot_curve.h"#include <vector>#include <string>#include <iostream>#include <fstream>#include <sstream>#include <windows.h>#include <stdio.h>#include <stdlib.h>#include <time.h>#include <math.h>const size_t DSIZE = 500;static HANDLE sourceValMutex;static double PI = 3.14159;class ECG{public:
/*** Default constructor, initializes ECG class with passed in QtGUI widget*/ECG(MainWindow* widget);/*** Destructor*/~ECG();/*** Updates plot data, counting variables, timing variables and replots graph* for animation. Calls setSource. Ensures a replot occurs every 1/100 of* second.*/void run();/*** Checks clipboard for IPC data and calls setSource if new data is found*/void checkIPC();/*** Builds correct file path to ECG data calls openFile() with that path*/void setSource();/**
45
* Calls checkIPC() and setSource() sequentially*/void checkAndSet();/*** Opens file with passed in string, manages transfer of file contents into* sourceVals_.*/void openFile(std::string fileName);/*** Uses preset formula to load ventricular tachycardia data into sourceVals_*/void loadVTac();/*** Uses preset formula to load ventricular fibrillation data into sourceVals_*/void loadVFib();/*** Sets sourceVals_ to zeros*/void loadZero();/*** Checks to see if passed in string is a recognized arrhythmia type*/bool isAR(std::string aR);
private:// GUI objectsMainWindow* mainWindow_;QwtPlot* ecgDisplay_;QwtPlotCurve* ecgTrace_;// Variables to indicate current state of simulationbool go_;std::string curAR_;int curHRate_;// Data storage variablesdouble xAxis_[DSIZE]; // xdouble displayVals_[DSIZE]; // display ystd::vector<double> sourceVals_; // source y
};#endif // ECG_H
/*** Filename: ECG.cpp** @author Jacob Bauer** Description: This is the source file for the ECG class.*/#include "ECG.h"// Default ctorECG::ECG(MainWindow* mainW) : mainWindow_(mainW),
{// Initializes displayVals_ array with zerosfor(size_t i = 0; i < DSIZE; ++i){
xAxis_[i] = i;displayVals_[i] = 0.0;
}// Sets sourceVals_ to all zerosloadZero();// Colors & labelecgDisplay_->setCanvasBackground(QColor(Qt::black));ecgDisplay_->setTitle("ECG Display");ecgDisplay_->setAxisTitle(QwtPlot::xBottom, "Time(s)");ecgDisplay_->enableAxis(QwtPlot::yLeft, false);ecgTrace_->setPen(QPen(QColor(Qt::green)));// Sets QwtPlot "ecgDisplay_" as central widget
46
mainWindow_->setCentralWidget(ecgDisplay_);// Attaches QwtPlotCurve "ecgTrace_" to QwtPlot "ecgDisplay_"ecgTrace_->attach(ecgDisplay_);// Shows main windowmainWindow_->show();ecgTrace_->setSamples(xAxis_, displayVals_, DSIZE);
}// DtorECG::~ECG(){
delete ecgDisplay_;delete ecgTrace_;
}// run()void ECG::run(){
// Counting and timing variablessize_t i = 0;size_t j = 0;clock_t start;clock_t starttest; // FOR TESTING// Will not attempt to run display until both variables are set and// a file is loaded. Replot() allows window to be moved and resized// before the ECG animation "starts".while(curAR_ == "unset" || curHRate_ == -1){
// Resets counting variables when either reaches the end of their// countainerif(i >= DSIZE){
i = 0;// Tests to see if whole display is running in 5 secstd::cout<<"time: "<<(clock()-starttest)/1000<<std::endl; // FOR TESTINGstarttest = clock(); // FOR TESTING
}// Updating plot dataecgTrace_->setSamples(xAxis_, displayVals_, DSIZE);ecgDisplay_->replot();// Waits for all events to finishsQCoreApplication::processEvents();// Checks for new arrhythmia/heart ratecheckIPC();// Determines correct sleep time so that each update is 1/100 of a secif((clock()-start) < 40){
47
while(40 > (clock()-start)) {}}
}}// checkIPCvoid ECG::checkIPC(){
std::string IPCin;int intIPCin;// Getting IPC data from clipboardwhile(OpenClipboard(NULL) == 0) {std::cout<<"Open Clipboard failed"<<std::endl;}HANDLE hData = GetClipboardData(CF_TEXT);char * buffer = (char*)GlobalLock( hData );GlobalUnlock( hData );CloseClipboard();// Sets IPC variablesIPCin = buffer;intIPCin = atoi(IPCin.c_str());// Checks if IPC variable was a valid heart rate and if the value has// changed. Will not call set source until both values have been set.if(intIPCin >= 50 && intIPCin <= 150 && intIPCin != curHRate_){
setSource();}// Checks if IPC variable was a valid rhythm and if the value has changed.// Will not call set source until both values have been set.if(isAR(IPCin) && IPCin != curAR_){
// The file path is built using "GetComandLine()", curAR_ and curHRate_.// If curAR_ or curHRate_ have not yet been set, there is no attempt to// load a file.std::wstring wfilePath(GetCommandLine());std::string filePath;std::string filePathEnd;std::stringstream ss;// Converts returned command line to string and cuts off unneeded// sections. Builds the begining of the file path and the end of// the file path.filePath.assign(wfilePath.begin()+1, wfilePath.end()-16);filePath += "ECG_waveforms\\";ss<<curHRate_;ss>>filePathEnd;filePathEnd = "_" + (filePathEnd += ".txt");// Calls openFile with correct fileName or correct load function to update// sourceVals_, based on curAR_ and curHRate_if(curAR_ == "Normal"){
sourceVals_.clear();for(int i = 0; i < 2*(6000/curHRate_); i++){
// Sets sourceVals(i) to a random number between 0 and 1 and alters// multiplied by a sin wave. This effectively replicates a standard// ventricular fibrillation.sourceVals_.push_back(1.5 + (1.5*(((rand()%100)/100.00) * sin(0.1875*PI*i))) );
}}// loadZerovoid ECG::loadZero(){
sourceVals_.clear();for(int i = 0; i < (6000/curHRate_); i++){
sourceVals_.push_back(0);}
49
}// isARbool ECG::isAR(std::string aR){
return (aR == "Normal" || aR == "aFib" || aR == "vTac" || aR == "vFib"|| aR == "zero");
/*** Filename: ECGExceptions.h** @author Jacob Bauer** Description: This is the header file containing all the custom excetion* classes for the ECG_display program*/#ifndef ECGEXCEPTIONS_H#define ECGEXCEPTIONS_H#include <stdexcept>#include <string>class MutexTimeOutExc : public std::runtime_error{public:
Introduction:The aim of the ECG simulator is to produce the typical ECG waveforms of different leads and as many arrhythmiasas possible. My ECG simulator is a matlab based simulator and is able to produce normal lead II ECG waveform.
52
The use of a simulator has many advantages in the simulation of ECG waveforms. First one is saving of time andanother one is removing the difficulties of taking real ECG signals with invasive and noninvasive methods. The ECGsimulator enables us to analyze and study normal and abnormal ECG waveforms without actually using the ECGmachine. One can simulate any given ECG waveform using the ECG simulator.
Significant features of ECG waveform:A typical scalar electrocardiographic lead is shown in Fig. 1, where the significant features of the waveform are theP, Q, R, S, and T waves, the duration of each wave, and certain time intervals such as the P-R, S-T, and Q-Tintervals.
fig 1.Typical ECG signal
Main features of this simulator: Any value of heart beat can be set Any value of intervals between the peaks (ex-PR interval) can be set Any value of amplitude can be set for each of the peaks Fibrillation can be simulated Noise due to the electrodes can be simulated Heart pulse of the particular ECG wave form can be represented in a separate graph
Principle:Fourier series
Any periodic functions which satisfy dirichlet’s condition can be expressed as a series of scaled magnitudes of sinand cos terms of frequencies which occur as a multiple of fundamental frequency.
∞ ∞f (x) = (ao/2) + Σ an cos (nπx / l) + Σ bn sin (nπx / l),
n=1 n=1
ao = (1/ l ) ∫ f (x) dx , T = 2l -- (1)T
an = (1/ l ) ∫ f (x) cos (nπx / l) dx , n = 1,2,3…. -- (2)T
bn = (1/ l ) ∫ f (x) sin (nπx / l) dx , n = 1,2,3…. -- (3)T
53
ECG signal is periodic with fundamental frequency determined by the heart beat. It also satisfies the dirichlet’sconditions: Single valued and finite in the given interval Absolutely integrable Finite number of maxima and minima between finite intervals It has finite number of discontinuitiesHence fourier series can be used for representing ECG signal.
Calculations:If we observe figure1, we may notice that a single period of a ECG signal is a mixture of triangular and sinusoidalwave forms. Each significant feature of ECG signal can be represented by shifted and scaled versions one of thesewaveforms as shown below. QRS, Q and S portions of ECG signal can be represented by triangular waveforms P, T and U portions can be represented by triangular waveforms
Once we generate each of these portions, they can be added finally to get the ECG signal.Lets take QRS waveform as the centre one and all shiftings takes place with respect to this part of the signal.
How do we generate periodic QRS portion of ECG signal
Fig 2. generating QRS waveformFrom equation (1), we have
f(x) = (–bax/l) + a 0 < x < ( l/b )= ( bax/l) + a (– l/b)< x < 0
ao = (1/ l ) ∫ f (x) dxT
= (a/b) * (2 – b )
an = (1/ l ) ∫ f (x) cos (nπx / l) dxT
= ( 2ba / (n2π2 )) * ( 1 – cos (nπ/b))
bn = (1/ l ) ∫ f (x) sin (nπx / l) dxT
= 0 ( because the waveform is a even function)
∞
54
f (x) = (ao/2) + Σ an cos (nπx / l)n=1
How do we generate periodic p-wave portion of ECG signal
Fig 3. generation of p-wavef(x) = cos ((πbx) /(2l)) (–l/b)< x < (l/b)
ao = (1/ l ) ∫ cos ((πbx) / (2l)) dxT
= (a/(2b))(2-b)
an = (1/ l ) ∫ cos ((πbx) / (2l)) cos (nπx / l) dxT
= (((2ba)/(i2π2)) (1-cos((nπ)/b))) cos((nπx)/l)
bn = (1/ l ) ∫ cos ((πbx) / (2l)) sin (nπx / l) dxT
= 0 ( because the waveform is a even function)
∞f (x) = (ao/2) + Σ an cos (nπx / l)
n=1
Implementation in MATLAB:Code:
55
Save the below file as complete.mx=0.01:0.01:2;default=input('Press 1 if u want default ecg signal else press 2:\n');if(default==1)
li=30/72;
a_pwav=0.25;d_pwav=0.09;t_pwav=0.16;
a_qwav=0.025;d_qwav=0.066;t_qwav=0.166;
a_qrswav=1.6;d_qrswav=0.11;
a_swav=0.25;d_swav=0.066;t_swav=0.09;
a_twav=0.35;d_twav=0.142;t_twav=0.2;
a_uwav=0.035;d_uwav=0.0476;t_uwav=0.433;
elserate=input('\n\nenter the heart beat rate :');li=30/rate;
All the files have to be saved in the same folder Save the files in the names mentioned above the code While entering the specification, give the amplitude in mV and duration in seconds
Output waveform:
Default Specification
Heart beat :72 Amplitude:
P wave 25mVR wave 1.60mVQ wave 0.025mVT wave 0.35mV
Not all the default values are specified here. They can be obtained from the code of the simulator from the filecomplete.m. The user can enter their desired values of specifications too. Other concepts of the code are simpleand are self explanatory.
A typical output for the above specification will be like this:
References:
R.S. Khandpur, Handbook of Biomedical Instrumentation Leslie Cromwell, Biomedical Instrumentation and Measurements, Prentice Hall of India. MATLAB The Language of Technical Computing, The Mathworks.
Appendix 5: Arduino Uno Code
The following is the code used to program the Arduino microcontroller to interface with thecomputer software and generate the signals used to control the piezoelectric speaker and solenoidvalve.
int ledPin = 13;int tonePin = 7;int state=0;void setup() {
pinMode(ledPin, OUTPUT); // pin will be used to for outputSerial.begin(9600); // same as in your c++ script
}
void loop(){
if (Serial.available() > 0){
61
state = Serial.read(); // used to read incoming data