1:05:28 am on 2/9/10
Menu
» Home
» About Scott
» Biomed
» Old Stuff
» Archive
» Contact

Categories
» C/C++
» Circuitry
» Dentistry
» DIY ECG
» General
» Linux
» Microcontrollers
» Molecular Biology
» My Website
» PHP
» Prime Numbers
» Python
» Radio
» UCF Lab
» Everything
Writings
» MD Labels
» Streamrip
» AIM Thoughts
» WindowsXP?
» Partitioning
» CD/DVD Repair
» Monitor Info
» CRT Deflection
» Venomcrack
» Flash Thing
» Heart/Brain
» Diabetes
» Triops

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




Archives
» 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

You are currently browsing the archives for the DIY ECG category.

Archive for the 'DIY ECG' Category



DIY ECG Machine On The Cheap
Posted by
Scott August 14th, 2009 | 5,253 words | 21 Comments »

Note from the Author: This page documents how I made an incredibly simple ECG machine with a minimum of parts to view the electrical activity of my own heart. Feel free to repeat my experiment, but do so at your own risk. There are similar projects floating around on the internet, but I aim to provide a more complete, well-documented, and cheaper solution, with emphasis on ECG processing and analysis, rather than just visualization. If you have any questions or suggestions please contact me. Also, if you attempt this project yourself I’d love to post your results! Good luck!
–Scott

Background

You’ve probably seen somebody in a hospital setting hooked up to a big mess of wires used to analyze their heartbeat. ecgmanThe goal of such a machine (called an electrocardiograph, or ECG) is to amplify, measure, and record the natural electrical potential created by the heart. Note that cardiac electrical signals are different than heart sounds, which are listened to with a stethoscope. The intrinsic cardiac pacemaker system is responsible for generating these electrical signals which serve to command and coordinate contraction of the four chambers at the heart at the appropriate intervals [atria (upper chambers) first, then the ventricles (lower chambers) a fraction of a second later], and their analysis reveals a wealth of information about cardiac regulation, as well insights into pathological conditions. Each heartbeat produces a similar pattern in the ECG signal, called a PQRST wave. ecg_principle_slow [picture] The smooth curve in the ECG (P) is caused by the stimulation of the atria via the Sinoatrial (SA) node in the right atrium. There is a brief pause, as the electrical impulse is slowed by the Atrioventricular (AV) node and Purkinje fibers in the bundle of His. The prominent spike in the ECG (the QRS complex) is caused by this step, where the electrical impulse travels through the inter-ventricular septum and up through the outer walls of the ventricles. The sharp peak is the R component, and exact heart rate can be calculated as the inverse of the R-to-R interval (RRi). Fancy, huh?

Project Goal

The goal of this project is to generate an extremely cheap, functional ECG machine made from common parts, most of which can be found around your house. This do-it-yourself (DIY) ECG project is different than many others on the internet in that it greatly simplifies the circuitry by eliminating noise reduction components, accomplishing this via software-based data post-processing. Additionally, this writeup is intended for those without any computer, electrical, or biomedical experience, and should be far less convoluted than the suspiciously-cryptic write-ups currently available online. In short, I want to give everybody the power to visualize and analyze their own heartbeat!

The ECG of my own heart:

ecg31

Video Overview

I know a lot of Internet readers aren’t big fans of reading. Therefore, I provided an outline of the process in video form. Check out the videos, and if you like what you see read more!

Video 1/3: Introducing my ECG machine

Video 2/3: Recording my ECG

Video 3/3: Analyzing my ECG

Electrical Theory

Measurement: The electrical signals which command cardiac musculature can be detected on the surface of the skin. In theory one could grab the two leads of a standard volt meter, one with each hand, and see the voltage change as their heart beats, but the fluctuations are rapid and by the time these signals reach the skin they are extremely weak (a few millionths of a volt) and difficult to detect with simple devices. Therefore, amplification is needed.

Amplification: A simple way to amplify the electrical difference between two points is to use a operational amplifier, otherwise known as an op-amp. The gain (multiplication factor) of an op-amp is controlled by varying the resistors attached to it, and an op-amp with a gain of 1000 will take a 1 millivolt signal and amplify it to 1 volt. There are many different types of microchip op-amps, and they’re often packaged with multiple op-amps in one chip (such as the quad-op-amp lm324, or the dual-op-amp lm358n). Any op-amp designed for low voltage will do for our purposes, and we only need one.

Noise: Unfortunately, the heart is not the only source of voltage on the skin. Radiation from a variety of things (computers, cell phones, lights, and especially the wiring in your walls) is absorbed by your skin and is measured with your ECG, in many cases masking your ECG in a sea of electrical noise. The traditional method of eliminating this noise is to use complicated analog circuitry, but since this noise has a characteristic, repeating, high-frequency wave pattern, it can be separated from the ECG (which is much slower in comparison) using digital signal processing computer software!

Digitization: Once amplified, the ECG signal along with a bunch of noise is in analog form. You could display the output with an oscilloscope, but to load it into your PC you need an analog-to-digital converter. Don’t worry! If you’ve got a sound card with a microphone input, you’ve already got one! It’s just that easy. We’ll simply wire the output of our ECG circuit to the input of our sound card, record the output of the op-amp using standard sound recording software, remove the noise from the ECG digitally, and output gorgeous ECG traces ready for visualization and analysis!

Parts/Cost

I’ll be upfront and say that I spent $0.00 making my ECG machine, because I was able to salvage all the parts I needed from a pile of old circuit boards. If you need specific components, check your local RadioShack. If that’s a no-go, hit-up Digikey (it’s probably cheaper too). Also, resistor values are flexible. Use mine as a good starter set, and vary them to suit your needs. If you buy everything from Digikey, the total cost of this project would be about $1. For now, here’s a list of all the parts you need:

  • 1x low voltage op-amp LM358N $0.40
  • 1x 100kOhm resistor (brn,blk,yel) virtually free
  • 1x 1kOhm resistor (brn,blk,red) virtually free
  • 1x 0.1uF capacitor (104Z) virtually free
  • Microphone cable to get from the op-amp to your PC
  • Electrodes 3 pennies should do. ($0.03)

Making the Device

Keep in mind that I’m not an electrical engineer (I have a masters in molecular biology but I’m currently a dental student if you must know) and I’m only reporting what worked well for me. I don’t claim this is perfect, and I’m certainly open for (and welcome) suggestions for improvement. With that in mind, here’s what I did!

img_2694

This is pretty much it. First off is a power source. If you want to be safe, use three AAA batteries in series. If you’re a daredevil and enjoy showing off your ghettorigging skills, do what I did and grab 5v from a free USB plug! Mua ha ha ha. The power goes into the circuit and so do the leads/electrodes connected to the body. You can get pretty good results with only two leads, but if you want to experiment try hooking up an extra ground lead and slap it on your foot. More on the electrodes later. The signal from the leads is amplified by the circuit and put out the headphone cable, ready to enter your PC’s sound card through the microphone jack!

img_2686

Note how I left room in the center of the circuit board. That was intentional! I wanted to expand this project by adding a microcontroller to do some on-board, real-time analysis. Specifically, an ATMega8! I never got around to it though. Its purpose would be to analyze the output of the op-amp and graph the ECG on a LCD screen, or at least measure the time between beats and display HR on a screen. (More ideas are at the bottom of this document.) Anyway, too much work for now, maybe I’ll do it one day in the future.

