classdef ECGtask_PPG_ABP_detector < ECGtask % ECGtask for ECGwrapper (for Matlab) % --------------------------------- % % Description: % % Abstract class for defining ECGtask interface % % % Author: Mariano Llamedo Soria (llamedom at {electron.frba.utn.edu.ar; unizar.es} % Version: 0.1 beta % Birthdate : 18/7/2014 % Last update: 18/7/2014 properties(GetAccess = public, Constant) name = 'PPG_ABP_detector'; target_units = 'ADCu'; doPayload = true; 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) cPPGdetectors = {'all-detectors' 'wavePPG' 'wabp' }; cLeadConfiguration = {'all-leads' 'PPG-ABP-only' 'User-defined-leads' }; end properties( Access = private ) detectors2do bWFDBdetectors tmp_path_local WFDB_bin_path WFDB_cmd_prefix_str end properties progress_handle tmp_path detectors = 'all-detectors'; lead_config = 'PPG-ABP-only'; PPG_ABP_idx = []; end methods function obj = ECGtask_PPG_ABP_detector(obj) end function Start(obj, ECG_header, ECG_annotations) if( strcmpi('all-leads', obj.lead_config) ) obj.lead_config = 'all-leads'; obj.PPG_ABP_idx = 1:ECG_header.nsig; elseif( strcmpi('PPG-ABP-only', obj.lead_config) ) obj.PPG_ABP_idx = get_PPG_ABP_idx_from_header(ECG_header); else if( isempty(obj.PPG_ABP_idx) || ~isnumeric(obj.PPG_ABP_idx) || ~(all(obj.PPG_ABP_idx > 0) && all(obj.PPG_ABP_idx <= ECG_header.nsig)) ) warning('ECGtask_QRS_detection:BadArg', 'Invalid lead indexes. Indexes between 1 and %d\n', ECG_header.nsig); obj.lead_config = 'all-leads'; obj.PPG_ABP_idx = 1:ECG_header.nsig; else obj.lead_config = 'User-defined-leads'; end end if( isempty(obj.PPG_ABP_idx) ) return end if( strcmpi('all-detectors', obj.detectors) ) obj.detectors2do = obj.cPPGdetectors(2:end); else if( ischar(obj.detectors) ) obj.detectors2do = {obj.detectors}; else obj.detectors2do = obj.detectors; end end obj.bWFDBdetectors = any(strcmpi('wabp', obj.cPPGdetectors)); % 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; end obj.tmp_path_local = obj.tmp_path; end obj.WFDB_bin_path = init_WFDB_library(obj.tmp_path_local); obj.WFDB_cmd_prefix_str = WFDB_command_prefix(obj.tmp_path_local); obj.started = true; end function payload = Process(obj, PPG_ABP, ECG_start_offset, ECG_sample_start_end_idx, ECG_header, ECG_annotations, ECG_annotations_start_end_idx, payload_in ) payload = []; 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 if(obj.bWFDBdetectors) % MIT conversion is needed for WFDB detectors. ECG_header.recname(ECG_header.recname == ' ') = '_'; MIT_filename = [ECG_header.recname '_' num2str(ECG_start_offset) '_' num2str(ECG_header.nsamp+ECG_start_offset-1) ]; ECG_header.recname = MIT_filename; MIT_filename = [obj.tmp_path_local MIT_filename '.dat']; fidECG = fopen(MIT_filename, 'w'); try fwrite(fidECG, PPG_ABP', 'int16', 0 ); fclose(fidECG); catch MEE fclose(fidECG); rethrow(MEE); end writeheader(obj.tmp_path_local, ECG_header); end cant_PPG_ABP_detectors = length(obj.detectors2do); for ii = 1:cant_PPG_ABP_detectors this_detector = obj.detectors2do{ii}; cprintf( 'Blue', [ 'Processing PPG/ABP detector ' this_detector '\n' ] ); %% perform pulse detection switch( this_detector ) case 'wavePPG' %% wavePPG delineation for jj = rowvec(obj.PPG_ABP_idx) try PPG_ABP_peak_idx = PPG_pulses_detector( double(PPG_ABP(:,jj)), ECG_header.freq, obj.progress_handle ); PPG_ABP_peak_idx = PPG_ABP_peak_idx( PPG_ABP_peak_idx >= ECG_sample_start_end_idx(1) & PPG_ABP_peak_idx <= ECG_sample_start_end_idx(2)) + ECG_start_offset - 1 ; % QRS detections in milliseconds str_aux = strrep(strtrim(ECG_header.desc(jj,:)), ' ', '_' ); payload.(['wavePPG_' str_aux]).time = colvec(PPG_ABP_peak_idx); catch aux_ME strAux = sprintf('WavePPG 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 end case 'wabp' %% wabp WFDB interface file_name_orig = [obj.tmp_path_local ECG_header.recname '.wabp' ]; % QRS detection. for jj = rowvec(obj.PPG_ABP_idx) if( exist(file_name_orig, 'file') ) delete(file_name_orig) end try % run wabp [status, ~] = system([ obj.WFDB_cmd_prefix_str 'wabp -r ' ECG_header.recname ' -s ' num2str(jj-1)]); if( status ~= 0 ); error('ECGtask_PPG_detector:WFDB_error', 'Error calling WFDB function.'); end catch aux_ME strAux = sprintf( '%s failed in recording %s in %s\n', this_detector, ECG_header.recname, ECG_header.desc(jj,:) ); report = getReport(aux_ME); fprintf(2, '%s\nError report:\n%s', strAux, report); end if( exist(file_name_orig, 'file') ) % reference comparison anns_test = []; try anns_test = readannot(file_name_orig); if( isempty(anns_test) ) str_aux = strrep(strtrim(ECG_header.desc(jj,:)), ' ', '_' ); payload.([this_detector '_' str_aux ]).time = []; else anns_test = AnnotationFilterConvert(anns_test, 'MIT', 'AAMI'); PPG_ABP_peak_idx = anns_test.time; PPG_ABP_peak_idx = PPG_ABP_peak_idx( PPG_ABP_peak_idx >= ECG_sample_start_end_idx(1) & PPG_ABP_peak_idx <= ECG_sample_start_end_idx(2)) + ECG_start_offset - 1 ; str_aux = strrep(strtrim(ECG_header.desc(jj,:)), ' ', '_' ); payload.([this_detector '_' str_aux ]).time = colvec(PPG_ABP_peak_idx); end catch aux_ME if( strcmpi(aux_ME.identifier, 'MATLAB:nomem') ) payload.([this_detector '_' ECG_header.desc(jj,:)]).time = []; else strAux = sprintf( '%s failed in recording %s in %s\n', this_detector, ECG_header.recname, ECG_header.desc(jj,:) ); report = getReport(aux_ME); fprintf(2, '%s\nError report:\n%s', strAux, report); end end end end end end % Add QRS detections quality metrics, Names, etc. payload = calculateSeriesQuality(payload, ECG_header, [1 ECG_header.nsamp] + ECG_start_offset - 1 ); % delete intermediate tmp files if(obj.bWFDBdetectors) delete([obj.tmp_path_local ECG_header.recname '.*' ]); end end function payload = Finish(obj, payload, ECG_header) if( isfield(payload, 'series_quality') && isfield(payload.series_quality, 'ratios') ) payload.series_quality.ratios = mean(payload.series_quality.ratios, 2); end end function payload = Concatenate(obj, plA, plB) if( isempty(plA) ) payload = plB; else fields = rowvec(unique( [rowvec(fieldnames(plA)) rowvec(fieldnames(plB))] )); diff_fields = setdiff(fields, {'series_quality'}); for fn = diff_fields if( isfield(plA, fn{1}) && isfield(plB, fn{1}) ) payload.(fn{1}).time = [ colvec(plA.(fn{1}).time); colvec(plB.(fn{1}).time) ]; elseif( ~isfield(plA, fn{1}) && isfield(plB, fn{1}) ) payload.(fn{1}).time = colvec(plB.(fn{1}).time); end end aux_idx = find(strcmpi(fields, 'series_quality')); if( ~isempty(aux_idx) ) if( isfield(plA, 'series_quality') && isfield(plB, 'series_quality') ) if( size(plA.series_quality.ratios,1) == size(plB.series_quality.ratios,1) ) payload.series_quality.ratios = [plA.series_quality.ratios plB.series_quality.ratios]; payload.series_quality.estimated_labs = cellfun(@(a,b)( [colvec(a);colvec(b)] ) , plA.series_quality.estimated_labs, plB.series_quality.estimated_labs, 'UniformOutput', false); payload.series_quality.AnnNames = plA.series_quality.AnnNames; else payload.series_quality.ratios = plA.series_quality.ratios; payload.series_quality.estimated_labs = plA.series_quality.estimated_labs; payload.series_quality.AnnNames = plA.series_quality.AnnNames; end end end end end %% property restriction functions function set.detectors(obj,x) if( (ischar(x) || iscellstr(x)) && ~isempty(intersect(obj.cPPGdetectors, cellstr(x))) ) obj.detectors = x; else warning('ECGtask_QRS_detection:BadArg', 'Invalid detectors.'); 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_QRS_detection:BadArg', ['Could not create ' x ]); end end else warning('ECGtask_QRS_detection: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_QRS_detection:BadArg', ['Could not create ' x ]); end end else warning('ECGtask_QRS_detection:BadArg', 'tmp_path_local must be a string.'); end end end methods ( Access = private ) end end