Noninvasive Fetal ECG: The PhysioNet/Computing in Cardiology Challenge 2013 1.0.0

File: <base>/sources/fanelli_at_mit.edu/physionet2013.m (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;