11:09:19 pm on 2/4/12

Menu
» Home
» About Scott
» VD Labs
» QRSS VD
» Old Stuff
» Archive
» Publications
» Contact

Categories
» C/C++
» Circuitry
» DIY ECG
» General
» high altitude balloon
» Linux
» Microcontrollers
» Molecular Biology
» My Website
» PHP
» Prime Numbers
» Python
» Radio
» UCF Lab
» Everything
» RF Links

Writings
» MD Labels
» Streamrip
» AIM Thoughts
» WindowsXP?
» Partitioning
» CD/DVD Repair
» Monitor Info
» CRT Deflection
» Venomcrack
» Flash Thing
» Heart/Brain
» Diabetes
» Triops
» Biomed

Friends
» Mike
» Fred
» Kyle W
» Nick
» Louis
» Tom
» Kyle H




Archives
» August 2011
» July 2011
» June 2011
» March 2011
» February 2011
» January 2011
» December 2010
» November 2010
» September 2010
» August 2010
» July 2010
» June 2010
» May 2010
» April 2010
» March 2010
» February 2010
» January 2010
» December 2009
» September 2009
» August 2009
» July 2009
» June 2009
» May 2009
» April 2009
» March 2009
» February 2009
» January 2009
» December 2008
» November 2008
» October 2008
» September 2008
» September 2007
» December 2006
» August 2006
» January 2006
» August 2005
» July 2005
» June 2005
» May 2005
» April 2005
» March 2005
» February 2005
» January 2005
» December 2004
» November 2004
» October 2004
» September 2004
» August 2004
» July 2004
» June 2004
» May 2004
» April 2004
» March 2004
» February 2004
» January 2004
» December 2003
» November 2003
» October 2003
» September 2003
» August 2003
» July 2003
» June 2003
» May 2003
» April 2003
» March 2003
» February 2003
» January 2003
» December 2002
» November 2002
» October 2002
» September 2002
» June 2001
« DIY ECG Trial 1
DIY ECG Progress »


Circuits vs. Software
1,458 words | Posted on January 15th, 2009
Scott was 23.31 years old when he wrote this!
Filed under: Circuitry, DIY ECG, General, Python

Would I rather design circuits or software? I’m a software guy (likely due to my lack of working knowledge of circuits) so I’d rather record noisy signals and write software to eliminate the noise, rather than assembling circuits to eliminate the noise for me. In the case of my DIY ECG machine, I’d say I’ve done a great job of eliminating noise via the software route. Most DIY ECG circuits on the net use multiple op-amps and diodes to do this, and have a hardware-based band-pass filter to eliminate frequencies around 60 Hz. Instead of all that fancy stuff, I made a super-crude circuit (a single op-amp and two resisters) to record my ECG. It was INCREDIBLY noisy! So, how did I clean it up with software? I’ll tell you.

The first step in removing electrical noise is classifying it. Most of the noise in my signal were overlapping sine waves caused by my electrodes picking up signals not from my body. This was determined by simply close-up observation of the original trace. Since this sine-type interference is consistant, power-spectral analysis could be applied to determine the frequencies of the noise so I could block them out. I used the fast Fourier transform algorithm (FFT) on the values from my wave file to generate a plot of noise level with respect to frequency (noise was seen as sharp peaks). I manually blocked-out certain regions of the FFT trace that I thought were noise-related (colored bands, all the values of which were set to zero) – a process known as band-pass filtering (something which is possible to do electronically with a more complicated circuit) – then performed an inverse FFT algorithm on the trace. The result was a trace with greatly reduced noise and after a moving window smoothing algorithm was applied the signal was better than ever. Below are some figures. Note that I recorded the raw WAV file with “sound recorder” (not GoldWave) and did all of the processing (including band-pass filtering) within Python.

What an awesome chart! On top we have the power spectral analysis with band-stop filters applied at the colored regions. Below is the trace of the original WAV file (light gray), the inverse-FFT-filtered trace (dark gray), and the smoothed inverse-FFT-filtered trace (black) – the final ECG signal I intend to use.