ECG circuit diagram:

simple_ecg_circuit

This is the circuit diagram. This is a classical high-gain analog differential amplifier. It just outputs the multiplied difference of the inputs. The 0.1uF capacitor helps stabilize the signal and reduce high frequency noise (such as the audio produced by a nearby AM radio station). Use Google if you’re interested in learning exactly how it works.

ECG schematic:

simple_ecg_circuit2

This is how I used my LM358N to create the circuit above. Note that there is a small difference in my board from the photos and this diagram. This diagram is correct, but the circuit in some of the pictures is not. Briefly, when I built it I accidentally connected the (-) lead directly to ground, rather than to the appropriate pin on the microchip. This required me to place a 220kOhm between the leads to stabilize the signal. I imagine if you wire it CORRECTLY (as shown in these circuit diagrams) it will work fine, but if you find it too finicky (jumping quickly from too loud to too quiet), try tossing in a high-impedance resistor between the leads like I did. Overall, this circuit is extremely flexible and I encourage you to build it on a breadboard and try different things. Use this diagram as a starting point and experiment yourself!

The Electrodes:

img_2704

You can make electrodes out of anything conductive. The most recent graphs were created from wires with gator clips on them clamping onto pennies (pictured). Yeah, I know I could solder directly to the pennies (they’re copper) but gator clips are fast, easy, and can be clipped to different materials (such as aluminum foil) for testing. A dot of moisturizing lotion applied to the pennies can be used to improve conduction between the pennies and the skin, but I didn’t find this to be very helpful. If pressed firmly on the body, conduction seems to be fine. Oh! I just remembered. USE ELECTRICAL TAPE TO ATTACH LEADS TO YOUR BODY! I tried a million different things, from rubber bands to packaging tape. The bottom line is that electrical tape is stretchy enough to be flexible, sticky enough not to fall off (even when moistened by the natural oils/sweat on your skin), and doesn’t hurt that bad to peel off.

Some of the best electrodes I used were made from aluminum cans! Rinse-out a soda can, cut it into “pads”, and use the sharp edge of a razor blade or pair of scissors to scrape off the wax coating on all contact surfaces. Although a little unconformable and prone to cut skin due to their sharp edges, these little guys work great!

Hooking it Up

