Noninvasive Fetal ECG: The PhysioNet/Computing in Cardiology Challenge 2013 1.0.0
(10,016 bytes)
%% Algorithm for FECG extraction from abdominal recordings
%The algorithm extracts FECG traces from abdominal recordings from pregnancies.
%Andrea Fanelli
%May 2013
%MODIFICATO
%% Definition of parameters
function [cleanedBestRR,QT_Interval] = physionet2013(tm,ECG)
load filt3
SMOOTH_PARAM=50;
SIG_NUMBER=size(ECG,2);
UPPER_THRESH=0.4;
LOWER_THRESH=0.2;
SAMPL_FREQ=1000;
START_POINT=1;
MAT_QRS_AVG=10; %number of maternal QRS to be averaged
MAT_QRS_TIME=0.12; %duration of maternal QRS in sec
MAT_QRS_SAMPLE=floor(SAMPL_FREQ*MAT_QRS_TIME); %duration of maternal QRS in samples
%definition of left and right maternal QRS windows
if rem(MAT_QRS_SAMPLE,2)==0,
L_MQRS=MAT_QRS_SAMPLE/2-1;
R_MQRS=MAT_QRS_SAMPLE/2;
else
L_MQRS=(MAT_QRS_SAMPLE-1)/2;
R_MQRS=(MAT_QRS_SAMPLE-1)/2;
end
FET_QRS_TIME=0.06; %duration of fetal QRS in sec
FET_QRS_SAMPLE=floor(SAMPL_FREQ*FET_QRS_TIME); %duration of fetal QRS in samples
%definition of left and right fetal QRS windows
if rem(FET_QRS_SAMPLE,2)==0,
L_FQRS=FET_QRS_SAMPLE/2-1;
R_FQRS=FET_QRS_SAMPLE/2;
else
L_FQRS=(FET_QRS_SAMPLE-1)/2;
R_FQRS=(FET_QRS_SAMPLE-1)/2;
end
DATA_LENGTH=length(ECG);
if DATA_LENGTH < 3500,
error('Data is too short to be processed. It should be at least 3500 samples long.');
end
%% data correction
for l=1:size(ECG,2)
wrongValues=find(isnan(ECG(:,l)));
ECG(wrongValues,l)=0;
end
%% Preprocessing
postSmooth=zeros(DATA_LENGTH ,SIG_NUMBER);
noMean=zeros(DATA_LENGTH,SIG_NUMBER);
noStd=zeros(DATA_LENGTH,SIG_NUMBER);
% filtering
abdomRecB=filtfilt(Hbs.numerator,1, ECG);
abdomRec=filtfilt(Hbp3.numerator,1, abdomRecB);
for h=1:SIG_NUMBER,
%mean subtraction
noMean(:,h)=abdomRec(:,h)-mean(abdomRec(:,h));
%std subtraction
noStd(:,h)=noMean(:,h)./(sqrt(sum(noMean(:,h).^2)));
end
%% Select channel for maternal Qrs detection
matMatrix=noStd;
abdQuality=sum(abs(diff(matMatrix,1)));
[~,ch]=min(abdQuality);
matSignal=matMatrix(:,ch);
%% Mat QRS detection
qrsMat=[];
i=1;
while i - 1 + SAMPL_FREQ + R_MQRS <= DATA_LENGTH,
%detection of Maternal QRS template for each window
sigWindow=matSignal(i:i+SAMPL_FREQ-1);
[~,time]=max(abs(sigWindow));
if time - L_MQRS < 1 && i==1,
winStart=1;
else
winStart=time - L_MQRS;
end
mqrsTempl=matSignal(i -1 +winStart:i - 1 + time + R_MQRS);
%avoid mistakes in the last window
if (i + SAMPL_FREQ + MAT_QRS_SAMPLE <= DATA_LENGTH),
%compute of cross correlation for each window
crossTempl=conv(matSignal(i:i+SAMPL_FREQ+MAT_QRS_SAMPLE),rot90(rot90(mqrsTempl)));
crossTempl=crossTempl(MAT_QRS_SAMPLE:end-MAT_QRS_SAMPLE-1);
%normalize cross correlation signal between 0 and 1
crossTempl= crossTempl ./ sum(mqrsTempl.^2);
%detection of all the peaks in the signal
overThresh=0;
actualMax=0;
for k=1:length(crossTempl),
if (overThresh==0 && (crossTempl(k)) > UPPER_THRESH),
overThresh=1;
end
if (overThresh==1),
if (crossTempl(k) > actualMax),
actualMax = crossTempl(k);
peakTime=k;
end
end
if (overThresh==1 && crossTempl(k) < LOWER_THRESH),
overThresh=0;
[~,realTime]=max(abs(matSignal(i + peakTime: i + peakTime + MAT_QRS_SAMPLE)));
%if the interbeat distance is > 1 sec, the algorithm identify the peak between the actual beat and the previous detected beat
if (isempty(qrsMat)==0) && (i-1 + peakTime + realTime - qrsMat(end,1)) > SAMPL_FREQ,
[~,timePast]=max(abs(matSignal(qrsMat(end,1) + MAT_QRS_SAMPLE : i + peakTime)));
qrsMat=[qrsMat; [qrsMat(end,1) + MAT_QRS_SAMPLE + timePast, -1]];
end
%simple control to avoid double identification
if ((isempty(qrsMat)==1) || (i-1 + peakTime + realTime - qrsMat(end,1) > 5))
qrsMat=[qrsMat; [i-1 + peakTime + realTime, actualMax]];
end
actualMax=0;
end
end
end
i=i+SAMPL_FREQ-1;
end
F_AVG_MAT=mean(diff(qrsMat));
%% Clean extracted mecg
[mQrs]=clearFHR(F_AVG_MAT(:,1)/1000,qrsMat,1000);
% figure, plot(matSignal);
% hold on
% plot(qrsMat(:,1),matSignal(qrsMat(:,1)),'.g');
% plot(mQrs,matSignal(mQrs),'.r');
%% Smoothing filter to increase fetal to maternal SNR
abdomRec=filtfilt(Hlp.numerator,1, abdomRecB);
for h=1:SIG_NUMBER,
%mean subtraction
noMean(:,h)=abdomRec(:,h)-mean(abdomRec(:,h));
%std subtraction
noStd(:,h)=noMean(:,h)./(sqrt(sum(noMean(:,h).^2)));
end
for h=1:SIG_NUMBER,
postSmooth(:,h)=noStd(:,h)-smooth(noStd(:,h),SMOOTH_PARAM);
end
%% Mat QRS removal
fEcgMatrix= zeros(DATA_LENGTH,SIG_NUMBER);
mQrsTempl=zeros(MAT_QRS_SAMPLE,1);
for z=1:SIG_NUMBER,
fEcg=postSmooth(:,z);
if exist('mQrsMatrix','var')==1,
clear mQrsMatrix
end
for j=1:length(mQrs),
if (j~=1 || mQrs(j) - L_MQRS >= 1),
mBeat=fEcg(mQrs(j)-L_MQRS:mQrs(j)+R_MQRS);
if exist('mQrsMatrix','var')==0,
mQrsMatrix=mBeat;
mQrsTempl=mBeat;
else
rmsDiff=sqrt(sum((mQrsTempl-mBeat).^2)/MAT_QRS_SAMPLE);
mQrsMatrix=cat(2,mQrsMatrix,mBeat);
if size(mQrsMatrix,2)> MAT_QRS_AVG,
mQrsMatrix=mQrsMatrix(:,2:MAT_QRS_AVG+1);
end
mQrsTempl=mean(mQrsMatrix,2);
a=((mQrsTempl'*mQrsTempl)^(-1))*(mQrsTempl')* mBeat;
mBeatCap=mQrsTempl*a;
fEcg(mQrs(j)-L_MQRS:mQrs(j)+R_MQRS)=mBeat-mBeatCap;
end
else
fEcg(1:mQrs(j)+R_MQRS)=0;
end % j != 1
end % j > length(mQrs)
fEcgMatrix(:,z)=fEcg;
end % z > SIG_NUMBER
%
% for ll=1:SIG_NUMBER,
% figure, plot(fEcgMatrix(:,ll));
% end
%% Detezione QRS fetali
for z=1:SIG_NUMBER,
depFecg=fEcgMatrix(:,z)-mean(fEcgMatrix(:,z));
normFecg=depFecg/sqrt(sum(depFecg.^2));
fQrs=[];
for i=1:SAMPL_FREQ/2:DATA_LENGTH - SAMPL_FREQ/2 -1,
%detection of Fetal QRS template for each window
sigWindow=normFecg(i:i+ SAMPL_FREQ/2 -1);
[peak,time]=max(abs(sigWindow));
if time - L_FQRS < 1 && i==1,
winStart=1;
else
winStart=time - L_FQRS;
end
fqrsTempl=normFecg(i -1 +winStart:i - 1 + time + R_FQRS);
%avoid mistakes in the last window
if (i + SAMPL_FREQ/2 + FET_QRS_SAMPLE <= DATA_LENGTH),
%compute of cross correlation for each window
crossTempl=conv(normFecg(i:i+SAMPL_FREQ/2+FET_QRS_SAMPLE),rot90(rot90(fqrsTempl)));
crossTempl=crossTempl(FET_QRS_SAMPLE:end-FET_QRS_SAMPLE-1);
%normalize cross correlation signal between 0 and 1
crossTempl= crossTempl ./ sum(fqrsTempl.^2);
%detection of all the peaks in the signal
overThresh=0;
actualMax=0;
for k=1:length(crossTempl),
if (overThresh==0 && (crossTempl(k)) > UPPER_THRESH),
overThresh=1;
end
if (overThresh==1),
if (crossTempl(k) > actualMax),
actualMax = crossTempl(k);
peakTime=k;
end
end
if (overThresh==1 && crossTempl(k) < LOWER_THRESH),
overThresh=0;
[realPeak,realTime]=max(abs(normFecg(i + peakTime: i + peakTime + FET_QRS_SAMPLE)));
%if the interbeat distance is > 1 sec, the algorithm identify the peak between the actual beat and the previous detected beat
% if (isempty(fQrs)==0) && (i-1 + peakTime + realTime - fQrs(end,1)) > 500,
%
% [pastPeak,timePast]=max(abs(matSignal(fQrs(end,1) + FET_QRS_SAMPLE : i + peakTime)));
% fQrs=[fQrs; [fQrs(end,1) + FET_QRS_SAMPLE + timePast, -1]];
%
% end
%simple control to avoid double identification
if ((isempty(fQrs)==1) || (i-1 + peakTime + realTime - fQrs(end,1) > 5))
fQrs=[fQrs; [i-1 + peakTime + realTime, actualMax]];
end
actualMax=0;
end
end
end
end
eval(['qrsFet',num2str(z),'=fQrs;']);
end
%% Clean extracted fecg
quality=zeros(4,1);
for k=1:SIG_NUMBER,
if eval(['isempty(qrsFet',num2str(k),')==0,']);
eval(['cleanQrsFet',num2str(k),'=clearFHR(mean(diff(qrsFet',num2str(k),'(:,1)))/1000,qrsFet',num2str(k),',1000);']);
eval(['quality(k)=mean(abs(diff(diff(cleanQrsFet',num2str(k),'))));']);
else
quality(k)=NaN;
end
end
%% Identify HRV series of best quality
[~,bestQuality]=min(quality);
eval(['bestRR=qrsFet',num2str(bestQuality),'(:,1);']);
eval(['cleanedBestRR=cleanQrsFet',num2str(bestQuality),';']);
%% Visualization
% figure, plot(ECG(:,bestQuality));
% hold on
% % plot(bestRR,ECG(bestRR,bestQuality),'.g');
% plot(cleanedBestRR,ECG(cleanedBestRR,bestQuality),'.r');
%%
QT_Interval=NaN;