ECG-Kit 1.0
(18,812 bytes)
classdef ECGtask_ECG_delineation < ECGtask
% ECGtask for ECGwrapper (for Matlab)
% ---------------------------------
%
% Description:
%
% Abstract class for defining ECGtask interface
%
% Adding user-defined QRS detectors:
% A QRS detector that has the following interface can be added to the task:
%
% [positions_single_lead, position_multilead] = your_ECG_delineator( ECG_matrix, ECG_header, progress_handle, payload_in);
%
% where the arguments are:
% + ECG_matrix, is a matrix size [ECG_header.nsamp ECG_header.nsig]
% + ECG_header, is a struct with info about the ECG signal, such as:
% .freq, the sampling frequency
% .desc, description about the signals.
% + progress_handle, is a handle to a waitbar object, that can be used
% to track the progress within your function. See the
% documentation about this class in this kit.
% + payload_in, is a user data variable allowed to be sent each call to
% your function. It is sent, via the payload property of this
% class, for example:
%
% this_ECG_wrappers.ECGtaskHandle.payload = your_variable;
% this_ECG_wrappers.ECGtaskHandle.payload = {your_var1 your_var2};
% this_ECG_wrappers.ECGtaskHandle.payload = load(cached_filenames);
%
% In the context of delineation, it is thought to be a user
% corrected, or "gold quality" QRS location, in order to
% improve the wave delineation quality. If "payload_in" is a
% struct, this function will automatically filter and time-shift
% all QRS detection fields started with the string "corrected_".
% For this purpose, QRScorrector task, automatically appends this
% string to eachm anually reviewed QRS location series.
%
% the output of your function must be:
% + positions_single_lead, a cell array size ECG_header.nsig with the
% QRS sample locations found in each lead.
% + position_multilead, a numeric vector with the QRS locations
% calculated using multilead rules.
%
%
% Author: Mariano Llamedo Soria (llamedom at {electron.frba.utn.edu.ar; unizar.es}
% Version: 0.1 beta
% Birthdate : 18/2/2013
% Last update: 18/2/2013
properties(GetAccess = public, Constant)
name = 'ECG_delineation';
target_units = 'ADCu';
doPayload = true;
cAnnotationFields = { 'Pon' 'P' 'Poff' 'QRSon' 'qrs' 'Q' 'R' 'S' 'QRSoff' 'Ton' 'T' 'Toff' };
end
properties( GetAccess = public, SetAccess = private)
% if user = memory;
% memory_constant is the fraction respect to user.MaxPossibleArrayBytes
% which determines the maximum input data size.
memory_constant = 0.3;
started = false;
end
properties( Access = private, Constant)
cQRSdelineators = {'all-delineators' 'wavedet' };
end
properties( Access = private )
delineators2do
bWFDBdelineators
tmp_path_local
command_sep
WFDB_bin_path
lead_idx = [];
lead_names
end
properties
progress_handle
only_ECG_leads = false;
delineators = 'all-delineators';
wavedet_config
payload
tmp_path
end
methods
function obj = ECGtask_ECG_delineation(obj)
% wavedet configuration structure
obj.wavedet_config.setup.wavedet.QRS_detection_only = false;
end
function Start(obj, ECG_header, ECG_annotations)
if( obj.only_ECG_leads )
obj.lead_idx = get_ECG_idx_from_header(ECG_header);
else
% 'all-leads'
obj.lead_idx = 1:ECG_header.nsig;
end
% lead names desambiguation
str_aux = regexprep(cellstr(ECG_header.desc), '\W*(\w+)\W*', '$1');
obj.lead_names = regexprep(str_aux, '\W', '_');
[str_aux2, ~ , aux_idx] = unique( obj.lead_names );
aux_val = length(str_aux2);
if( aux_val ~= ECG_header.nsig )
for ii = 1:aux_val
bAux = aux_idx==ii;
aux_matches = sum(bAux);
if( sum(bAux) > 1 )
obj.lead_names(bAux) = strcat( obj.lead_names(bAux), repmat({'v'}, aux_matches,1), cellstr(num2str((1:aux_matches)')) );
end
end
end
obj.lead_names = regexprep(obj.lead_names, '\W*(\w+)\W*', '$1');
obj.lead_names = regexprep(obj.lead_names, '\W', '_');
if( strcmpi('all-delineators', obj.delineators) )
obj.delineators2do = obj.cQRSdelineators(2:end);
else
if( ischar(obj.delineators) )
obj.delineators2do = {obj.delineators};
else
obj.delineators2do = obj.delineators;
end
end
% local path required to avoid network bottlenecks in distributed filesystems
if( isunix() && exist('/scratch/', 'dir') )
str_username = getenv('USER');
obj.tmp_path_local = ['/scratch/' str_username filesep];
if( ~exist(obj.tmp_path_local, 'dir') )
if(~mkdir(obj.tmp_path_local))
obj.tmp_path_local = '/scratch/';
end
end
obj.tmp_path = tempdir;
else
if( isempty(obj.tmp_path) )
obj.tmp_path = tempdir;
obj.tmp_path_local = tempdir;
end
end
obj.started = true;
end
function payload_out = Process(obj, ECG, ECG_start_offset, ECG_sample_start_end_idx, ECG_header, ECG_annotations, ECG_annotations_start_end_idx )
payload_out = [];
if( ~obj.started )
obj.Start(ECG_header);
if( ~obj.started )
cprintf('*[1,0.5,0]', 'Task %s unable to be started for %s.\n', obj.name, ECG_header.recname);
return
end
end
% payload property is used in this task to input an external QRS
% detector, or manually corrected detections.
if( isstruct(obj.payload) )
fnames = fieldnames(obj.payload);
aux_idx = find(cell2mat( cellfun(@(a)(~isempty(strfind(a, 'corrected_'))), fnames, 'UniformOutput', false)));
aux_struct = obj.payload;
if( isempty(aux_idx) )
if( isfield(aux_struct, 'series_quality') )
[~, aux_idx] = sort(aux_struct.series_quality.ratios, 'descend');
aux_val = aux_struct.(aux_struct.series_quality.AnnNames{aux_idx(1),1}).(aux_struct.series_quality.AnnNames{aux_idx(1),2}) - ECG_start_offset + 1;
aux_val = aux_val( aux_val >= ECG_sample_start_end_idx(1) & aux_val < ECG_sample_start_end_idx(2) );
aux_struct = aux_val;
else
for fname = rowvec(fnames)
if( isfield(aux_struct.(fname{1}), 'time') )
aux_val = aux_struct.(fname{1}).time - ECG_start_offset + 1;
aux_val = aux_val( aux_val >= ECG_sample_start_end_idx(1) & aux_val < ECG_sample_start_end_idx(2) );
aux_struct = aux_val;
break
end
end
end
else
for ii = rowvec(aux_idx)
aux_val = aux_struct.(fnames{ii}).time - ECG_start_offset + 1;
aux_val = aux_val( aux_val >= ECG_sample_start_end_idx(1) & aux_val < ECG_sample_start_end_idx(2) );
aux_struct = aux_val;
end
end
else
aux_struct = [];
end
cant_QRSdelineators = length(obj.delineators2do);
for ii = 1:cant_QRSdelineators
this_delineator = obj.delineators2do{ii};
[this_delineator, this_delineator_name] = strtok(this_delineator, ':');
if( isempty(this_delineator_name) )
this_delineator_name = this_delineator;
else
this_delineator_name = this_delineator_name(2:end);
end
cprintf( 'Blue', [ 'Processing ECG delineator ' this_delineator_name '\n' ] );
%% perform ECG delineation
switch( this_delineator )
case 'wavedet'
%% Wavedet delineation
obj.progress_handle.checkpoint(['Processing ' this_delineator])
try
[position_multilead, positions_single_lead] = wavedet_interface(ECG, ECG_header, aux_struct, obj.lead_idx, obj.wavedet_config, ECG_sample_start_end_idx, ECG_start_offset, obj.progress_handle);
for jj = 1:length(obj.lead_idx)
payload_out.wavedet.(obj.lead_names{obj.lead_idx(jj)}) = positions_single_lead(jj);
end
payload_out.wavedet.multilead = position_multilead;
catch aux_ME
strAux = sprintf('Wavedet failed in recording %s\n', ECG_header.recname);
strAux = sprintf('%s\n', strAux);
report = getReport(aux_ME);
fprintf(2, '%s\nError report:\n%s', strAux, report);
end
case 'user'
%% user-defined delineator
obj.progress_handle.checkpoint(['Processing ' this_delineator_name])
try
if( exist(this_delineator_name) == 2 )
% ud_func_pointer = eval(['@' this_delineator_name]);
ud_func_pointer = str2func(this_delineator_name);
obj.progress_handle.checkpoint([ 'User defined function: ' this_delineator_name])
ECG_header_aux = trim_ECG_header(ECG_header, obj.lead_idx);
[positions_single_lead, position_multilead] = ud_func_pointer( double(ECG(:,obj.lead_idx)), ECG_header_aux, obj.progress_handle, aux_struct);
% filter and offset delineation
for jj = 1:length(obj.lead_idx)
% filter heartbeats within range
bAux = positions_single_lead(jj).qrs >= ECG_sample_start_end_idx(1) & positions_single_lead(jj).qrs <= ECG_sample_start_end_idx(2);
aux_struct = [];
for fn = rowvec(fieldnames(positions_single_lead(jj)))
aux_val = positions_single_lead(jj).(fn{1});
if( any(strcmpi(obj.cAnnotationFields, fn{1})) )
aux_struct.(fn{1}) = aux_val(bAux) + ECG_start_offset - 1;
else
aux_struct.(fn{1}) = aux_val;
end
end
payload_out.(this_delineator_name).(obj.lead_names{obj.lead_idx(jj)}) = aux_struct;
end
if( ~isempty(position_multilead) )
% filter heartbeats within range
bAux = position_multilead.qrs >= ECG_sample_start_end_idx(1) & position_multilead.qrs <= ECG_sample_start_end_idx(2);
aux_struct = [];
for fn = rowvec(fieldnames(position_multilead))
aux_val = position_multilead.(fn{1});
if( any(strcmpi(obj.cAnnotationFields, fn{1})) )
aux_struct.(fn{1}) = aux_val(bAux) + ECG_start_offset - 1;
else
aux_struct.(fn{1}) = aux_val;
end
end
position_multilead = aux_struct;
payload_out.(this_delineator_name).multilead = position_multilead;
end
else
disp_string_framed(2, sprintf('Function "%s" is not reachable in path.', this_delineator_name));
fprintf(1, 'Make sure that exist(%s) == 2\n',this_delineator_name);
end
catch aux_ME
disp_string_framed(2, sprintf('Delineator "%s" failed in recording %s lead %s', this_delineator_name, ECG_header.recname, ECG_header.desc(jj,:) ) );
report = getReport(aux_ME);
fprintf(2, 'Error report:\n%s', report);
end
end
end
end
function payload = Finish(obj, payload, ECG_header)
end
function payload = Concatenate(obj, plA, plB)
if( isempty(plA) )
payload = plB;
else
for this_ECG_delineator = rowvec(fieldnames(plA))
this_ECG_delineator = this_ECG_delineator{1};
if( isfield(plB, this_ECG_delineator) )
for this_lead = rowvec(fieldnames(plA.(this_ECG_delineator)))
this_lead = this_lead{1};
if( isfield(plB.(this_ECG_delineator), this_lead) )
for fn = rowvec(fieldnames(plA.(this_ECG_delineator).(this_lead) ))
if( isfield(plB.(this_ECG_delineator).(this_lead), fn{1}) )
payload.( this_ECG_delineator).(this_lead).(fn{1}) = [ colvec(plA.( this_ECG_delineator).(this_lead).(fn{1}) ); colvec( plB.(this_ECG_delineator).(this_lead).(fn{1}) ) ];
else
error('ECGtask_ECG_delineation:BadTMPfiles', ['Results from ' this_ECG_delineator '.' this_lead '.' fn{1} ' not found. Skipping concatenation.'])
end
end
else
error('ECGtask_ECG_delineation:BadTMPfiles', ['Results from ' this_ECG_delineator '.' this_lead ' not found. Skipping concatenation.'] )
end
end
else
error('ECGtask_ECG_delineation:BadTMPfiles', ['Results from ' this_ECG_delineator ' not found. Skipping concatenation.'] )
end
end
end
end
%% property restriction functions
function set.delineators(obj,x)
if( (ischar(x) || iscellstr(x)) )
x = cellstr(x);
aux_val = colvec(intersect(obj.cQRSdelineators, x));
aux_idx = find(cellfun(@(a)(~isempty(strfind(a, 'user:'))), x));
obj.delineators = [aux_val; colvec(x(aux_idx)) ];
else
warning('ECGtask_ECG_delineation:BadArg', 'Invalid delineators.');
end
end
function set.tmp_path_local(obj,x)
if( ischar(x) )
if(exist(x, 'dir'))
obj.tmp_path_local = x;
else
if(mkdir(x))
obj.tmp_path_local = x;
else
warning('ECGtask_ECG_delineation:BadArg', ['Could not create ' x ]);
end
end
else
warning('ECGtask_ECG_delineation:BadArg', 'tmp_path_local must be a string.');
end
end
function set.tmp_path(obj,x)
if( ischar(x) )
if(exist(x, 'dir'))
obj.tmp_path = x;
else
if(mkdir(x))
obj.tmp_path = x;
else
warning('ECGtask_ECG_delineation:BadArg', ['Could not create ' x ]);
end
end
else
warning('ECGtask_ECG_delineation:BadArg', 'tmp_path_local must be a string.');
end
end
end
methods ( Access = private )
end
end