Noninvasive Fetal ECG: The PhysioNet/Computing in Cardiology Challenge 2013 1.0.0
(7,335 bytes)
function [sQRS,chNb] = select_channel(ecg,ecgType,varargin)
% this function automates the channel selection onto
% which to perform sQRS detection for whether to get MQRS or FQRS.
% In the case of FQRS extraction, the key ideas are to to use the regularity
% of the extracted HR to decide what FECG channel to choose and to use bxb
% (proportion of matching sQRS btw two sQRS time series) to check that we
% are not picking up the MECG.
%
% inputs
% ecg: the preprocessed FECG matrix
% ecgType: 'FECG' or 'MECG'
% varargin
% MQRS: MQRS position (in ms)
% paramStruct: structure containing challenge parameters
% outputs
% sQRS: selected QRS location (in ms)
% chNb: selected ABD channel
%
% FECG extraction toolbox, version 1.0, Sept 2013
% Released under the GNU General Public License
%
% Copyright (C) 2013 Joachim Behar
% Oxford university, Intelligent Patient Monitoring Group - Oxford 2013
% joachim.behar@eng.ox.ac.uk
%
% Last updated : 02-08-2013
%
% 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 in the hope that it will be useful, but
% WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
% Public License for more details.
% == managing inputs
MQRS = [];
paramStruct.detThres = 0.5;
paramStruct.fs = 1000;
paramStruct.qrsdet_wind = 15;
debug = 0;
switch nargin
case 3
MQRS = varargin{1};
case 4
MQRS = varargin{1};
paramStruct = varargin{2};
case 5
MQRS = varargin{1};
paramStruct = varargin{2};
debug = varargin{3};
otherwise
error('select_channel: wrong number of input arguments \n');
end
if size(ecg,2)>size(ecg,1)
ecg = ecg';
end
NB_AECG = size(ecg,2); % nb of ABD ecg
QRSce = cell(NB_AECG,1); % sQRS cell
sQRS = cell(1,1);
SMI = zeros(NB_AECG,1); % smoothness indices
bxb = zeros(NB_AECG,1); % matching btw MQRS and another QRS time series
try
% == core function
if strcmp(ecgType,'FECG')
% == FECG channel selection
for ch=1:NB_AECG
% Normalize residuals to avoid big bumps that crashed the FQRS detector (this showed improvement)
[ecgn,~] = normalise_ecg(ecg(:,ch),1,5*paramStruct.fs);
% sQRS detection
QRSce{ch} = run_qrsdet_by_seg(ecgn,paramStruct.fs,paramStruct.qrsdet_wind,paramStruct.detThres,ecgType);
% smooth the time series (physiological)
if paramStruct.smoothRR
QRSce{ch} = smooth_rr(QRSce{ch});
end
% compute smoothness indice
[SMI(ch),~] = assess_regularity(QRSce{ch},1);
% check that the FQRSs are not matching the MQRSs
bxb(ch) = bsqi(MQRS/paramStruct.fs,QRSce{ch}/paramStruct.fs);
end
% if time series too similar to the one from the mother then we are
% probably picking up the mother residuals. Then put SMI to inf to
% not select this channel
SMI(bxb>0.4) = Inf;
% in order to select the ABD channel onto which the FQRS will be
% identified we look at SMI (lower SMI -> most likely to be a good detection)
[~,ind_std_trans] = sort(SMI);
chNb = ind_std_trans(1);
sQRS = QRSce{chNb};
% == plots
if debug || isempty(nargout)
tm = 1/paramStruct.fs:1/fs:length(ecg)/paramStruct.fs;
[m,n] = size(ecg);
ax = zeros(m,1);
for pp=1:n
ax(pp) = subplot(ceil(n/2),2,pp); plot(tm,ecg(:,pp));
legend(strcat('SMI:',num2str(SMI(pp)),' bxb:',num2str(bxb(pp))));
linkaxes(ax,'x');
end
end
elseif strcmp(ecgType,'MECG')
% == MECG channel selection
% this step is performed to get more robust MQRS detection.
% This is aimed at finding the MQRS with the best accuracy but
% ALSO to distinguish between MQRS and FQRS time series since
% in some instances the FECG is of higher or similar amplitude
% that the MECG. In practice a chest electrode should be used
% to avoid this to happen as the chest signal will be of
% better quality and will ensure that there is no confusion
% between MQRS and FQRS time series.
[~,icaECG] = jade(ecg);
ecg = [ecg';icaECG]';
NB_AECG = 2*NB_AECG;
lengthQRS = zeros(4,1);
for i=1:NB_AECG
% run sQRS detector
sQRS{i} = run_qrsdet_by_seg(ecg(:,i),paramStruct.fs,10,0.6,ecgType);
lengthQRS(i) = length(sQRS{i});
% compute SMI of MHR at 96% CI
[SMI(i),~] = assess_regularity(sQRS{i});
end
if isempty(find(SMI~=inf,1))
[~,chNb] = min(lengthQRS);
sQRS = sQRS{chNb};
else
% this set of rules is specific to the PCinC2013
% challenge and should not be kept for practical
% applications
indLowSMI = find(SMI<10);
if length(indLowSMI)>1
[mi,indMin] = min(lengthQRS(indLowSMI));
[ma,indMax] = max(lengthQRS(indLowSMI));
if ma-mi>7
% if there is no big gap in length btw the 'good'
% time series then take the most regular
chNb = indLowSMI(indMin);
else
% if there is the gap then we might be detecting
% both the MQRS and FQRS in which case it is safer
% to assume that the MQRS is the shortest time
% series so take the longuest for the foetus
chNb = indLowSMI(indMax);
end
else
[~,chNb] = min(SMI);
end
sQRS = sQRS{chNb};
end
else
disp('Wrong entry \n');
end
catch ME
for enb=1:length(ME.stack); disp(ME.stack(enb)); end;
sQRS = [20 400 600 1000]; chNb=1;
end
end
% alternative to bSQI is to use the Physionet bxb function but this requires converting
% .mat annotations to WFDB format. This is unfortunately taking a lot of computational time!!
% (0.116627sec)
%
% mat2wfdbanns('qrs','fqrs',QRSce{ch},paramStruct.fs);
% mat2wfdbanns('qrs','mqrs',MQRS,paramStruct.fs);
% bxb('qrs','mqrs','fqrs','bxbReport.txt','00:00:00','00:01:00');
% testFil = importdata('bxbReport.txt');
% delete('bxbReport.txt');
% bxb(ch) = testFil.data(1,1)/(testFil.data(1,1)+testFil.data(6,1));