This part is the most difficult part of the project! This circuit is extremely finicky. The best way to get it right is to open your sound editor (In Windows I use GoldWave because it’s simple, powerful, and free, but similar tools exist for Linux and other Unix-based OSes) and view the low-frequency bars in live mode while you set up. When neither electrode is touched, it should be relatively quiet. When only the + electrode is touched, it should go crazy with noise. When you touch both (one with each hand) the noise should start to go away, possibly varying by how much you squeeze (how good of a connection you have). The whole setup process is a game between too much and too little conduction. You’ll find that somewhere in the middle, you’ll see (and maybe hear) a low-frequency burst of noise once a second corresponding to your heartbeat. [note: Did you know that's how the second was invented? I believe it was ] Once you get that good heartbeat, tape up your electrodes and start recording. If you can’t get it no matter what you do, start by putting the ground electrode in your mouth (yeah, I said it) and pressing the + electrode firmly and steadily on your chest. If that works (it almost always does), you know what to look for, so keep trying on your skin. For short recordings (maybe just a few beats) the mouth/chest method works beautifully, and requires far less noise reduction (if any), but is simply impractical for long-term recordings. I inside vs. outside potential is less susceptible to noise-causing electrical radiation. Perhaps other orifices would function similarly? I’ll leave it at that. I’ve also found that adding a third electrode (another ground) somewhere else on my body helps a little, but not significantly. Don’t give up at this step if you don’t get it right away! If you hear noise when + is touched, your circuit is working. Keep trying and you’ll get it eventually.

Recording the ECG

This is the easy part. Keep an eye on your “bars” display in the audio program to make sure something you’re doing (typing, clicking, etc) isn’t messing up the recording. If you want, try surfing the net or playing computer games to see how your heart varies. Make sure that as you tap the keyboard and click the mouse, you’re not getting noise back into your system. If this is a problem, try powering your device by batteries (a good idea for safety’s sake anyway) rather than another power source (such as USB power). Record as long as you want! Save the file as a standard, mono, wave file.

Digitally Eliminating Noise

Now it’s time to clean-up the trace. Using GoldWave, first apply a lowpass filter at 30 Hz. This kills most of your electrical noise (> 30hz), while leaving the ECG intact (< 15Hz). However, it dramatically decreases the volume (potential) of the audio file. Increase the volume as necessary to maximize the window with the ECG signal. You should see clear heartbeats at this point. You may want to apply an auto-gain filter to normalize the heartbeats potentials. Save the file as a raw sound file (.snd) at 1000 Hz (1 kHz) resolution.

Presentation and Analysis

Now you’re ready to analyze! Plop your .snd file in the same folder as my [ecg.py script], edit the end of the script to reflect your .snd filename, and run the script by double-clicking it. (Keep in mind that my script was written for python 2.5.4 and requires numpy 1.3.0rc2 for python 2.5, and matplotlib 0.99 for python 2.5 – make sure you get the versions right!) Here’s what you’ll see!

diy_ecg_sample_trace

This is a small region of the ECG trace. The “R” peak is most obvious, but the details of the other peaks are not as visible. If you want more definition in the trace (such as the blue one at the top of the page), consider applying a small collection of customized band-stop filters to the audio file rather than a single, sweeping lowpass filter. Refer to earlier posts in the DIY ECG category for details. Specifically, code on Circuits vs. Software for noise reduction entry can help. For our purposes, calculating heart rate from R-to-R intervals (RRIs) can be done accurately with traces such as this.

diy_ecg_heart_rate_over_time

Your heart rate fluctuates a lot over time! By plotting the inverse of your RRIs, you can see your heart rate as a function of time. Investigate what makes it go up, go down, and how much. You’d be surprised by what you find. I found that checking my email raises my heart rate more than first-person-shooter video games. I get incredibly anxious when I check my mail these days, because I fear bad news from my new university (who knows why, I just get nervous about it). I wonder if accurate RRIs could be used to assess nervousness for the purposes of lie detection?

diy_ecg_rr_beat_interval

This is the RRI plot where the value of each RRI (in milliseconds) is represented for each beat. It’s basically the inverse of heart rate. Miscalculated heartbeats would show up as extremely high or extremely low dots on this graph. However, excluding points above or below certain bounds means that if your heart did double-beat, or skip a beat, you wouldn’t see it. Note that I just realized my axis label is wrong (it should be sec, not ms). Oh well =o\

diy_ecg_poincare_plot

A Poincare Plot is a commonly-used method to visually assess heart rate variability as a function of RRIs. In this plot, each RRI is plotted against the RRI of the next subsequent beat. In a heart which beats at the same speed continuously, only a single dot would be visible in the center. In a heart which beats mostly-continuously, and only changes its rate very slowly, a linear line of dots would be visible in a 1:1 ratio. However, in real life the heart varies RRIs greatly from beat to beat, producing a small cloud of dots. The size of the cloud corresponds to the speed at which the autonomic nervous system can modulate heart rate in the time frame of a single beat.

diy_ecg_rr_deviation_histogram

The frequency of occurrence of various RRIs can be expressed by a histogram. The center peak corresponds to the standard heart rate. Peaks to the right and left of the center peak correspond to increased and decreased RRIs, respectively. A gross oversimplification of the interpretation of such data would be to state that the upper peak represents the cardio-inhibitory parasympathetic autonomic nervous system component, and the lower peak represents the cardio-stimulatory sympathetic autonomic nervous system component.

diy_ecg_power_spectrum_raw

Taking the Fast Fourier Transformation of the data produces a unique trace whose significance is extremely difficult to interpret. Near 0Hz (infinite time) the trace heads toward ∞ (infinite power). To simplify the graph and eliminate the near-infinite, low-frequency peak we will normalize the trace by multiplying each data point by its frequency, and dividing the vertical axis units by Hz to compensate. This will produce the following graph…

diy_ecg_power_spectrum_weighted
This is the power spectrum density (PSD) plot of the ECG data we recorded. Its physiological interpretation is extraordinarily difficult to understand and confirm, and is the subject of great debate in the field of autonomic neurological cardiac regulation. An oversimplified explanation of the significance of this graph is that the parasympathetic (cardio-inhibitory) branch of the autonomic nervous system works faster than the sympathetic (cardio-stimulatory) branch. Therefore, the lower peak corresponds to the sympathetic component (combined with persistent parasympathetic input, it’s complicated), while the higher-frequency peak corresponds to the parasympathetic component, and the sympathetic/parasympathetic relationship can be assessed by the ratio of the integrated areas of these peaks after a complicated curve fitting processes which completely separates overlapping peaks. To learn more about power spectral analysis of heart rate over time in the frequency domain, I recommend skimming this introduction to heart rate variability website and the article on Heart Rate Variability following Myocardial Infarction (heart attack). Also, National Institute of Health (NIH) funded studies on HRV should be available from pubmed.org. If you want your head to explode, read Frequency-Domain Characteristics and Filtering of Blood Flow Following the Onset of Exercise: Implications for Kinetics Analysis for a lot of good frequency-domain-analysis-related discussion and rationalization.

Encouraging Words:

Please, if you try this don’t die. The last thing I want is to have some kid calling me up and yelling at me that he nearly electrocuted himself when he tried to plug my device directly into a wall socket and now has to spend the rest of his life with two Abraham Lincolns tattooed onto his chest resembling a second set of nipples. Please, if you try this use common sense, and of course you’re responsible for your own actions. I provide this information as a description of what I did and what worked for me. If you make something similar that works, I’ve love to see it! Send in your pictures of your circuit, charts of your traces, improved code, or whatever you want and I’ll feature it on the site. GOOD LUCK!

Fancier Circuit:

If you want to try this, go for it! Briefly, this circuit uses 6 op-amps to help eliminate effects of noise. It’s also safer, because of the diodes interconnecting the electrodes. It’s the same circuit as on [this page].

Last minute thoughts:

  • More homemade ECG information can be found on my earlier posts in the DIY ECG category, however this page is the primary location of my most recent thoughts and ideas.
  • You can use moisturizing lotion between the electrodes and your skin to increase conduction. However, keep in mind that better conduction is not always what you want. You’ll have to experiment for yourself.
  • Variation in location of electrodes will vary the shape of the ECG. I usually place electrodes on each side of my chest near my arms. If your ECG appears upside-down, reverse the leads!
  • Adding extra leads can improve grounding. Try grounding one of your feet with a third lead to improve your signal. Also, if you’re powering your device via USB power consider trying battery power – it should be less noisy.
  • While recording, be aware of what you do! I found that if I’m not well-grounded, my ECG is fine as long as I don’t touch my keyboard. If I start typing, every keypress shows up as a giant spike, bigger than my heartbeat!
  • If you get reliable results, I wonder if you could make the device portable? Try using a portable tape recorder, voice recorder, or maybe even minidisc recorder to record the output of the ECG machine for an entire day. I haven’t tried it, but why wouldn’t it work? If you want to get fancy, have a microcontroller handle the signal processing and determine RRIs (should be easy) and save this data to a SD card or fancy flash logger.
  • The microcontroller could output heart rate via the serial port.
  • If you have a microcontroller on board, why not display heart rate on a character LCD?
  • While you have a LCD on there, display the ECG graphically!
  • Perhaps a wireless implementation would be useful.
  • Like, I said, there are other, more complicated analog circuits which reduce noise of the outputted signal. I actually built Jason Nguyen’s fancy circuit which used 6 op-amps but the result wasn’t much better than the simple, 1 op-amp circuit I describe here once digital filtering was applied.
  • Arrhythmic heartbeats (where your heart screws-up and misfires, skips a beat, double-beats, or beats awkwardly) are physiological (normal) and surprisingly common. Although shocking to hear about, sparse, single arrhythmic heartbeats are normal and are a completely different ball game than chronic, potentially deadly heart arrhythmias in which every beat is messed-up. If you’re in tune with your body, you might actually feel these occurrences happening. About three times a week I feel my heart screw up a beat (often when it’s quiet), and it feels like a sinking feeling in my chest. I was told by a doctor that it’s totally normal and happens many times every day without me noticing, and that most people never notice these single arrhythmic beats. I thought it was my heart skipping a beat, but I wasn’t sure. That was my motivation behind building this device – I wanted to see what my arrhythmic beats looked like. It turns out that it’s more of a double-beat than a skipped beat, as observed when I captured a single arrhythmic heartbeat with my ECG machine, as described in this entry.
  • You can improve the safety of this device by attaching diodes between leads, similar to the more complicated circuit. Theory is that if a huge surge of energy does for whatever reason get into the ECG circuit, it’ll short itself out at the circuit level (conducting through the diodes) rather than at your body (across your chest / through your heart).
  • Alternatively, use an AC opto-isolator between the PC sound card and the ECG circuit to eliminate the possibility of significant current coming back from the PC.
  • On the Hackaday post, Flemming Frandsen noted that an improperly grounded PC could be dangerous because the stored charge would be manifest in the ground of the microphone jack. If you were to ground yourself to true ground (using a bench power supply or sticking your finger in the ground socket of an AC wall plug) this energy could travel through you! So be careful to only ground yourself with respect to the circuit using only battery power to minimize this risk.
  • Do not attempt anything on this page. Ever. Don’t even read it. You read it already! You’re sill reading it aren’t you? Yeah. You don’t follow directions well do you?

SAMPLE FILTERED RECORDING:

I think this is the same one I used in the 3rd video from my single op-amp circuit. [scottecg.snd] It’s about an hour long, and in raw sound format (1000 Hz). It’s already been filtered (low-pass filtered at 30Hz). You can use it with my code below!

CODE

print "importing libraries..."
import numpy, pylab
print "DONE"

class ECG:

    def trim(self, data,degree=100):
        print 'trimming'
        i,data2=0,[]
        while i<len(data):
            data2.append(sum(data[i:i+degree])/degree)
            i+=degree
        return data2

    def smooth(self,list,degree=15):
        mults=[1]
        s=[]
        for i in range(degree): mults.append(mults[-1]+1)
        for i in range(degree): mults.append(mults[-1]-1)
        for i in range(len(list)-len(mults)):
            small=list[i:i+len(mults)]
            for j in range(len(small)):
                small[j]=small[j]*mults[j]
            val=sum(small)/sum(mults)
            s.append(val)
        return s

    def smoothWindow(self,list,degree=10):
        list2=[]
        for i in range(len(list)):
            list2.append(sum(list[i:i+degree])/float(degree))
        return list2

    def invertYs(self):
        print 'inverting'
        self.ys=self.ys*-1

    def takeDeriv(self,dist=5):
        print 'taking derivative'
        self.dys=[]
        for i in range(dist,len(self.ys)):
            self.dys.append(self.ys[i]-self.ys[i-dist])
        self.dxs=self.xs[0:len(self.dys)]

    def genXs(self, length, hz):
        print 'generating Xs'
        step = 1.0/(hz)
        xs=[]
        for i in range(length): xs.append(step*i)
        return xs

    def loadFile(self, fname, startAt=None, length=None, hz=1000):
        print 'loading',fname
        self.ys = numpy.memmap(fname, dtype='h', mode='r')*-1
        print 'read %d points.'%len(self.ys)
        self.xs = self.genXs(len(self.ys),hz)
        if startAt and length:
            self.ys=self.ys[startAt:startAt+length]
            self.xs=self.xs[startAt:startAt+length]

    def findBeats(self):
        print 'finding beats'
        self.bx,self.by=[],[]
        for i in range(100,len(self.ys)-100):
          if self.ys[i]<15000: continue # SET THIS VISUALLY
          if self.ys[i]<self.ys[i+1] or self.ys[i]<self.ys[i-1]: continue
          if self.ys[i]-self.ys[i-100]>5000 and self.ys[i]-self.ys[i+100]>5000:
              self.bx.append(self.xs[i])
              self.by.append(self.ys[i])
        print "found %d beats"%(len(self.bx))

    def genRRIs(self,fromText=False):
        print 'generating RRIs'
        self.rris=[]
        if fromText: mult=1
        else: 1000.0
        for i in range(1,len(self.bx)):
            rri=(self.bx[i]-self.bx[i-1])*mult
            #if fromText==False and len(self.rris)>1:
                #if abs(rri-self.rris[-1])>rri/2.0: continue
            #print i, "%.03f\t%.03f\t%.2f"%(bx[i],rri,60.0/rri)
            self.rris.append(rri)

    def removeOutliers(self):
        beatT=[]
        beatRRI=[]
        beatBPM=[]
        for i in range(1,len(self.rris)):
            #CHANGE THIS AS NEEDED
            if self.rris[i]<0.5 or self.rris[i]>1.1: continue
            if abs(self.rris[i]-self.rris[i-1])>self.rris[i-1]/5: continue
            beatT.append(self.bx[i])
            beatRRI.append(self.rris[i])
        self.bx=beatT
        self.rris=beatRRI

    def graphTrace(self):
        pylab.plot(self.xs,self.ys)
        #pylab.plot(self.xs[100000:100000+4000],self.ys[100000:100000+4000])
        pylab.title("Electrocardiograph")
        pylab.xlabel("Time (seconds)")
        pylab.ylabel("Potential (au)")

    def graphDeriv(self):
        pylab.plot(self.dxs,self.dys)
        pylab.xlabel("Time (seconds)")
        pylab.ylabel("d/dt Potential (au)")

    def graphBeats(self):
        pylab.plot(self.bx,self.by,'.')

    def graphRRIs(self):
        pylab.plot(self.bx,self.rris,'.')
        pylab.title("Beat Intervals")
        pylab.xlabel("Beat Number")
        pylab.ylabel("RRI (ms)")

    def graphHRs(self):
        #HR TREND
        hrs=(60.0/numpy.array(self.rris)).tolist()
        bxs=(numpy.array(self.bx[0:len(hrs)])/60.0).tolist()
        pylab.plot(bxs,hrs,'g',alpha=.2)
        hrs=self.smooth(hrs,10)
        bxs=bxs[10:len(hrs)+10]
        pylab.plot(bxs,hrs,'b')
        pylab.title("Heart Rate")
        pylab.xlabel("Time (minutes)")
        pylab.ylabel("HR (bpm)")

    def graphPoincare(self):
        #POINCARE PLOT
        pylab.plot(self.rris[1:],self.rris[:-1],"b.",alpha=.5)
        pylab.title("Poincare Plot")
        pylab.ylabel("RRI[i] (sec)")
        pylab.xlabel("RRI[i+1] (sec)")

    def graphFFT(self):
        #PSD ANALYSIS
        fft=numpy.fft.fft(numpy.array(self.rris)*1000.0)
        fftx=numpy.fft.fftfreq(len(self.rris),d=1)
        fftx,fft=fftx[1:len(fftx)/2],abs(fft[1:len(fft)/2])
        fft=self.smoothWindow(fft,15)
        pylab.plot(fftx[2:],fft[2:])
        pylab.title("Raw Power Sprectrum")
        pylab.ylabel("Power (ms^2)")
        pylab.xlabel("Frequency (Hz)")

    def graphFFT2(self):
        #PSD ANALYSIS
        fft=numpy.fft.fft(numpy.array(self.rris)*1000.0)
        fftx=numpy.fft.fftfreq(len(self.rris),d=1)
        fftx,fft=fftx[1:len(fftx)/2],abs(fft[1:len(fft)/2])
        fft=self.smoothWindow(fft,15)
        for i in range(len(fft)):
            fft[i]=fft[i]*fftx[i]
        pylab.plot(fftx[2:],fft[2:])
        pylab.title("Power Sprectrum Density")
        pylab.ylabel("Power (ms^2)/Hz")
        pylab.xlabel("Frequency (Hz)")

    def graphHisto(self):
        pylab.hist(self.rris,bins=20,ec='none')
        pylab.title("RRI Deviation Histogram")
        pylab.ylabel("Frequency (count)")
        pylab.xlabel("RRI (ms)")
        #pdf, bins, patches = pylab.hist(self.rris,bins=100,alpha=0)
        #pylab.plot(bins[1:],pdf,'g.')
        #y=self.smooth(list(pdf[1:]),10)
        #x=bins[10:len(y)+10]
        #pylab.plot(x,y)

    def saveBeats(self,fname):
        print "writing to",fname
        numpy.save(fname,[numpy.array(self.bx)])
        print "COMPLETE"

    def loadBeats(self,fname):
        print "loading data from",fname
        self.bx=numpy.load(fname)[0]
        print "loadded",len(self.bx),"beats"
        self.genRRIs(True)

def snd2txt(fname):
    ## SND TO TXT ##
    a=ECG()
    a.loadFile(fname)#,100000,4000)
    a.invertYs()
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphTrace()
    a.findBeats()
    a.graphBeats()
    a.saveBeats(fname)
    pylab.show()

def txt2graphs(fname):
    ## GRAPH TXT ##
    a=ECG()
    a.loadBeats(fname+'.npy')
    a.removeOutliers()
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphHRs();pylab.subplots_adjust(left=.1,bottom=.12,right=.96)
    pylab.savefig("DIY_ECG_Heart_Rate_Over_Time.png");
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphFFT();pylab.subplots_adjust(left=.13,bottom=.12,right=.96)
    pylab.savefig("DIY_ECG_Power_Spectrum_Raw.png");
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphFFT2();pylab.subplots_adjust(left=.13,bottom=.12,right=.96)
    pylab.savefig("DIY_ECG_Power_Spectrum_Weighted.png");
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphPoincare();pylab.subplots_adjust(left=.1,bottom=.12,right=.96)
    pylab.savefig("DIY_ECG_Poincare_Plot.png");
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphRRIs();pylab.subplots_adjust(left=.1,bottom=.12,right=.96)
    pylab.savefig("DIY_ECG_RR_Beat_Interval.png");
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphHisto();pylab.subplots_adjust(left=.1,bottom=.12,right=.96)
    pylab.savefig("DIY_ECG_RR_Deviation_Histogram.png");
    pylab.show();

fname='publish_05_10min.snd' #CHANGE THIS AS NEEDED
#raw_input("\npress ENTER to analyze %s..."%(fname))
snd2txt(fname)
#raw_input("\npress ENTER to graph %s.npy..."%(fname))
txt2graphs(fname)


Defibrillating My DIY ECG Project
Posted by
Scott August 6th, 2009 | 5,253 words | No Comments »

I’ve done a lot of random things the last few months, but few things were as random, cool, or googled-for as my Do-It-Yourself Electrocardiography project . My goal was to produce an effective ECG machine which interfaced the computer sound card for as little cost as possible. I started out small with an extremely simple circuit which technically worked, but required a lot of custom-written software to do a ton of math to decipher the ECG signal from the noise (such as inverse fast flourier transformations after band-stopping several bands of predictable, high-frequency noise). I later started building more complicated circuits in an attempt to minimize the noise, which worked well but were much more difficult to construct. For some reason, my nice ECG circuit died (burned? broke? don’t know why) right after I started to actually generate useful data about my occasional double-beats (which apparently are common, normal, and even expected during basal physiological states).
UPDATE: [2am, nextday] Here’s some video of the prototype briefly demonstrating the concept of how to use a minimum of parts to generate a great ECG trace using digital signal processing on the PC side.

simple_ecg_circuit_output

I’ve decided to revitalize this project quickly and effectively, going back to its roots and focusing on cost-minimal solutions, and using software (rather than complicated analog circuitry) to eliminate the noise. This will be a beautiful marriage of biomedical analog circuitry with software-based processing and linear data analysis, all on the cheap. If there were ever a project that represented my early 20s life, this would be it. Briefly, I built a circuit with only 3 components (!) which produces extraordinary results (above). That’s the signal after minimal processing.

Check it out yourself! I’ll provide data file for this trace (snd2.zip) along with the Python code to graph it (below) which requires numpy and matplotlib in addition to the Python scripting language. I’ll post the circuity along with some more intricate code when my project progresses a little further.

import numpy, pylab

def trim(data,degree=100):
    i,data2=0,[]
    while i&ltlen(data):
        data2.append(sum(data[i:i+degree])/degree)
        i+=degree
    return data2

def genXs(length,trim=100,hz=44100):
    step = 1.0/(hz/trim)
    xs=[]
    for i in range(length):
        xs.append(step*i)
    return xs

data = numpy.memmap("ecg2.snd", dtype='h', mode='r')
data = trim(data)
pylab.grid(alpha=.2)
pylab.plot(genXs(len(data)),data)
pylab.title("Simplified ECG Circuit Output")
pylab.xlabel("Time (seconds)")
pylab.ylabel("Potential (Au)")
pylab.show()


Losing… Energy… Fast…
Posted by
Scott January 28th, 2009 | 5,253 words | No Comments »

Only a couple weeks into my self-induced crunch time (caused by the fact my thesis work is supposed to be completed in less than a month) I’m beginning to feel a little overwhelmed. I still don’t feel it’s impossible, but I do feel like I’m making things better than they actually are in my mind. In what free time I do have, I work on side projects (like my homemade ECG machine). Last night I recorded myself sleeping. In the middle of the night I woke up and the electrodes were incredibly uncomfortable, so I ripped them off my chest and arms (ouch!) and threw them on the floor and went back to sleep. I figured that I had at least several hours’ worth of data, so I’d be fine. The next morning when I woke up I examined the recordings, and apparently I had fallen asleep in under a minute, slept for 29 minutes, then woke up and (assuming it was several hours later) ripped off the electrodes. That’s it! A mere 29 minutes of recording. One day I’ll get my whole sleep cycle recorded. Just you wait.

Here’s a little visual goodie from that recording. This data was processed in a new way – no Fourier transformation! I integrated the original trace, smoothed it 3 times, identified peaks, and used the inverse peak-to-peak distance to determine heart rate. The trace itself is not beautiful (see photo and compare it with previous traces) but if I’ll I’m determining is heartrate, it works! If there’s a weird problem (missed beats, double beats, etc) it will show up as a spike on the heartrate trace and then I can use better tools to identify the trouble in the region affected.

Okay, it’s about time I go do something. I’m in the laboratory right now (go fig) but surprisingly I can’t do a whole lot. I just got some new chemicals in today and am performing a trial run which will be completed tomorrow. Until then, I just have to wait for 18 hours for a reaction to complete. Maybe I’ll browse DA or try to draw something groovy in inkscape. See ya!



Signal Filtering with Python
Posted by
Scott January 21st, 2009 | 5,253 words | 3 Comments »

It’s time for a lecture. I’ve been spending a lot of time creating a DIY dlectrocardiogram and it produces fairly noisy signals. I’ve spent some time and effort researching the best ways to clean-up these signals, and the results are incredibly useful! Therefore, I’ve decided to lightly document these results in a blog entry.

Here’s an example of my magic! I take a noisy recording and turn it into a beautiful trace. See the example figure with the blue traces. How is this possible? Well I’ll explain it for you. Mostly, it boils down to eliminating excess high-frequency sine waves which are in the original recording due to electromagnetic noise. A major source of noise can be from the alternating current passing through wires traveling through the walls of your house or building. My original ECG circuit was highly susceptible to this kind of interference, but my improved ECG circuit eliminates most of this noise. However, noise is still in the trace (see the figure to the left), and it needed to be removed.

The key is the FFT (Fast Fourier Transformation) algorithm which can get pretty intimidating to research at first! I’ll simplify this process. Let’s say you have a trace with repeating sine-wave-shaped noise. The output of the FFT transformation of the signal is the breakdown of the signal by frequency. Check out this FFT trace of a noisy signal from a few posts ago (top graph). High peaks represent frequencies which are common. See the enormous peak around 60 Hz? (Hz means “per second” by the way, so 60 Hz is a sine wave that repeats 60 times a second) That’s from my AC noise. Other peaks (shown in colored bands) are other electromagnetic noise sources, such as wireless networks, TVs, telephones, and your computer processor. The heart produces changes in electricity that are very slow (the heartbeat is about 1 Hz, or 1 beat per second), so if we can eliminate all of the sine waves with frequencies higher than what we want to isolate we can get a pretty clear trace. This is called a band-stop filter (we block-out certain bands of frequencies) A band-pass filter is the opposite, where we only allow frequencies which are below (low-pass) or above (high-pass) a given frequency. By eliminating each of the peaks in the colored regions (setting each value to 0), then performing an inverse fast Fourier transformation (going backwards from frequency back to time), the result is the signal trace (seen as light gray on the bottom graph) with those high-frequency sine waves removed! (the gray trace on the bottom graph). A little touch-up smoothing makes a great trace (black trace on the bottom graph).

Here’s some Python code to get you started in cleaning-up your noisy signals! The image below is the output of the Python code at the bottom of this entry. This python file requires that test.wav (~700kb) (an actual ECG recording of my heartbeat) be saved in the same folder. Brief descriptions of each portion of the graph will follow.

(A) The original signal we want to isolate. (IE: our actual heart signal)

(B) Some electrical noise. (3 sine waves of different amplitudes and periods)

(C) Electrical noise (what happens when you add those 3 sine waves together)

(D) Static (random noise generated by a random number generator)

(E) Signal (A) plus static (D)

(F) Signal (A) plus static (D) plus electrical noise (C)

(G) Total FFT trace of (F). Note the low frequency peak due to the signal and electrical noise (near 0) and the high frequency peak due to static (near 10,000)

(H) This is a zoomed-in region of (F) showing 4 peaks (one for the original signal and 3 for high frequency noise). By blocking-out (set it to 0) everything above 10Hz (red), we isolate the peak we want (signal). This is a low-pass filter.

(I) Performing an inverse FFT (iFFT) on the low-pass iFFT, we get a nice trace which is our original signal!

(J) Comparison of our iFFT with our original signal shows that the amplitude is kinda messed up. If we normalize each of these (set minimum to 0, maximum to 1) they line up. Awesome!

(K) How close were we? Graphing the difference of iFFT and the original signal shows that usually we’re not far off. The ends are a problem though, but if our data analysis trims off these ends then our center looks great.

Here’s the code I used to make the image:

 import numpy, scipy, pylab, random  

 # This script demonstrates how to use band-pass (low-pass)  
 # filtering to eliminate electrical noise and static  
 # from signal data!  

 ##################  
 ### PROCESSING ###  
 ##################  

 xs=numpy.arange(1,100,.01) #generate Xs (0.00,0.01,0.02,0.03,...,100.0)  
 signal = sin1=numpy.sin(xs*.3) #(A)  
 sin1=numpy.sin(xs) # (B) sin1  
 sin2=numpy.sin(xs*2.33)*.333 # (B) sin2  
 sin3=numpy.sin(xs*2.77)*.777 # (B) sin3  
 noise=sin1+sin2+sin3 # (C)  
 static = (numpy.random.random_sample((len(xs)))-.5)*.2 # (D)  
 sigstat=static+signal # (E)  
 rawsignal=sigstat+noise # (F)  
 fft=scipy.fft(rawsignal) # (G) and (H)  
 bp=fft[:]  
 for i in range(len(bp)): # (H-red)  
     if i>=10:bp[i]=0  
 ibp=scipy.ifft(bp) # (I), (J), (K) and (L)  

 ################  
 ### GRAPHING ###  
 ################  

 h,w=6,2  
 pylab.figure(figsize=(12,9))  
 pylab.subplots_adjust(hspace=.7)  

 pylab.subplot(h,w,1);pylab.title("(A) Original Signal")  
 pylab.plot(xs,signal)  

 pylab.subplot(h,w,3);pylab.title("(B) Electrical Noise Sources (3 Sine Waves)")  
 pylab.plot(xs,sin1,label="sin1")  
 pylab.plot(xs,sin2,label="sin2")  
 pylab.plot(xs,sin3,label="sin3")  
 pylab.legend()  

 pylab.subplot(h,w,5);pylab.title("(C) Electrical Noise (3 sine waves added together)")  
 pylab.plot(xs,noise)  

 pylab.subplot(h,w,7);pylab.title("(D) Static (random noise)")  
 pylab.plot(xs,static)  
 pylab.axis([None,None,-1,1])  

 pylab.subplot(h,w,9);pylab.title("(E) Signal + Static")  
 pylab.plot(xs,sigstat)  

 pylab.subplot(h,w,11);pylab.title("(F) Recording (Signal + Static + Electrical Noise)")  
 pylab.plot(xs,rawsignal)  

 pylab.subplot(h,w,2);pylab.title("(G) FFT of Recording")  
 fft=scipy.fft(rawsignal)  
 pylab.plot(abs(fft))  
 pylab.text(200,3000,"signals",verticalalignment='top')  
 pylab.text(9500,3000,"static",verticalalignment='top',  
            horizontalalignment='right')  

 pylab.subplot(h,w,4);pylab.title("(H) Low-Pass FFT")  
 pylab.plot(abs(fft))  
 pylab.text(17,3000,"sin1",verticalalignment='top',horizontalalignment='left')  
 pylab.text(37,2000,"sin2",verticalalignment='top',horizontalalignment='center')  
 pylab.text(45,3000,"sin3",verticalalignment='top',horizontalalignment='left')  
 pylab.text(6,3000,"signal",verticalalignment='top',horizontalalignment='left')  
 pylab.axvspan(10,10000,fc='r',alpha='.5')  
 pylab.axis([0,60,None,None])  

 pylab.subplot(h,w,6);pylab.title("(I) Inverse FFT")  
 pylab.plot(ibp)  

 pylab.subplot(h,w,8);pylab.title("(J) Signal vs. iFFT")  
 pylab.plot(signal,'k',label="signal",alpha=.5)  
 pylab.plot(ibp,'b',label="ifft",alpha=.5)  

 pylab.subplot(h,w,10);pylab.title("(K) Normalized Signal vs. iFFT")  
 pylab.plot(signal/max(signal),'k',label="signal",alpha=.5)  
 pylab.plot(ibp/max(ibp),'b',label="ifft",alpha=.5)  

 pylab.subplot(h,w,12);pylab.title("(L) Difference / Error")  
 pylab.plot(signal/max(signal)-ibp/max(ibp),'k')  

 pylab.savefig("SIG.png",dpi=200)  
 pylab.show()  
 


DIY ECG Detected an Irregular Heartbeat
Posted by
Scott January 20th, 2009 | 5,253 words | 3 Comments »

Am I going to dlie? It’s unlikely. Upon analyzing ~20 minutes of heartbeat data (some of which is depicted in the previous entry) I found a peculiarity. Technically this could be some kind of noise (a ‘pop’ in the microphone signal due to the shuffling of wires or a momentary disconnect from the electrodes or perhaps even a static shock to my body from something), but because this peculiarity happened only once in 20 minutes I’m not ruling out the possibility that this is the first irregular heartbeat I captured with my DIY ECG. Note that single-beat irregularities are common, and that this does not alarm me so much as fascinates me. Below is the section of the data which contains this irregular beat.

In the spirit of improvement I wonder how much more interesting this project would be if I were to combine the already-designed ECG machine with a sensor to detect the physical effect of the heart’s beating on my vasculature. in other words, can I combine my electrical traces with physical traces? (Blood pressure or blood flow) I found an interesting site that shows how someone built a DIY blood flow meter using a piezo film pulse sensor. Pretty clever I must say… but I think I draw my limit at what I’ve done. Although blood flow would be interesting to analyze (does the murmur depicted above produce an alteration in normal blood flow?), it’s not worth the time, hassle or expense of building.



DIY ECG Improvements
Posted by
Scott January 20th, 2009 | 5,253 words | 1 Comment »

No 3-day weekend would be complete without a project that’s, well, virtually useless. I present to you my new and improved ECG machine! Instead of using a single op-amp circuit like the previous entries which gave me decent but staticky traces, I decided to build a more advanced ECG circuit documented by Jason Nguyen which boasted 6 op amps! (I’d only been using one) Luckily I picked up a couple LM 324 quad op amp chips at radioshack for about $1.40 each, so I had everything I needed. I’ll skip to the results. In short, they’re gorgeous. Noise is almost nothing, so true details of the trace are visible. I can now clearly see the P-Q-R-S-T features in the wave (before the P was invisible). I’ll detail how I did this in a later entry. For now, here are some photos of the little device and a video I uploaded to YouTube. It’s not fancy.

UPDATE: Upon analyzing ~20 minutes of heartbeat data I found a peculiarity. Technically this could be some kind of noise (a ‘pop’ in the microphone signal due to the shuffling of wires or a momentary disconnect from the electrodes or perhaps even a static shock to my body from something), but because this peculiarity happened only once in 20 minutes I’m not ruling out the possibility that this is the first irregular heartbeat I captured with my DIY ECG. Note that single-beat irregularities are common, and that this does not alarm me so much as fascinates me. Below is the section of the data which contains this irregular beat.



DIY ECG Progress
Posted by
Scott January 16th, 2009 | 5,253 words | No Comments »

Last night I finished building my DIY ECG as a prototype (I finally got the circuit off the breadboard and onto a plastic sheet). This is a similar circuit to the one used to record data from the last entry (resister values are now identical to the crude circuit described several posts ago). I left-in the crude band-pass filter (made by grounding my primary electrode sensor through a 0.1µF capacitor) because it seemed to help a great deal, and wasn’t hard to implement. I picked up all of my parts (including the LF324 quad-op-amp microchip) at RadioShack. Of note, the quad-op-amp is overkill because I’m only using one of the 4 op-amps. Theoretically I could add 3 more electrodes to this circuit (which would allow for multi-sensor recording, similar to the electrodes placed at http://en.wikipedia.org/wiki/File:Limb_leads.svg) but this would require multiple microphone jacks, which isn’t very common. I guess I could use 2 microphone jacks, and differentiate right/left channels. Anyway, here are some photos.


I made the prototype by drilling holes in a small rectangular piece of a non-conductive plastic-fiberish material. (I picked up a stack of these rectangular sections for a quarter at a local electrical surplus store and they’re perfect for prototyping). The two green wires coming out the left side attach to a ~5v power supply (either a plugged in AC->DC transformer, 3 or 4 AA batteries, or even a 9V should work). The blue wires on the right attach to the electrodes I put on my chest. The black wires go to a headphone-jack which I plug into the microphone hole of my PC to record the signal.


This is the back of the device which shows my crummy soldering technique. Let it slide folks, I’m a molecular biology not an electrical engineer. Anyhow, basic point-to-point contacts, nothing special. The white/yellow wires correspond to the left/right channels of the microphone connector. I only use the left one (white), but attached the right channel (yellow) to the op-amp just in case I decide to add another sensor later – this is not required.


Here’s the full shabang! You can clearly see the circuit (note its small size – easy to mount inside of a tictac box or something) with the green wires leading to a power supply, black cable being the microphone connector, and the blue wires leading to electrodes made… from… fanta… cans…? Yes, in the spirit of ghetto-rigging electronics (my specialty) I found that surprisingly nice chest electrodes can be made from aluminum soda cans! If you go this route, cut them delicately so you don’t get metal shards in your skin like I did at first. Also, note that you have to firmly scrape each side of the aluminum to get the insulating waxy-plastic stuff off or it just won’t work. (I guess it’s coated with something to prevent the soda from tasting like metal.) Aluminum rapidly transfers heat, so it’s nearly impossible to solder leads onto these pads, so I wrapped a wire (tipped with a bead of solder) with the edge of the aluminum several times and crushed the heck out of it with pliers and it seems to stay on well and make a good connection. Also, before taping these onto your skin, you have to put a highly-conductive goo on it to make the connection better. I chose to use a copious squirt of my wife’s skin moisturizer on each electrode (shh, don’t tell her!) and taped the gooey electrode directly onto my chest.

I recorded ~20 minutes of data last night with this rig and it looked pretty good. I went to analyze it with Python and it kept crashing! The python script I gave you previously loads the entire wave file into an array of numbers, but with a 20 minute wave file (at over 40,000 samples a second) this is just too big for memory. I wrote an updated wave loader function which loads large wave files in parts which is much more efficient. It also performs the condensation method at load time. Basically, it loads 100 data points (or whatever deg is set to), averages them, and adds this value to a list. The result is a smoothed trace with a resolution of ~400Hz instead of ~40,000Hz. I’d apply it to my wave file I recorded last night but it’s on my laptop which is in the car and I’ve got to get back to work. Here’s that function:

 def loadWav(fname,deg=100):  
     global hz  
     w=wave.open(fname)  
     nchannel, width, rate, length, comptype, compname = w.getparams()  
     print "[%s] 
 rate: %d Hz 
 frames: %d 
 length: %.02f sec" %  
           (fname, rate, length, float(length)/rate)  
     hz=rate/deg  
     chunks=int(length/deg)  
     data=[]  
     for i in range(chunks):  
         if i%7777==0:  
             print "processing chunk %d of %d (%0.02f%%)" %  
                   (i,chunks,100.0*i/chunks)  
         vals = struct.unpack("%sh" %deg,w.readframes(deg))  
         data.append(sum(vals)/float(deg))  
     print "complete!"  
     return data  
 


Circuits vs. Software
Posted by
Scott January 15th, 2009 | 5,253 words | No Comments »

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"  
 


DIY ECG Trial 1
Posted by
Scott January 15th, 2009 | 5,253 words | 1 Comment »

I’ve succeeded in building my own electrocardiograph (ECG) to record the electrical activity of my own heart! Briefly, I built a micropotential amplifier using an op-amp and attached it to makeshift electrodes on my chest (pennies and shampoo lol), fed the amplified signal into my sound card, and recorded it as a WAV. The signal is INCREDIBLY noisy though. I was able to do a great job at removing this noise using band/frequency filters in GoldWave (audio editing software designed to handle WAV files). I band-blocked 50-70 Hz (which removed the oscillations from the 60 Hz AC lines running around my apartment). I then wrote the Python code (at the bottom of this entry) to load this WAV file as a single list of numbers (voltage potentials). I performed a data condensation algorithm (converting 100 points of raw WAV data into a single, averaged point, lessening my processing load by 100x), followed by two consecutative moving window averages (20-point window, performed on the condensed data). The result was a voltage reading that had most of the random interference oscillations removed and, behold, a BEAUTIFUL ECG signal!!! I also tossed in some code to determine the peak of the R wave, label it (yellow dot), and use the inverse R-R time distance to calculate heart rate. Nifty, huh?

I swear, this is legitimate data that I recorded myself tonight! This is my actual ECC signal as record by a circuit similar to the one in the previous entry, recorded through my sound card, and processed with the Python script below. Incredible, huh? You can clearly see the Q, R, S, and T spikes (described in the previous couple entries)! I can’t wait to solder-up a prototype (it’s currently breadboarded) and try to analyze hours of data rather than just a few seconds! I’ll take pictures of this device soon. Until then, here’s some real data!

This is a zoomed-in view of a representative QRST wave of mine. Amazing, huh? I’m proud of my ticker! =o)

This is the output of the python script I wrote tonight

And here’s the code I used: note that it relies on the WAV file I recorded – if you want it just ask me for it and I’ll email it to you – it’s about 12 sec long. Oh yeah, not all of the functions in this script are used to create the image (such as integration calculations), but I left ‘em in because you may find them useful.

 import wave, struct, numpy, pylab, scipy  

 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  

 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):  
     """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]  
     return smooth  

 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='./wavs/bandpassed.wav'):  
     """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  

 Xs,Ys=loadwav()  
 px,py,pHR=getPoints(Xs,Ys)  

 pylab.figure(figsize=(12,6))  
 pylab.subplot(2,1,1)  
 #pylab.axhline(color='.4',linestyle=':')  
 pylab.plot(Xs,Ys,'b-')  
 pylab.plot(px,py,'y.')  
 pylab.axis([None,None,-.6,.6])  
 pylab.title("DIY Electrocardiogram - Trial 1",fontsize=26)  
 pylab.ylabel("Normalized Potential",fontsize=16)  
 #pylab.xlabel("Time (sec)")  
 ax=pylab.axis()  
 pylab.subplot(2,1,2)  
 pylab.plot(px,pHR,'k:')  
 pylab.plot(px,pHR,'b.')  
 pylab.axis([ax[0],ax[1],None,None])  
 pylab.ylabel("Heart Rate (BPM)",fontsize=16)  
 pylab.xlabel("Time (seconds)",fontsize=16)  
 pylab.savefig("test.png",dpi=120)  
 pylab.show()  
 print "DONE"  
 


