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

File: <base>/sources/joachim.behar_at_gmail.com/subfunctions/select_channel.m (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));