PPG and Remote PPG peak detection

40 views (last 30 days)
Lena Harrigan
Lena Harrigan on 28 Dec 2022
Answered: Star Strider on 22 Jan 2023
Good evening!
First of all excuse me for the long question, I am unexperienced in this field and I just started to study more about it!. I have two signals, one is the PPG signal collected with a sensor (typical pulse oximeter) and the other with a camera, i.e. RPPG (remote). Now I have to process these signals, align them in time and find a correct algorithm to detect the peaks. I have started trying the matlab Toolbox for PPG signal processing(https://ppg-beats.readthedocs.io/en/latest/toolbox/getting_started/) and it is indeed very useful but I think I don't get a good result (maybe because I did not understand how the algorithms work) and I don't know what I am doing wrong (I attach the image. Recording time 175.2398s and 2161 samples).
In addition, I have the signal obtained by the sensor, which curiously is noisier and less 'ideal' than the one collected by the camera and I wanted to know if for both signals can be used, or is optimal, the same code. As a last question (I know it's stupid but I haven't worked in signal processing for a long time and I would like to understand it) how could I align these two signals in time? It is simply to be able to compare them when I process them.
Thank you in advance for your help and excuse me for the long post :) (and happy new year!)
These are my raw signals:
RPPG = load('RPPG.mat')
RPPGSignal = RPPG.ProcessedData.RPPG;
title('RPPG signal')
ylabel('RPPG value')
heartRate= table2array(MonHearthRate);
title('Sensor measurement PPG')
ylabel('PPG value')
And this is my processed RPPG signal according to the matlab toolbox on PPG peak detection:
% setup using the code provided in the question
%readData = csvread('p3_sit.csv',0,0);
%ppg_head = readData(:,1).';
clear all;
RPPG = load('RPPG.mat')
ppg_head = RPPG.ProcessedData.RPPG;
%ppg_head= RPPGSignal.';
fs = 12;
S.v =ppg_head(:); % extract PPG data
S.fs = 12;
beat_detector = 'IMS'; % Select Incremental-Merge Segmentation beat detector
[peaks, onsets, mid_amps] = detect_ppg_beats(S, beat_detector); % detect beats in PPG
figure('Position', [20,20,1000,350]) % Setup figure
subplot('Position', [0.05,0.17,0.92,0.82])
t = [0:length(S.v)-1]/S.fs; % Make time vector
plot(t, S.v, 'b'), hold on, % Plot PPG signal
plot(t(peaks), S.v(peaks), 'or'), % Plot detected beats
ftsize = 20; % Tidy up plot
set(gca, 'FontSize', ftsize, 'YTick', [], 'Box', 'off');
ylabel('RPPG', 'FontSize', ftsize),
xlabel('Time (s)', 'FontSize', ftsize)

Answers (1)

Star Strider
Star Strider on 22 Jan 2023
I went back to have another look at this.
The two signals appear to have very little in common, even with respect to their Fourier transforms.
Defining the sampling frequencies could improve this (I defined the RPPG sampling frequency as:
RPPG_Fs = LRPPG / RPPG_ElapTime; % Sampling Frequency (Hz)
for lack of a better option), and with that it is similar to the ‘PPG’ sampling frequency. Even using resample to equalise the sampling frequencies slightly did not improve the results.
The Fourier transforms of the signals indicates that the ‘RPPG’ instrumentation and existing signal preprocessing may have a ‘highpass’ characteristic of sorts, removing frequencies below about 0.25 Hz that contain a significant amount of information, according to the ‘PPG’ signal Fourier transform.
To me, that points to a fundamental problem of the ‘RPPG’ instrumentation or preprocessing (or both) that would significantly limit its usefulness, especially with respect to clinical applications. Improvements in the instrumentation or initial signal preprocessing could correct for that, however I have no specific suggestions on what those might be, since I have no idea what the preprocessing did to the original ‘RPPG’ signal.
T1 = readtable('https://www.mathworks.com/matlabcentral/answers/uploaded_files/1245967/PPG_sensor.csv');
PPG_Fs = 12; % Sampling Frequency (Hz)
PPG = T1.Wave;
LPPG = numel(PPG)
LPPG = 5187
t_PPG = linspace(0, LPPG-1, LPPG)/PPG_Fs;
LD = load(websave('RPPG','https://www.mathworks.com/matlabcentral/answers/uploaded_files/1245972/RPPG.mat'));
ProcessedData = LD.ProcessedData;
RPPG = ProcessedData.RPPG(:);
RPPG_ElapTime = ProcessedData.ElapsedTime; % Seconds
LRPPG = numel(RPPG);
RPPG_Fs = LRPPG / RPPG_ElapTime; % Sampling Frequency (Hz)
t_RPPG = linspace(0, LRPPG-1, LRPPG)/RPPG_Fs;
[rRPPG,tr_RPPG] = resample(RPPG,t_RPPG,PPG_Fs);
plot(t_PPG, PPG, 'DisplayName','PPG')
plot(tr_RPPG, rRPPG, 'DisplayName','RPPG')
xlabel('Time (s)')
LrRPPG = numel(rRPPG);
ixv = (1:LrRPPG); % Equal-Length Vectors (Assume Concurrnet Times)
% figure
% yyaxis left
% plot(t_PPG(ixv), PPG(ixv), 'DisplayName','PPG')
% yyaxis right
% plot(tr_RPPG, rRPPG, 'DisplayName','RPPG')
% grid
% legend('Location','best')
% legend('Location','best')
Fn = PPG_Fs/2;
NFFT = 2^nextpow2(LrRPPG);
FT2 = fft(([rRPPG PPG(ixv)]-mean([rRPPG(:) PPG(ixv)])).*hann(LrRPPG),NFFT)/LrRPPG;
Fv = linspace(0, 1, NFFT/2+1)*Fn;
Iv = 1:numel(Fv);
% figure
% plot(Fv, abs(FT2(Iv,:)*2).*1./max(abs(FT2)))
% grid
% xlabel('Frequency (Hz)')
% ylabel('Magnitude (Amplitude Scaled)')
% legend('rRPPG','PPG(ixv)', 'Location','best')
% xlim([0 2])
plot(Fv, abs(FT2(Iv,1)*2))
xlim([0 2])
plot(Fv, abs(FT2(Iv,2)*2))
xlabel('Frequency (Hz)')
xlim([0 2])
sgtitle('Fourier Transforms')

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by