ECG Success!
Posted by
Scott January 14th, 2009 | 5,253 words | 1 Comment »

I kept working on my homemade ECG machine (I had to change the values of some of the resisters) and it looks like I’m getting some valid signals! By recording the potential using my sound card (microphone hole = a nice analog to digital converter that every PC has) I was able record my ECG with sound recording software, smooth it, and this is what it looks like. Pretty cool huh?

This was based on a circuit I made using a single op-amp (A LM324 from RadioShack $1.49). Basically the op-amp just amplifies micropotential generated by my heart and outputs it in such a way that I can connect it to a standard headphone jack to plug into my microphone hole. The signal is very noisy though. I’m thinking about making the intricate circuit (with 6 op-amps) to produce a better signal-to-noise ratio, but first I’ll try coding my way out of the noise.



DIY ECG Attempt 1: Failure
Posted by
Scott January 14th, 2009 | 5,253 words | No Comments »

So I followed-through on yesterday’s post and actually tried to build an ECG machine. I had a very small amount of time to work on it, so instead of building the fancy circuit (with 6 band-pass filtered op-amps and diodes posted in the previous entry) I built the most crude circuit that would theoretically work. I used one of the 4 available op-amps from a LM324 chip (pictured to the left) . I was working late at night, and I’m quite colorblind, so I had to take a gamble (as usual) with the resistors I used. Resistors are color coded with bands that represent their resistance. But, since I’m highly colorblind, red, orange, and black all look the same to me. So I can’t be sure I go the resistances right. I’d check it with my digital multimeter but I seem to have lost it and my wife doesn’t remember seeing it recently. Blast! Anyway, I built the sucker, hooked it up to my sound card, and made electrodes by soldering wires to pennies. After a good lick, I attached the pennies to my chest with tape and tried recording. Every time the pennies made contact with my skin, I would see noise on the trace, but I couldn’t seem to isolate a strong heartbeat signal. This is what I saw and the circuit I build to see it:

