Friday 20 July 2018

Measuring the heart beat using a smart phone

Long time, no blog posts. Four days short of two years, actually. It almost feels bad to ruin the imaginary anniversary party but luckily it's not illegal to have cake anyway.

So, today I was typing a IM and decided to deliver a mediocre joke in form of a hasty sketch. For the canvas I though I could take black photo by covering the camera of my phone with my palm. However, it turned out to be more difficult than I thought since the camera adjusted its light sensitivity so much that it picked up the light scattering from the inside of the palm making the picture always reddish in color. 

This gave me an idea to test whether I can measure my heart rate by recording video from my palm. I put on the flashlight on my trusty Honor 6 and recorded a short video with the camera pressed against my palm. What I got was hundreds of frames looking like this:

Representative example frame of the raw video data
Although one doesn't see much just looking at the video, a more careful analysis with Python reveals that there is in fact some interesting time behaviour hidden under all that redness. By taking the mean value of the red channel (with 8-bit value ranging from 0-255) over all 1920x1080 pixels, the following signal is obtained:

So, the peak-to-peak variation is small, about 1.5 per pixel (or 0.6% compared to the background 'redness') but it is there and it is very clear. That's the variation of scattered intensity of red light due to varying amount of blood in veins during the pump cycle of the heart. From that we could obtain the rate by calculating the number of beats per time interval but let's use a more sophisticated method instead.

Mathematically every signal can be constructed by summing up a suitable set of sine waves. To find the multipliers and the phases of the waves, we use something called the Fourier transform. The Internet is full of excellent explanations what it is and how it works, for example this Youtube video by 3Blue1Brown, so I'm not going to go it through here. The important part here is to know that taking the absolute value of the Fourier transform and squaring it gives tells us how the power of the signal is distributed between the different frequency components of the signal.

To get of rid of the constant background which would cause a huge zero frequency component to the power spectrum, the mean value is subtracted from the red channel signal before taking the Fourier transform. The resulting power spectrum looks like this:


The first larger peak is now the one we are interested in, for it is the base frequency of the signal and thus corresponds to the heart rate. The peak value is close to 85 bpm (beats per minute) which actually matches very well the value measured by my (in)activity bracelet. Not bad, eh?

We also see another peak at 170 bpm which is twice the base frequency. The integer multiples of the base frequency, called harmonics, usually appear in the power spectrum of periodic signals, unless they are pure sine waves. However, interesting fact to me is that there is very little contribution to the signal from higher harmonics. The signal is thus mainly composed of the base frequency and its first harmonic with twice the base frequency. To me this makes sense since the blood pressure is mainly affected by ventricular chambers which either contract (systole) or relax (diastole). However, don't take this too seriously since I'm not a physician but a physicist, so there's a somewhat realistic chance that I might be oversimplifying the cardiovascular system a bit too much..

After a long break it was fun to write something to the blog again. I hope that the next update won't take quite as long. Lenk's quest is not dead either, and has even progressed a bit since the last time. If the stars align right, we might see the next part soon..

Below you find the Python code I used in the analysis of the video data:


from __future__ import division, print_function

import numpy as np
import matplotlib.pyplot as plt
from moviepy.editor import VideoFileClip

clip = VideoFileClip('source.mp4',audio=False)
fps = clip.fps

data = []

#Compute the mean value of red channel per frame
for frame in clip.iter_frames():
    data.append(np.mean(np.mean(frame[:,:,0],axis=1)))

data = np.array(data)

data = data[700:826]
time = np.arange(data.size)/fps #time relative to frame number 700

#Calculate the Fourier power spectrum
#The mean value is substracted in order to remove the zero frequency component
fourier = np.abs(np.fft.fft(data - np.mean(data)))#**2
freq = np.fft.fftfreq(data.size,1/fps)

plt.plot(time,data)
plt.xlabel("Time (s)")
plt.ylabel("Mean red channel signal")

plt.figure()
plt.axvline(x=85,label='Heart rate sensor (85 bpm)',color='red')
plt.axvline(x=2*85,label='2x Heart rate sensor (170 bpm)',color='orange')
plt.axvline(x=3*85,label='3x Heart rate sensor (255 bpm)',color='green')
plt.plot(freq[:freq.size//2]*60,fourier[:freq.size//2],label="Fourier transform",color='blue',linewidth=2)
plt.legend()

plt.xlabel("Frequency (1/min)")
plt.ylabel("Fourier power spectrum |FFT|$^2$")

plt.show()