% Use basic and diagnostic quality classifier
clear all
close all
clc
load('basic_classifier.mat');
load('diagnostic_classifier.mat');
% Put in parent_dir the path to the folder to be processed (for single
% subject)
parent_dir = 'C:\Users\Alice\OneDrive - Alma Mater Studiorum Università di Bologna\Perso nal_Health_Systems_Lab\Tutor\WearableSensors2021\Tesina\Rec\1';
PPGraw = importdata('Rec/7/BVP.csv');
ACCraw = importdata('Rec/7/ACC.csv');
% PPGraw = importdata('Rec/7/BVP.csv');
% ACCraw = importdata('Rec/7/ACC.csv');
% PPGraw = load(fullfile(parent_dir,'BVP.csv'));
% ACCraw = load(fullfile(parent_dir,'ACC.csv'));
template_struct = load('Template_v3.m','-mat');
template = template_struct.template;
start = datetime(PPGraw(1),'convertfrom','posixtime','timezone','Europe/Rome');
PPG = PPGraw(3:end);
N_PPG = length(PPG);
fs_PPG = PPGraw(2);
Ts_PPG = 1/fs_PPG;
t_PPGs = (0:1:N_PPG - 1) * Ts_PPG;
t_PPG = start + seconds(t_PPGs);
ACC = ACCraw(3:end,:);
N_ACC = length(ACC);
fs_ACC = ACCraw(2,1);
Ts_ACC = 1/fs_ACC;
t_ACCs = (0:1:N_ACC - 1) * Ts_ACC;
t_ACC = start + seconds(t_ACCs);
g = 9.8; %[m/s^2]
ACC_g(:,1) = ACC(:,1) / (64);
ACC_g(:,2) = ACC(:,2) / (64);
ACC_g(:,3) = ACC(:,3) / (64);
% Modulus from the three accelerometer components
ACC_mod = sqrt(ACC_g(:,1).^2 + ACC_g(:,2).^2 + ACC_g(:,3).^2);
PPG_filt = PPG_preproc(PPG,fs_PPG);
L = length(PPG_filt);
[sys_peak, I_peak, sys_foot, I_foot] = ab_detection(PPG_filt,Ts_PPG);
I_foot_acc = round((I_foot .* Ts_PPG) .* fs_ACC);
for i = 1:length(I_foot) - 3
pulse_analysis_nozscore = PPG_filt(I_foot(i):I_foot(i+1));
pulse_analysis = zscore(PPG_filt(I_foot(i):I_foot(i+1)));
P = length(pulse_analysis);
pulse_analysis(pulse_analysis == 0) = 0.01;
%In such this way, log(0) is replaced by log(0.01), that is not -Inf
% Signal Quality
% By Moody's algorithm
SNR(i) = moody(pulse_analysis);
% Accelerometer peak2peak amplitude
pp_amp(i) = peak2peak(ACC_mod(I_foot_acc(i):I_foot_acc(i+1)));
% Spectral Coherence, MSE, and PSNR
if length(template) < length(pulse_analysis)
N = length(pulse_analysis);
template_2 = interp1(1:numel(template),template,linspace(1,numel(template),numel(pulse_analysis)));
R_mat = corrcoef(template_2,pulse_analysis);
R_corr(i) = R_mat(1,2);
Cxy(i) = mean(mscohere(template_2,pulse_analysis,[],[],[],64));
MSE(i) = mean((template_2 - pulse_analysis').^2);
PSNR(i) = 10*log10(max(template_2)^2/MSE(i));
elseif length(template) > length(pulse_analysis)
N = length(template);
pulse_analysis_2 = interp1(1:numel(pulse_analysis),pulse_analysis,linspace(1,numel(pulse_analysis),numel(template)));
R_mat = corrcoef(template,pulse_analysis_2);
R_corr(i) = R_mat(1,2);
Cxy(i) = mean(mscohere(template,pulse_analysis_2,[],[],[],64));
MSE(i) = mean((template - pulse_analysis_2).^2);
PSNR(i) = 10*log10(max(template)/MSE(i));
else
N = length(pulse_analysis); % = length(template);
R_mat = corrcoef(template,pulse_analysis);
R_corr(i) = R_mat(1,2);
Cxy(i) = mean(mscohere(template,pulse_analysis,[],[],[],64));
MSE(i) = mean((template - pulse_analysis').^2);
PSNR(i) = 10*log10(max(template)^2/MSE(i));
end
% By Jang18: Correlation coefficient between two adjancent pulses
if i == 1
Sig_sim(i) = 1;
else
pulse_analysis_prev = PPG_filt(I_foot(i-1):I_foot(i));
if length(pulse_analysis_prev) < length(pulse_analysis)
pulse_analysis_prev_2 = interp1(1:numel(pulse_analysis_prev),pulse_analysis_prev,linspace(1,numel(pulse_analysis_prev),numel(pulse_analysis)));
Sig_sim_mat = corrcoef(pulse_analysis_prev_2,pulse_analysis);
Sig_sim(i) = Sig_sim_mat(1,2);
elseif length(pulse_analysis_prev) > length(pulse_analysis)
pulse_analysis_2 = interp1(1:numel(pulse_analysis),pulse_analysis,linspace(1,numel(pulse_analysis),numel(pulse_analysis_prev)));
Sig_sim_mat = corrcoef(pulse_analysis_prev,pulse_analysis_2);
Sig_sim(i) = Sig_sim_mat(1,2);
else
Sig_sim_mat = corrcoef(pulse_analysis_prev,pulse_analysis);
Sig_sim(i) = Sig_sim_mat(1,2);
end
end
% Skewness
mean_sd = mean(pulse_analysis) / std(pulse_analysis);
vec_mean_sd = mean_sd * ones(size(pulse_analysis));
S(i) = 1/P * sum((pulse_analysis - vec_mean_sd).^3)';
% Kurtosis
Kur(i) = 1/P * sum((pulse_analysis - vec_mean_sd).^4);
% Relative Power
[PSD,F] = pwelch(pulse_analysis,[],[],[],fs_PPG);
f_1Hz = 1; %Hz
f1 = 2.25; %Hz
f2 = 8; %Hz
index_f1Hz = find(F == f_1Hz);
index_f1 = find(F == f1);
index_f2 = find(F == f2);
RelP(i) = sum(PSD(index_f1Hz:index_f1))/sum(PSD(1:index_f2));
% Statistical parameters
StdSig(i) = std(pulse_analysis_nozscore);
MedianSig(i) = median(pulse_analysis);
% By Sukor11: morphological features
trough_depth(i) = abs(pulse_analysis(1) - pulse_analysis(end));
pos_foot(i,1) = I_foot(i);
pos_foot(i,2) = I_foot(i+1);
% Updated 18.05
% # of detected peaks
[pks] = findpeaks(pulse_analysis);
if isempty(pks)
Npeaks(i) = 0;
else
Npeaks(i) = length(pks);
end
% Derivative zerocrossing rate
der = diff(pulse_analysis);
P_der = length(der);
der_zerocross = length(find (der < 0));
Z_der(i) = 1/P_der * sum(der_zerocross);
end
disp('end loop')
FeaturesNames_basic = cellstr({'Peak2peak ACC','Signal Similarity','Skewness','Trough depth','MedianPulse','StdPulse noZ','Npeaks','Z der rate','SigCoh','PSNR'});
Matrix_basic = [pp_amp', Sig_sim', S', trough_depth', MedianSig', StdSig', Npeaks', Z_der', Cxy', PSNR'];
Table_basic = array2table(Matrix_basic,'VariableNames',FeaturesNames_basic);
FeaturesNames_diagnostic = {'Peak2peak ACC','Signal Similarity','Kurtosis','Rel Power','Skewness','MedianPulse','StdPulse noZ','SNR Moody','Npeaks','Z der rate','MSE','PSNR'};
Matrix_diagnostic = [pp_amp', Sig_sim', Kur', RelP', S', MedianSig', StdSig', SNR', Npeaks', Z_der', MSE', PSNR'];
Table_diagnostic = array2table(Matrix_diagnostic,'VariableNames',FeaturesNames_diagnostic);
%% Preprocess features - Basic classifier
% Box-Cox transformation
% To make the features normally-distributed
% Requirements: data > 0
min_features = zeros(size(Matrix_basic,2),1);
New_Matrix = zeros(size(Matrix_basic));
var_trans = zeros(size(Matrix_basic,2),1);
Norm_Matrix = zeros(size(Matrix_basic));
lambda = zeros(size(Matrix_basic,2),1);
Matrix_min = zeros(size(Matrix_basic));
for i = 1:size(Matrix_basic,2)
min_features(i) = min(Matrix_basic(:,i));
if min_features(i) < 0 || min_features(i) == 0
New_Matrix(:,i) = Matrix_basic(:,i) + abs(min_features(i)) + eps;
var_trans(i) = 1;
else
New_Matrix(:,i) = Matrix_basic(:,i);
end
[Norm_Matrix(:,i), lambda(i)] = boxcox(New_Matrix(:,i));
if min_features(i) < 0 || min_features(i) == 0
Matrix_min(:,i) = Norm_Matrix(:,i) - abs(min_features(i)) - eps;
else
Matrix_min(:,i) = Norm_Matrix(:,i);
end
end
Matrix_basic_zscored = zscore(Matrix_min);
%% Preprocess features - Diagnostic classifier
% Box-Cox transformation
% To make the features normally-distributed
% Requirements: data > 0
min_features = zeros(size(Matrix_diagnostic,2),1);
New_Matrix = zeros(size(Matrix_diagnostic));
var_trans = zeros(size(Matrix_diagnostic,2),1);
Norm_Matrix = zeros(size(Matrix_diagnostic));
lambda = zeros(size(Matrix_diagnostic,2),1);
Matrix_min = zeros(size(Matrix_diagnostic));
for i = 1:size(Matrix_diagnostic,2)
min_features(i) = min(Matrix_diagnostic(:,i));
if min_features(i) < 0 || min_features(i) == 0
New_Matrix(:,i) = Matrix_diagnostic(:,i) + abs(min_features(i)) + eps;
var_trans(i) = 1;
else
New_Matrix(:,i) = Matrix_diagnostic(:,i);
end
[Norm_Matrix(:,i), lambda(i)] = boxcox(New_Matrix(:,i));
if min_features(i) < 0 || min_features(i) == 0
Matrix_min(:,i) = Norm_Matrix(:,i) - abs(min_features(i)) - eps;
else
Matrix_min(:,i) = Norm_Matrix(:,i);
end
end
Matrix_diagnostic_zscored = zscore(Matrix_min);
Table_diagnostic_zscored = array2table(Matrix_diagnostic_zscored,'VariableNames',FeaturesNames_diagnostic);
%% Basic classifier
% y_basic = 1 if the pulse can be used to estimate the HR, 0 otherwise
y_basic_double = basic_classifier(Matrix_basic_zscored');
y_basic = logical(round(y_basic_double));
% Find the foot indices of pulses that can be used to estimate the HR
ind_basic = find(y_basic == 1);
% E.g.: Interbeat Interval
IBI = (I_foot(ind_basic+1) - I_foot(ind_basic)) .* Ts_PPG;
%% Diagnostic classifier
% y_diagnostic = 1 if the pulse can be used for morphological analysis, 0 otherwise
y_diagnostic = diagnostic_classifier.predictFcn(Table_diagnostic_zscored);
ind_diagnostic = find(y_diagnostic == 1);
for i = 1:length(ind_diagnostic) - 1
% E.g.: Crest Time
CT(i) = (I_peak(ind_diagnostic(i)+1) - I_foot(ind_diagnostic(i))) * Ts_PPG;
% + 1 in I_peak because the ab_detection algorithm detect the I_peak
% first and then the I_foot
end