When I have more time I’ll locate my multimeter and build the full circuit described below. I’ll update everyone on my progress later. Wish me luck!

Perhaps this project will be working soon. How cool is that? Many techno-savvy people have made these DIY ECG machines, but no where on the net do these people describe how to interpret the data. Since I’m planning on building it, testing it, recording ECG data, and processing/analyzing it, I’ll have something truly unique on the internet. Perhaps it will be worthy of mention on Hack-a-day when it’s complete! How cool would that be?



DIY ECG?
Posted by
Scott January 13th, 2009 | 5,253 words | No Comments »

Last night my wife put her head on my chest while we were watching a movie. A minute or two later I felt a light sinking feeling in my upper chest, and my wife looked up at me in horror. “Your heart stopped beating!” I assured her that everything was okay (it quickly resumed), and that it happens all the time. I feel the sinking feeling often, know it’s because my heart is briefly beating irregularly, and assume it’s normal. After all, your heart isn’t a robot, it’s a living organ doing the best it can. It’s never perfectly regular, and presumably everybody has momentary irregularities, they just don’t notice them. When I got in bed I began wondering how regular irregular heartbeats are. What would the chances be that I have some kind of arrhythmia? I’ve had a checkup not too long ago by a family practice physician who used a stethoscope on my back to listen to my heartbeat, and he didn’t notice anything. Then again, how often do general practice doctors detect subtle arrhythmia?
I know that whatever problem I have is likely too small to cause any serious troubles, but at the same time I’m becoming obsessed as to determining exactly what my problem is. How many times a day does my heart skip beats? What about nighttime? If only there were some way to record heartbeat data, then I could analyze it and determine the severity of my problem. But wait, data? That would be hours of heartbeat recordings… that means… YES!!!! HOME MADE DO-IT-YOURSELF HARDWARE! WRITING SOFTWARE TO ANALYZE LARGE AMOUNT OF DATA! SUPER-COOL SCIENCY STUFF THAT I CAN MAKE MYSELF, COMBINING BASIC ELECTRONICS WITH BIOMEDICAL DATA ANALYSIS AND, HARAY, PROGRAMMING WITH PYTHON!
Naturally, my thoughts began to overwhelm my reality as soon as Python entered the scene. I wondered how I could use my PC to record my heartbeat, without spending much money on hardware, and only using software I write myself. I pondered this on the way to work this morning, and came up with two possible methods:
Method 1: acoustical recordings. This would be the easiest way to record my heartbeat. I could tape a stethoscope to my chest, insert a small microphone in the earpiece, connect the microphone to my PC, and record sound data for several hours. Theoretically it would work, but it would be highly prone to noise from breathing, and I would have to lay perfectly still to avoid noise caused by movements. The data (trace) would have to be smoothed, processed with a band-pass filter (to eliminate interference), and heartbeats could be calculated. However, this would only give me heart beat time information…
Method 1: electrical recordings. This would be a little more complicated, but generate much more information. I could record the electrical activity of my heart, and the charts would look like the cool electrocardiograms (ECGs) that you see on TV shows and stuff. I would have to build some circuity to amplify the 1mV heartbeat signal recorded from electrodes taped to my chest, but then again what’s more fun than ghetto-building circuits! (I apologize for any people reading this blog who actually live in the ghetto and enjoy building circuits.) I did a little Googling and found that similar things have been done before with a handful of diodes, resisters, and 6 op-amps. I think I’m going to follow the guide on this page and build the circuit seen below:


Schematic of a crude ECG circuit

Supposedly, the data I can obtain looks something like the image at the bottom of this blog entry. I’d attach 3 electrodes to my body (chest, arm, and leg), hook them up to my little circuit, then connect to circuit to my PCs sound card. I’d record the trace (maybe while I sleep?) and analyze it with Python, Numpy, Scipy, and Matplotlib (gosh I love Python). There are several websites which demonstrate how to build DIY ECG recording devices, but none of these actually ANALYZE the data they obtain! Hopefully I could fill this little niche on the internet. We’ll see what happens. I have my thesis to work on, and a whole bunch of other stuff on my plate right now.

A sample recording from the circuit pictured above

The terms assigned to different parts of the heartbeat

UPDATE: I found an extremely crude ECG circuit which I can make from parts I already have at my house. It has tons of noise, but maybe I can filter it out? Perhaps I’ll try this tonight? [ponders]

copyright © 2006 swharden@gmail.com