function qrsFes=FecgQRSfDet(X,fs,cName,QRSm,graph,dbFlag,saveFig,saveFigRRf,qrsAfs) %--------------------------------------------------------------------------------------------- % Fecg: Fetal QRS detection % - Fetal QRS pre-detection on each signal % - Best fetal ECG channel selection based on a priori information on fECG pseudo-periodicity % - Identification of a good interval % - Fetal QRS detection in forward direction starting from the end % and backward starting from the beginning of such interval % -------------------------------------------------------------------------------------------- % Author: Maurizio Varanini, Clinical Physiology Institute, CNR, Pisa, Italy % For any comment or bug report, please send e-mail to: maurizio.varanini@ifc.cnr.it % -------------------------------------------------------------------------------------------- % This program is free software; you can redistribute it and/or modify it under the terms % of the GNU General Public License as published by the Free Software Foundation; either % version 2 of the License, or (at your option) any later version. % % This program is distributed "as is" and "as available" in the hope that it will be useful, % but WITHOUT ANY WARRANTY of any kind; without even the implied warranty of MERCHANTABILITY % or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. % -------------------------------------------------------------------------------------------- if(nargin<3), cName=''; end if(nargin<4), QRSm=[]; end if(nargin<5), graph=1; end if(nargin<6), dbFlag=0; end if(nargin<7), saveFig=0; end if(nargin<8), saveFigRRf=0; end if(nargin<9), qrsAfs=0; end fprintf('\n --------------------------------------------------------- \n'); [progpath, progname] = fileparts(which(mfilename)); fprintf('Program: %s, record name: %s\n', progname, cName); if(saveFig), figFmt='png'; figPath=fullfile('../Figure/',progname); if(~exist(figPath,'dir')), mkdir(figPath); end end if(~isempty(qrsAfs)); learning=1; % learning set, annotations are available else learning=0; %test set end %------------------------------------------------------------- % recording duration [ndt, ns]=size(X); time= [1:ndt]/fs; if(dbFlag && learning), PlotSgnMrkNc(X, qrsAfs*fs, fs, [cName,': faQRS']); end % ----- qrsM=QRSm(1,:)'; if(dbFlag), PlotSgnMrkNc(X, qrsM, fs, [cName,': mQRS']); end qrsMs=qrsM/fs; %-------------------------------------------------------------------------------------------- for is=1:ns, X(:,is)= (X(:,is)-mean(X(:,is)))/std(X(:,is)); end % X(:,ns+1)= mean(X')'; % if ICA fails S/N improvment could be obtained % ns=ns+1; if(graph) figure, set(gcf,'Color','white'); for is=1:ns, subplot(ns,1,is), plot(vtime,X(:,is)); wgmi1= min(X(:,is)) -2; wgma1= max(X(:,is)) +2; ylim([wgmi1, wgma1]); set(gca,'YTick',[-5 0 5]) % if(is~=ns), set(gca,'XTickLabel',''); end if(is==1), title([cName,': ICA fetal ECG']); end end end ecg=X; % raw derivative filter coefficients % 5ms before and after, 3ms in the middle nu=ceil(0.005 * fs); nz=floor(0.0030*fs /2)*2 +1; % nz=nearest odd value B=[ones(nu,1);zeros(nz,1);-ones(nu,1)]; delay=floor(length(B)/2); % --- compute the derivative signal ecgfx=[repmat(ecg(1,:),delay,1);ecg;repmat(ecg(end,:),delay,1)]; decgr=filter(B,1,ecgfx); decgr= decgr(2*delay+1:end,:); if(dbFlag) figure; freqz(B,1,1000,fs); end % ----- Butterworth forward and backward bandpass filtered (1-6Hz) fmind=0.7; fmaxd=8; Wn = [fmind, fmaxd]/(fs/2); % normalized cut-off frequency (0,1) [b,a]= butter(1,Wn); decgBp=filtfilt(b,a,decgr); % ----- absolute derivative ------ adecg=abs(decgr); if(graph) figure, set(gcf,'Color','white'); wgmima= mimaxscG(adecg,0,0,0.1); for is=1:ns, subplot(ns,1,is), plot(vtime,adecg(:,is)); ylim(wgmima); % if(is~=ns), set(gca,'XTickLabel',''); end if(is==1), title([cName,': ECG absolute derivative']); end end end % ----- Butterworth forward and backward bandpass filtered (1-6Hz) fmind=0.7; fmaxd=8; Wn = [fmind, fmaxd]/(fs/2); % normalized cut-off frequency (0,1) [b,a]= butter(1,Wn); adecgBp=filtfilt(b,a,adecg); if(graph) figure, set(gcf,'Color','white'); wgmima= mimaxscG(adecgBp(fs:end-fs),0,0,0.1); for is=1:ns, subplot(ns,1,is), plot(vtime,adecgBp(:,is)); ylim(wgmima); % if(is~=ns), set(gca,'XTickLabel',''); end if(is==1), title([cName,': ECG filtered absolute derivative']); end end end % ---- if(learning), nQRSaf=length(qrsAfs); fprintf('Number of annotated fetal QRSs= %d\n', nQRSaf); RRafs= diff(qrsAfs); % annotated fetal RR end RRmc= diff(qrsM); RRms= RRmc/fs; % detected mother RR % % ------------------------------------------------------------------------------- qrsFs=cell(1,4); RRfs=cell(1,4); madRR = zeros(ns,1); rmssd = zeros(ns,1); for ic=1:ns, if(dbFlag && learning), PlotSgnMrkNc(xs, qrsAfs*fs, fs, [cName,' - eS']); end % ---- xic=X(:,ic); ecgic=ecg(:,ic); decgic=decgr(:,ic); decgBpic=decgBp(:,ic); adecgic=adecg(:,ic); adecgBpic=adecgBp(:,ic); % ---- QRS detection (first step) --------------------------------------------- % qrsM=QRSdetectorQeA2T(vadx,vdx,fs,pth,RRts,pmQT) pth=0.35; qrsFp=QRSdetectorF1(adecgBpic,decgic,fs,pth,0.45,0.8); % qrsFp1=qrsFp(1,:)'; if(dbFlag) fprintf('Number of pre-detected fetal QRSs= %d\n', length(qrsFp1)); end RRfc1= diff(qrsFp1); RRfs1= RRfc1/fs; % predetected fetal RR if(graph) figure, set(gcf,'Color','white'); hold on; plot(qrsMs, [RRms(1);RRms],'m.-'); if(learning), plot(qrsAfs, [RRafs(1);RRafs],'b.-'); end plot(qrsFp1/fs, [RRfs1(1);RRfs1],'r.-'); ylim([0.1, 1]); if(learning), title([cName,': detected mother(m), annotated (b) & pre-detected(r) fetal RR series']); else title([cName,': detected mother (m) & pre-detected fetal(r) RR series']); end figResize(0, 1, 1, .35); end % [nb,vp]=hist(RRfs,[0.25:0.05:0.9]); RRfsc1=sort(RRfs1); RRfsc1=RRfsc1(round(0.05*length(RRfsc1)):round(0.95*length(RRfsc1))); [nb,vp]=hist(RRfsc1); if(graph) figure, bar(vp,nb,1); title([cName,': pre-detected RR hist']); end [nbm,ibm]=max(nb); RRst=vp(ibm); % most frequent value adRR=abs(diff(RRfs1)); adRRx=[repmat(adRR(1),2,1);adRR;repmat(adRR(end),2,1)]; %adRRx=medfilt1(adRRx,5); mvadRR=filter(ones(1,5),1,adRRx); mvadRR= mvadRR(2*2+1:end,:); if(graph), figure, plot(qrsFp(1,3:end)'/fs,mvadRR); end thad=0.4; RRoklen=0; while(~RRoklen) for j=1:4 isRRok=[]; ieRRok=[]; jint=1; RRokFlag=0; for i=1:length(mvadRR) if(RRst-0.15), break; end thad=thad+0.1; end end RRsmi=mean(RRfs1(isRRok(im):ieRRok(im))); iqis=qrsFp(1,isRRok(im)); iqie=qrsFp(1,ieRRok(im)); sgnOklens=(iqie-iqis)/fs; pth=0.2; qrsFb=QRSdetectorF2(adecgBpic(iqie:-1:1),decgic(iqie:-1:1),fs,pth,RRst,RRsmi,sgnOklens,1); % backward qrsFb=iqie-qrsFb(end:-1:1); pth=0.2; qrsFf=QRSdetectorF2(adecgBpic(iqis:end),decgic(iqis:end),fs,pth,RRst,RRsmi,sgnOklens,0); % forward qrsFf=iqis+qrsFf; if(isempty(qrsFb)) qrsFic=qrsFf'; elseif(isempty(qrsFf)) qrsFic=qrsFb'; else ibe=min(find(qrsFb>qrsFf(1)-0.5*RRst*fs)); ifs=min(find(qrsFf>qrsFb(ibe)+0.5*RRst*fs)); qrsFic=[qrsFb(1:ibe), qrsFf(ifs:end)]'; end nQRSdf=length(qrsFic); if(dbFlag), fprintf('Channel=%d, number of detected fetal QRSs= %d\n', ic, nQRSdf); end qrsFsic=qrsFic/fs; RRfsic= diff(qrsFsic); % detected fetal RR [nb,vp]=hist(RRfsic,[0.25:0.05:0.9]); if(graph) figure, bar(vp,nb,1); title([cName,': detected RR hist']); end [nbm,ibm]=max(nb); RRst=vp(ibm); RRfmean=meansc(RRfsic,4,4); %RfRmean= mean(RRfsic); RRfstd= std(RRfsic); if(dbFlag), fprintf('fetal RR mean= %d, stdev=%d\n', RRfmean, RRfstd); end debugQRS=1; if(dbFlag && debugQRS) figure subplot(2,1,1),plot(adecgic,'b'); title([cName,': filter absolute derivative']); DrawVertMarker(qrsFic,'r',':','none'); % DrawVertMarker(qrsF(2,:)','b',':','none'); % DrawVertMarker(qrsF(end,:)','r',':','none'); subplot(2,1,2),plot(ecgic(:,1),'b'); title('ecg signal'); DrawVertMarker(qrsFic,'r',':','none'); % DrawVertMarker(qrsF(2,:)','b',':','none'); % DrawVertMarker(qrsF(end,:)','r',':','none'); end if(graph || saveFig) if(graph), figure; else figure('visible','off'); end set(gcf,'Color','white'); hold on; plot(qrsMs, [RRms(1);RRms],'m.-'); if(learning), plot(qrsAfs, [RRafs(1);RRafs],'b.-'); end plot(qrsFsic, [RRfsic(1);RRfsic],'r.-'); ylim([0.1, 1]); if(learning), title([cName,': ic=',num2str(ic),', detected mother(m), annotated (b) & detected(r) fetal RR series']); else title([cName,': ic=',num2str(ic),', detected mother (m) & fetal(r) RR series']); end figResize(0, 1, 1, .35); if(saveFigRRf), figFmtRR='png'; figPathRR='../Figure/'; if(~exist(figPathRR,'dir')), mkdir(figPathRR); end figName=fullfile(figPathRR,[cName,'_',num2str(ic),'_RRfetal']); print(gcf, ['-d',figFmtRR],figName); end end qrsFs{ic}=qrsFsic; RRfs{ic}=RRfsic; end % ----------------- choosing the best fetal RR series ----------------------- for ic=1:ns, meRR(ic) = median(RRfs{ic}); if(meRR(ic)<0.35), cfact(ic)=0.3*(0.35-meRR(ic)); elseif(meRR(ic)>0.5), cfact(ic)=0.05*(meRR(ic)-0.5); else cfact(ic)=0; end dRRfs=diff(RRfs{ic}); ddRRfs=diff(dRRfs); madRR(ic) = meansc(abs(dRRfs),0,5); maddRR(ic) = meansc(abs(ddRRfs),0,5); %rmssd(ic) = std(diff(RRfs{ic})); %dRRfsz=dRRfs-mean(dRRfs); rmssd(ic) = sqrt(meansc(dRRfsz.*dRRfsz,0,8)); cmpRes=QRSdet_ann_cmp(qrsFs{ic},qrsMs, 0.1, 0); %FMsim(ic)=cmpRes.nTP/(cmpRes.nFP+cmpRes.nFN); cFMsim(ic)=0.05*cmpRes.nTP/(0.05*cmpRes.nTP+cmpRes.nFP+cmpRes.nFN); fprintf('channel=%1d, meRR=%7.4f, madRR=%7.4f, maddRR=%7.4f, cfact=%7.4f, cFMsim=%7.4f\n', ... ic, meRR(ic), madRR(ic),maddRR(ic),cfact(ic),cFMsim(ic)); chanIqf(ic)=madRR(ic)+maddRR(ic)+cfact(ic)+cFMsim(ic); end for ic=1:ns, fprintf('channel=%1d, chanIqf=%7.4f\n', ic, chanIqf(ic)); end [dummy,icsV]=sort(chanIqf); % select the best channel ics=icsV(1); fprintf('Selected channel=%1d\n', ics); qrsFes=qrsFs{ics}; RRfes=RRfs{ics}; xs=X(:,ics); ecgs=ecg(:,ics); % decgs=decgr(:,ics); % adecgs=adecg(:,ics); if(graph) figure, set(gcf,'Color','white'); for is=1:ns, subplot(ns,1,is), plot(vtime, X(:,is)); wgmi1= min(X(:,is)) -2; wgma1= max(X(:,is)) +2; ylim([wgmi1, wgma1]); set(gca,'YTick',[-5 0 5]) % if(is~=ns), set(gca,'XTickLabel',''); end if(is==1), title([cName,': sorted fetal ECG']); end shg end end % ----------------------------------------------------------------------------- % if(dbFlag) PlotSgnMrkNc(ecgs, qrsF(1,:), fs, cName); end if(learning && graph), PlotSgnMrkNc(ecgs(:,1), {qrsAfs'*fs,qrsFe*fs}, fs, [cName,': fecg, ann.(m) & det.(b) qrs']); end if(saveFig) figName=fullfile(figPath,[cName,'_ECGfetal']); print(gcf, ['-d',figFmt],figName); end if(graph || saveFig) figure, set(gcf,'Color','white'); for is=1:ns, subplot(ns,1,is), plot(vtime, X(:,is)); wgmi1= min(X(:,is)) -2; wgma1= max(X(:,is)) +2; ylim([wgmi1, wgma1]); % DrawVertLines(vtime', qrsF(1,:)/fs, 'r', ylim); if(learning), DrawVertMarker(qrsAfs,'k',':','+'); end DrawVertMarker(qrsFes,'r',':','none'); set(gca,'YTick',[-5 0 5]) % if(is~=ns), set(gca,'XTickLabel',''); end if(is==1) if(learning), title([cName,': annotated (k) detected (r) fetal qrs']); else title([cName,': detected (r) fetal qrs']); end end end shg if(saveFig) figName=fullfile(figPath,[cName,'_fetal']); print(gcf, ['-d',figFmt],figName); end end %------------------------------------------------------------- if(graph || saveFigRRf) if(graph), figure; else figure('visible','off'); end set(gcf,'Color','white'); hold on; plot(qrsMs, [RRms(1);RRms],'m.-'); if(learning), plot(qrsAfs, [RRafs(1);RRafs],'b.-'); end plot(qrsFes, [RRfes(1);RRfes],'r.-'); ylim([0.1, 1]); if(learning), title([cName,': detected mother(m), annotated (b) & detected(r) fetal RR series']); else title([cName,': detected mother (m) & fetal(r) RR series']); end figResize(0, 1, 1, .35); if(saveFigRRf), figFmtRR='png'; figPathRR='../FigureRRf/'; if(~exist(figPathRR,'dir')), mkdir(figPathRR); end figName=fullfile(figPathRR,[cName,'_RRfetal']); print(gcf, ['-d',figFmtRR],figName); end end return end %== function ================================================================ % % ------------------------------------------------------------------------------------------------- % Routine to compare QRS detection annotation % % Author: Maurizio Varanini, Clinical Physiology Institute, CNR, Pisa, Italy % For any comment or bug report, please send e-mail to: maurizio.varanini@ifc.cnr.it % ------------------------------------------------------------------------------------------------- function cmpRes=QRSdet_ann_cmp(qrsD,qrsA, diffMax, debug) if(nargin<4) debug=0; end nqrsA=length(qrsA); nqrsD=length(qrsD); qrsA_TP=zeros(1,nqrsA); qrsA_diff=zeros(1,nqrsA); nTP = 0; % number of matching (TP) nFN = 0; % number of False Positive for i = 1:nqrsA TP=0; qrsAi=qrsA(i); iQs = min(find(qrsD > qrsAi)); iQp = max(find(qrsD <= qrsAi)); if(~isempty(iQs)) qrsDis = qrsD(iQs); diffQPs= qrsDis - qrsAi; if(diffQPs <= diffMax) TP=1; qrsA_TP(i) = qrsDis; qrsA_diff(i) = diffQPs; end end if(~isempty(iQp)) qrsDip = qrsD(iQp); diffQPp= qrsAi - qrsDip; if(diffQPp <= diffMax && diffQPp<=diffQPs) TP=1; qrsA_TP(i) = qrsDip; qrsA_diff(i) = -diffQPp; end end if (TP) nTP = nTP + 1; qrsA_T(i) = qrsAi; else nFN = nFN + 1; end end nFP = max(0, nqrsD - nTP); indTP=find(qrsA_TP>0); if(~isempty(indTP)) qrsA_diffTP=qrsA_diff(indTP); meanDiff=mean(qrsA_diffTP); else meanDiff=0; % patch end if(debug) fprintf('Number of mother QRSs= %d\n', nqrsA); fprintf('Number of fetal QRSs= %d\n', nqrsD); fprintf('Number of true positives= %d\n', nTP); fprintf('Number of false positives= %d\n', nFP); fprintf('Number of false negatives= %d\n', nFN); end cmpRes.nTP=nTP; cmpRes.nFP=nFP; cmpRes.nFN=nFN; cmpRes.meanDiff=meanDiff; return end %== function ================================================================ %