November, 2012

Heart Rate Data Analysis

I needed a way to collect real heart rate sample data for more in-depth analysis. I wanted to not only validate the Butterworth filtering algorithm, but also understand the sensitivity of its parameters and its robustness against other exogenous noise. Furthermore, having a good dataset will enable me to examine other possible algorithms in detecting the pulse accurately. The CC2540 implementation has a bug. Hopefully, I can use the dataset to identify the bug and modify the algorithm to make it work under my new implementation.

I’ve written some small python scripts to extract the raw data from both the Arduino and my implementation on CC2540. Fortunately, the SmartRF05EB board comes with access to UART so I was able to spit out the same data out to PC. From my linux box, I listen to the serial port over USB and just write the data out to raw text. Here’s the script that dumps data from the serial port.

#!/usr/bin/env python
import sys
import time
sys.path.insert(0,'/nas/dev/c/uC/8051/projects/cc2540/scripts/pyserial-2.6')
import serial

def readlines(ser):
  starttime = time.time()
  while True:
    text = ser.readline();
    difftime = time.time() - starttime
    sys.stdout.write("%f %s" % (difftime,text))

ser = serial.Serial(port='/dev/ttyUSB0', baudrate=115200, timeout=None)
readlines(ser)

ser.flush()
ser.close()

Once the raw text data is collected, another python script parses this data and writes it into tabular csv data. The csv data is much more ammenable for experimentation, and importing it is a snap on any statistical or computing environment of your choice. (for me it’s R) This is the script that reads from the serial port.

#!/usr/bin/env python
import sys
import re
import time
import os

path = sys.argv[1]
f = open(path, "r");

gotS = False
gotB = False
gotQ = False
data = []
row  = [ None, None, None, None]
ts = None

print ",".join(['ts','signal','filtered','hrv','bpm'])

while f:
  line = f.readline()
  tok = line.split()
  n = len(line)
  if n == 0:
    break
  if len(tok) < 2:
    continue
  s = tok[1]

  # when we detect the first S, the rest is part of the same row
  if s[0] == 'S' and gotS == True:
    gotS = False
    print ",".join(str(i) for i in [ts]+ row)
    # data.append(row[:])  # copies

  if s[0] == 'S' and gotS == False:
    ts = float(tok[0])
    row[0] = int(s[1:])
    gotS = True

  if s[0] == 'F' and gotS == True:
    sign = -1 if s[1] == 'N' else 1
    row[1] = sign*int(s[2:])

  if s[0] == 'B' and gotS == True:
    row[2] = int(s[1:])

  if s[0] == 'Q' and gotS == True:
    row[3] = int(s[1:])

The pulse is detected whenever the filtered data line crosses zero. The algorithm refines the raw data by filtering it so that pulse detection is easier.

The data from CC2540 gives almost the same result.

Another difference to note is the scale of the raw data. The ADC values (signal) from Arduino hovers around ~900, whereas the CC2540 values hover around ~300. The differences are due to the reference voltage, and the operating voltage among other things. Arduino uses 5V by default for the reference voltage, whereas CC2540 uses 1.25V by default. Arduino runs at 5V, CC2540 runs at 3V.

Read More