This is a magnified view of a few heartbeats after the inverse FFT filtering (multi-band-blocking) process was applied. Not bad! Not bad at all…

And, of course, the updated code:

 import wave, struct, numpy, pylab, scipy  

 fname='./success3.wav'  

 def readwave(wavfilename):  
     """load raw data directly from a WAV file."""  
     global rate  
     w=wave.open(wavfilename,'rb')  
     (nchannel, width, rate, length, comptype, compname) = w.getparams()  
     print "[%s] %d HZ (%0.2fsec)" %(wavfilename, rate, length/float(rate))  
     frames = w.readframes(length)  
     return numpy.array(struct.unpack("%sh" %length*nchannel,frames))  

 def shrink(data,deg=100):  
     """condense a linear data array by a multiple of [deg]."""  
     global rate  
     small=[]  
     print "starting with", len(data)  
     for i in range(len(data)/deg):  
         small.append(numpy.average(data[i*deg:(i+1)*deg]))  
     print "ending with", len(small)  
     rate = rate/deg  
     #return small[40000:50000]  
     return small  

 def normalize(data):  
     """make all data fit between -.5 and +.5"""  
     data=data-numpy.average(data)  
     big=float(max(data))  
     sml=float(min(data))  
     data=data/abs(big-sml)  
     data=data+float(abs(min(data)))-.47  
     return data  

 def smooth(data,deg=20,expand=False):  
     """moving window average (deg = window size)."""  
     for i in range(len(data)-deg):  
         if i==0: cur,smooth=sum(data[0:deg]),[]  
         smooth.append(cur/deg)  
         cur=cur-data[i]+data[i+deg]  
     if expand:  
         for i in range(deg):  
             smooth.append(smooth[-1])  
     return smooth  

 def smoothListGaussian(list,degree=10,expand=False):  
     window=degree*2-1  
     weight=numpy.array([1.0]*window)  
     weightGauss=[]  
     for i in range(window):  
         i=i-degree+1  
         frac=i/float(window)  
         gauss=1/(numpy.exp((4*(frac))**2))  
         weightGauss.append(gauss)  
     weight=numpy.array(weightGauss)*weight  
     smoothed=[0.0]*(len(list)-window)  
     for i in range(len(smoothed)):  
         smoothed[i]=sum(numpy.array(list[i:i+window])*weight)/sum(weight)  
     if expand:  
         for i in range((degree*2)-1):  
             smoothed.append(smoothed[-1])  
     return smoothed  

 def goodSmooth(data):  
     #data=smooth(fix,20,True)  
     data=smooth(fix,100,True)  
     #data=smooth(fix,20,True)  
     return data    

 def makeabs(data):  
     """center linear data to its average value."""  
     for i in range(len(data)): data[i]=abs(data[i])  
     return data  

 def invert(data):  
     """obviously."""  
     for i in range(len(data)): data[i]=-data[i]  
     return data  

 def loadwav(fname):  
     """a do-everything function to get usable, smoothed data from a WAV."""  
     wav=readwave(fname)  
     wav=shrink(wav)  
     wav=invert(wav)  
     wav=smooth(wav)  
     wav=smooth(wav,10)  
     wav=normalize(wav)  
     Xs=getXs(wav)  
     return Xs,wav  

 def getXs(datalen):  
     """calculate time positions based on WAV frequency resolution."""  
     Xs=[]  
     for i in range(len(datalen)):  
         Xs.append(i*(1/float(rate)))  
     print len(datalen), len(Xs)  
     return Xs  

 def integrate(data):  
     """integrate the function with respect to its order."""  
     inte=[]  
     for i in range(len(data)-1):  
         inte.append(abs(data[i]-data[i+1]))  
     inte.append(inte[-1])  
     return inte  

 def getPoints(Xs,data,res=10):  
     """return X,Y coordinates of R peaks and calculate R-R based heartrate."""  
     pXs,pYs,pHRs=[],[],[]  
     for i in range(res,len(data)-res):  
         if data[i]>data[i-res]+.1 and data[i]>data[i+res]+.1:  
             if data[i]>data[i-1] and data[i]>data[i+1]:  
                 pXs.append(Xs[i])  
                 pYs.append(data[i])  
                 if len(pXs)>1:   
                     pHRs.append((1.0/(pXs[-1]-pXs[-2]))*60.0)  
     pHRs.append(pHRs[-1])  
     return pXs,pYs,pHRs  

 def bandStop(fft,fftx,low,high,show=True):  
     lbl="%d-%d"%(low,high)  
     print "band-stopping:",lbl  
     if show:  
         col=pylab.cm.spectral(low/1200.)  
         pylab.axvspan(low,high,alpha=.4,ec='none',label=lbl,fc=col)  
         #pylab.axvspan(-low,-high,fc='r',alpha=.3)  
     for i in range(len(fft)):  
         if abs(fftx[i])>low and abs(fftx[i])<high :  
             fft[i]=0  
     return fft  

 def getXs(data):  
     xs=numpy.array(range(len(data)))  
     xs=xs*(1.0/rate)  
     return xs  

 def clip(x,deg=1000):  
     return numpy.array(x[deg:-deg])  

 pylab.figure(figsize=(12,8))  
 raw = invert(shrink(readwave(fname),10))  
 xs = getXs(raw)  
 fftr = numpy.fft.fft(raw)  
 fft = fftr[:]  
 fftx= numpy.fft.fftfreq(len(raw), d=(1.0/(rate)))  

 pylab.subplot(2,1,1)  
 pylab.plot(fftx,abs(fftr),'k')  

 fft=bandStop(fft,fftx,30,123)  
 fft=bandStop(fft,fftx,160,184)  
 fft=bandStop(fft,fftx,294,303)  
 fft=bandStop(fft,fftx,386,423)  
 fft=bandStop(fft,fftx,534,539)  
 fft=bandStop(fft,fftx,585,610)  
 fft=bandStop(fft,fftx,654,660)  
 fft=bandStop(fft,fftx,773,778)  
 fft=bandStop(fft,fftx,893,900)  
 fft=bandStop(fft,fftx,1100,max(fftx))  
 pylab.axis([0,1200,0,2*10**6])  
 pylab.legend()  
 pylab.title("Power Spectral Analysis",fontsize=28)  
 pylab.ylabel("Power",fontsize=20)  
 pylab.xlabel("Frequency (Hz)",fontsize=20)  

 pylab.subplot(2,1,2)  
 pylab.title("Original Trace",fontsize=28)  
 pylab.ylabel("Potential",fontsize=20)  
 pylab.xlabel("Time (sec)",fontsize=20)  
 pylab.plot(clip(xs),clip(raw),color='.8',label='1: raw')  

 fix = scipy.ifft(fft)  
 pylab.plot(clip(xs),clip(fix)+5000,color='.6',label='2: band-stop')  
 pylab.plot(clip(xs),clip(goodSmooth(fix))-5000,'k',label='3: smoothed')  
 pylab.legend()  
 pylab.title("Band-Stop Filtered Trace",fontsize=28)  
 pylab.ylabel("Potential",fontsize=20)  
 pylab.xlabel("Time (sec)",fontsize=20)  

 pylab.subplots_adjust(hspace=.5)  
 pylab.savefig('out.png',dpi=100)  
 pylab.show()  
 print "COMPLETE"  
 




This entry was posted on Thursday, January 15th, 2009 at 5:47 pmand is filed under Circuitry, DIY ECG, General, Python. You can follow any responses to this entry through the RSS 2.0 feed. You can skip to the end and leave a response. Pinging is currently not allowed.



One Response to “Circuits vs. Software”

Hiram wrote the following at 09:49:13 PM on September 14th, 2010

I am a student and research about audio. Thanks for you blog. It is very helpful for me.

Leave a Reply




copyright © 2006 swharden@gmail.com