%% Allow acces to ECG recordings of arbitrary format and length. % % Description: % ECG wrapper is a class to allow the access to cardiovascular signal % recordings of several formats (MIT, ISHNE, AHA, HES, MAT) and lengths, % from minutes to days. Also it can be plugged to an ECGtask object to % perform several tasks, such as QRS detection and ECG delineation among % others. % % Arguments: (specified as ECGwrapper('arg_name1', arg_val1, ... , 'arg_nameN', arg_valN) ) % % + recording_name : (char) the full filename of the ECG % recording. % % + recording_format : (char) the format of the ECG recording. By % default or if not specified, the wrapper will attemp to % auto-detect the format. % % + this_pid : (char) In case working in a multiprocess % environment, this value will identify the current process. % Can be a numeric value, or a string of the form 'N/M'. % This pid is N and the total amount of pid's to divide the % whole work is M. % % + tmp_path: (char) path to store the temp files. By default % will be the output of tempdir function. % % + output_path: (char) path to store the result files. By default % will be the same path of the recordings. % % + ECGtaskHandle: (char || ECGtask) The task to perform, can be % the name of the task, or an ECGtask object. Available % ECGtasks can be listed with list_all_ECGtask() command. % % + overlapping_time: (numeric) Time in seconds of overlapp among % consequtive segments. This segment is useful for ensuring % transitory responses of systems to be finished. Default is % 30 seconds. % % + partition_mode: (numeric) The way that this object will % partition lengthy signals: % - 'ECG_contiguous' no overlapp between segments % - 'ECG_overlapped' overlapp 'overlapping_time' between % segments. Default. % - 'QRS' do the partition based on the annotations in % ECG_annotations.time property. Typically but not % necessary are QRS annotations. % % + cacheResults: (logical) To save intermediate results to % recover in case of failure. Default is true. % % + syncSlavesWithMaster: (logical) In multiprocess environments % sometimes it is useful to terminate all pid's together in % orther to do further tasks later. Default is false. % % + repetitions: (numeric) In case the ECGtask is not % deterministic, the repetition property allows to repeat the % task several times. Default value is 1. % % % Output: % The constructed object. % % Examples: % % you can also check 'examples.m' in the same folder of this file. % % % See also ECGtask % % Author: Mariano Llamedo Soria llamedom@electron.frba.utn.edu.ar % Version: 0.1 beta % Last update: 14/5/2014 % Birthdate : 20/3/2012 % Copyright 2008-2015 % classdef ECGwrapper < handle properties(GetAccess = private, Constant) % all ECG formats that ECGkit can handle cKnownFormats = {'MIT' 'ISHNE', 'AHA', 'HES', 'MAT', 'Mortara', 'auto'}; % The fields required in the ECGheader property cHeaderFieldNamesRequired = {'freq' 'nsamp' 'nsig' 'gain' 'adczero' }; % Possible Partitions modes cPartitionModes = {'ECG_contiguous' 'ECG_overlapped' 'QRS'}; % The fields required in the ECGannotations property cAnnotationsFieldNamesRequired = {'time'}; % The methods required for an ECGtask object cObjMethodsRequired = {'Process' 'Start' 'Concatenate'}; % The properties required for an ECGtask object cObjPropsRequired = {'progress_handle' 'name' 'tmp_path'}; % maxQRSxIter: Maximum amount of heartbeats in ECG recordings of 2 leads maxQRSxIter = 5e3; % Minimum amount of heartbeats to be processed by a PID minQRSxIter = 1500; % Minimum amount of samples to be processed by a PID min_ECG_perPID = 432000; % 10*60*360*2 (10 minutes of 2-lead ECG@360Hz typical MITBIH arrhythmia rec) %gzip compression ratio typical_compression_ratio = 1.5; %times % maximum results file size payload_max_size = 2 * 1024^3; % bytes % maximum debugging email report file size max_report_filesize = 4 * 1024^2; % bytes % Maximum time to wait for other PIDs finishing their work. Time2WaitPIDs = 0 * 60; % seconds. end properties ( Access = private ) % private rec_filename % private rec_path % private common_path % private bHaveUserInterface % private Loops2io % private MaxNodesReading % private MaxNodesWriting % private QRS_locations % private bArgChanged = true; % private bECG_rec_changed = true; % private bCreated = false; % maxECGxIter: Maximum samples ECG maxECGxIter %list of handles in the toolbox cKnownECGtasksHdl % private cKnownECGtasks % private ErrorReport = []; % using annotations from ECG heartbeat classification task. bHB_task_annotations = false; end properties (SetAccess = private, GetAccess = public) % The filename generated as a result of the processing of the % ECGtask Result_files = []; % Flag check if the task generates a file payload as a result doPayload = true; end properties % The ECGtask generated any error ? Error % Was the ECGtask processed ? Processed % Had this pid work to do ? NoWork2Do % temporary path tmp_path % output path output_path % the full filename of the ECG recording recording_name % recording file format recording_format % annotations performed in the ECG (in MIT format) ECG_annotations % information about the ECG recording ECG_header % total amount of pid's cant_pids % identification of this pid this_pid % number of times to repeat the ECGtask repetitions % handle to the ECGtask object ECGtaskHandle % partition mode chosen partition_mode % time of overlapp among signal segments overlapping_time % is this caching ? cacheResults % are all pid's finishing the ECGtask together ? syncSlavesWithMaster % ECGannotations label format for heartbeat types. class_labeling = 'AAMI'; % user string to individualize each run user_string end methods function obj = ECGwrapper(varargin) %% % object constructor: parse arguments, check if user interface is % available %% Constants and definitions %Java user interface is started. Not started in clusters for example. obj.bHaveUserInterface = usejava('desktop'); if( obj.bHaveUserInterface ) % PC tyle, ignore this. obj.Loops2io = 1; obj.MaxNodesReading = inf; obj.MaxNodesWriting = inf; else %Cluster settings. Ignore them if running in a PC style computer. %Limits of the cluster to have multiple process accesing I/O obj.Loops2io = 100; obj.MaxNodesReading = 15; obj.MaxNodesWriting = 15; end % discover available ECG tasks installed [obj.cKnownECGtasks, obj.cKnownECGtasksHdl ] = list_all_ECGtask(); %% Argument parsing %argument definition p = inputParser; % Create instance of inputParser class. p.addParamValue('recording_name', [], @(x)( ischar(x) || isempty(x) )); aux_val = obj.cKnownFormats; p.addParamValue('recording_format', [], @(x)( ischar(x) && any(strcmpi(x,aux_val))) || isempty(x) ); p.addParamValue('this_pid', 1, @(x)(ischar(x) || (isnumeric(x) && all(x > 0) ) ) ); p.addParamValue('tmp_path', [], @(x)(ischar(x) || isempty(x) ) ); p.addParamValue('user_string', [], @(x)(ischar(x) || isempty(x)) ); p.addParamValue('output_path', [], @(x)(ischar(x) || isempty(x)) ); p.addParamValue('repetitions', 1, @(x)(isnumeric(x) && x > 0 ) ); p.addParamValue('ECGtaskHandle', ECGtask_do_nothing, @(x)( isobject(x) || ischar(x) ) ); p.addParamValue('overlapping_time', 30, @(x)(isnumeric(x) && x > 0 ) ); aux_val = obj.cPartitionModes; p.addParamValue('partition_mode', 'ECG_overlapped', @(x)( ischar(x) && any(strcmpi(x,aux_val))) ); p.addParamValue('cacheResults', true, @(x)(islogical(x)) ); p.addParamValue('syncSlavesWithMaster', false, @(x)(islogical(x)) ); try p.parse( varargin{:} ); catch MyError rethrow(MyError); end obj.recording_name = p.Results.recording_name; obj.recording_format = p.Results.recording_format; obj.this_pid = p.Results.this_pid; obj.tmp_path = p.Results.tmp_path; obj.output_path = p.Results.output_path; obj.repetitions = p.Results.repetitions; obj.ECGtaskHandle = p.Results.ECGtaskHandle; obj.overlapping_time = p.Results.overlapping_time; obj.partition_mode = p.Results.partition_mode; obj.cacheResults = p.Results.cacheResults; obj.syncSlavesWithMaster = p.Results.syncSlavesWithMaster; % Dont know why this variable uses a lot of bytes to store at disk. clear p obj.bCreated = true; obj.Processed = false; obj.NoWork2Do = false; obj.Error = false; end function result_payload = Run(obj) % Prepare and execute the ECGtask, creating a payload as a result result_payload = []; result_files = {}; repeat_idx = 1; obj.ErrorReport = []; obj.Processed = false; obj.NoWork2Do = false; obj.Error = false; if( obj.bArgChanged ) obj = obj.CheckArguments(); obj.bArgChanged = false; end fprintf(1, '\n'); cprintf( 'Blue', disp_option_enumeration( 'Description of the process:', { ['Recording: ' obj.recording_name] ['Task name: ' obj.ECGtaskHandle.name] } )); fprintf(1, '\n'); if( isempty(obj.user_string) ) aux_user_prefix = []; else aux_user_prefix= [obj.user_string '_']; end % check for cached results if( obj.cacheResults ) if( isprop(obj.ECGtaskHandle, 'signal_payload') ) % The results is a signal for arbitrary tasks, so check % the result signal instead MIT_filename = [ obj.rec_filename '_' aux_user_prefix obj.ECGtaskHandle.name ]; MIT_filename = regexprep(MIT_filename, '\W*(\w+)\W*', '$1'); MIT_filename = regexprep(MIT_filename, '\W', '_'); files_this_pid = dir([obj.output_path MIT_filename '*.dat']); else files_this_pid = dir([obj.output_path obj.rec_filename '_' aux_user_prefix obj.ECGtaskHandle.name '*.mat']); end if( ~isempty(files_this_pid) ) obj.Processed = true; obj.NoWork2Do = true; obj.Result_files = strcat(obj.output_path, {files_this_pid(:).name}); cellfun(@(a)(cprintf('[1,0.5,0]','Cached results found in %s.\n', a)), obj.Result_files); return end end overlapping_samples = obj.overlapping_time * obj.ECG_header.freq; while ( repeat_idx <= obj.repetitions ) try %% work starts here %% Prepare jobs to perform. % Activate the progress_struct bar. pb = progress_bar([obj.rec_filename ': ' obj.ECGtaskHandle.name ]); % Initialization of the process. obj.ECGtaskHandle.progress_handle = pb; obj.ECGtaskHandle.tmp_path = obj.tmp_path; obj.ECGtaskHandle.Start( obj.ECG_header, obj.ECG_annotations ); if( obj.ECGtaskHandle.started ) %% % find out the ammount of memory in the system if( ispc() ) user = memory(); else % empirical value from a x64 platform. user.MaxPossibleArrayBytes = 9.3784e+09; end % upper bound of 2 mins @ 12-leads 1000 Hz obj.maxECGxIter = round(obj.ECGtaskHandle.memory_constant*(min(2*60*60*12*1000,user.MaxPossibleArrayBytes/8))); % double = 8 bytes if( strcmpi(obj.partition_mode, 'QRS') ) %% QRS mode % this annotations are qrs locations provided % to the task, result of previous QRS % detections / correction, that were parsed in % the Start method. if( isprop(obj.ECGtaskHandle, 'annotations') && ~isempty(obj.ECGtaskHandle.annotations) ) % take precedence to QRS locations provided % with the signal. obj.QRS_locations = obj.ECGtaskHandle.annotations; obj.bHB_task_annotations = true; else obj.bHB_task_annotations = false; end if( isprop(obj.ECGtaskHandle, 'min_heartbeats_required') ) aux_min_QRS_iter = obj.ECGtaskHandle.min_heartbeats_required; else aux_min_QRS_iter = obj.minQRSxIter; end if( isprop(obj.ECGtaskHandle, 'max_heartbeats_per_iter') ) aux_max_QRS_iter = obj.ECGtaskHandle.max_heartbeats_per_iter; else aux_max_QRS_iter = obj.maxQRSxIter; end cant_QRS_locations = length(obj.QRS_locations); %PID parsing if( obj.cant_pids > 1 ) max_recommended_cant_pids = max(1, floor( cant_QRS_locations / aux_min_QRS_iter )); if( obj.cant_pids > max_recommended_cant_pids ) warning('ECGwrapper:TooMuchPIDs', 'CantPIDs too large for the work to do, consider decreasing it.'); obj.cant_pids = max_recommended_cant_pids; end [pid_starts, pid_ends] = TaskPartition( cant_QRS_locations, obj.cant_pids); if( obj.this_pid <= obj.cant_pids ) QRS_start_idx = pid_starts(obj.this_pid); QRS_end_idx = pid_ends(obj.this_pid); else % Extra PIDs warning('ECGwrapper:TooMuchPIDs', 'This PID has nothing to do. Exiting.'); return end else %Only one PID QRS_start_idx = 1; QRS_end_idx = cant_QRS_locations; end cant_QRS2do = QRS_end_idx - QRS_start_idx + 1; if( cant_QRS2do > aux_max_QRS_iter ) warning('ECGwrapper:TooFewPIDs', 'CantPIDs is too small for the work to do, consider increasing it.'); end cant_samples= obj.QRS_locations(QRS_end_idx) - obj.QRS_locations(QRS_start_idx); cant_iter = ceil(cant_samples * obj.ECG_header.nsig / obj.maxECGxIter); %calculate iters. [iter_starts, iter_ends] = TaskPartition( cant_QRS2do, cant_iter); % check that none of the iterations exceed the % memory quota cant_samples_each_iter = obj.QRS_locations(iter_ends) - obj.QRS_locations(iter_starts) + 1; iter_exceeded_idx = find( cant_samples_each_iter > (obj.maxECGxIter/obj.ECG_header.nsig) ); if( ~isempty(iter_exceeded_idx ) ) aux_jj_start = 1; aux_iter_starts = []; aux_iter_ends = []; for jj= rowvec(iter_exceeded_idx) aux_iter_starts = [aux_iter_starts ; iter_starts(aux_jj_start:jj-1)]; aux_iter_ends = [aux_iter_ends; iter_ends(aux_jj_start:jj-1)]; aux_jj_start = jj+1; aux_cant_this_iter = ceil(cant_samples_each_iter(jj) * obj.ECG_header.nsig / obj.maxECGxIter ); [aux_sample_starts, aux_sample_ends] = TaskPartition( cant_samples_each_iter(jj), aux_cant_this_iter); aux_starts = [ ]; aux_ends = [ ]; for kk = 1:length(aux_sample_starts) bAux = obj.QRS_locations >= aux_sample_starts(kk) + obj.QRS_locations(iter_starts(jj)) - 1 & obj.QRS_locations <= aux_sample_ends(kk) + obj.QRS_locations(iter_starts(jj)) - 1; aux_starts = [ aux_starts; find( bAux, 1, 'first' ) ]; aux_ends = [ aux_ends ; find( bAux, 1, 'last' )]; end aux_iter_starts = [aux_iter_starts ; ( aux_starts )]; aux_iter_ends = [aux_iter_ends; ( aux_ends )]; end aux_iter_starts = [aux_iter_starts ; iter_starts(aux_jj_start:cant_iter)]; aux_iter_ends = [aux_iter_ends; iter_ends(aux_jj_start:cant_iter)]; iter_starts = aux_iter_starts; iter_ends = aux_iter_ends; cant_iter = max(1, length(iter_starts)); end else %% ECG modes %PID parsing if( obj.cant_pids > 1 ) max_recommended_cant_pids = max(1, round( obj.ECG_header.nsamp * obj.ECG_header.nsig / obj.min_ECG_perPID )); if( obj.cant_pids > max_recommended_cant_pids ) warning('ECGwrapper:TooMuchPIDs', 'CantPIDs too large for the work to do, consider decreasing it.'); obj.cant_pids = max_recommended_cant_pids; end % make a partition of the ECG [pid_starts, pid_ends] = TaskPartition( obj.ECG_header.nsamp, obj.cant_pids); if( obj.this_pid <= obj.cant_pids ) ECG_start_idx = pid_starts(obj.this_pid); ECG_end_idx = pid_ends(obj.this_pid); else % Extra PIDs warning('ECGwrapper:TooMuchPIDs', 'This PID has nothing to do. Exiting.'); return end else %Only one PID ECG_start_idx = 1; ECG_end_idx = obj.ECG_header.nsamp; end % calculate iterations cant_samples2do = ECG_end_idx - ECG_start_idx + 1; cant_iter = max(1, ceil(cant_samples2do * obj.ECG_header.nsig / obj.maxECGxIter )); [iter_starts, iter_ends] = TaskPartition( cant_samples2do, cant_iter); end if( obj.this_pid > obj.cant_pids ) %este pid no trabaja. if(obj.bHaveUserInterface) cprintf('*[1,0.5,0]', 'This PID %d will not work, consider decreasing obj.cant_pids to %d.\n', obj.this_pid, obj.cant_pids); else fprintf(2, 'This PID %d will not work, consider decreasing obj.cant_pids to %d.\n', obj.this_pid, obj.cant_pids); end return end % Update point pb.checkpoint('Initializing'); %% Iterations over the whole ECG recording start_iter = 1; payload_files = []; %check for previous iterations already done, and try to restore. if( cant_iter > 1 ) % look for the previous cached point to continue for ii = 1:cant_iter aux_filename = [obj.output_path 'tmpfile_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_payload_cantpids_' num2str(obj.cant_pids) '_thispid_' num2str(obj.this_pid) '_iteration_' num2str(ii) '_of_' num2str(cant_iter) '.mat']; if( exist(aux_filename, 'file') ) payload_files = [ payload_files; cellstr(aux_filename)]; start_iter = ii+1; else break end end else aux_filename = [obj.output_path 'tmpfile_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_payload_cantpids_' num2str(obj.cant_pids) '_thispid_' num2str(obj.this_pid) '_iteration_1_of_1.mat']; if( exist(aux_filename, 'file') ) payload_files = [ payload_files; cellstr(aux_filename)]; start_iter = 2; obj.NoWork2Do = true; cprintf('*[1,0.5,0]', 'All work done. Exiting.\n'); end end pb.Loops2Do = cant_iter; for this_iter = start_iter:cant_iter %% loop initialization % por q deberia saberlo ? %obj.ECGtaskHandle.this_iteration = this_iter; %start of the progress_struct loop 0% pb.start_loop(); if( strcmpi(obj.partition_mode, 'QRS') ) %% la partici�n se hizo en la cantidad de latidos this_iter_QRS_start_idx = QRS_start_idx - 1 + iter_starts(this_iter); this_iter_QRS_end_idx = QRS_start_idx - 1 + iter_ends(this_iter); this_iter_ECG_start_idx = max(1, obj.QRS_locations(this_iter_QRS_start_idx) - overlapping_samples); this_iter_ECG_end_idx = min(obj.ECG_header.nsamp, obj.QRS_locations(this_iter_QRS_end_idx) + overlapping_samples); if( obj.bHB_task_annotations ) if( ~isempty(obj.QRS_locations) ) this_ann.time = obj.QRS_locations(this_iter_QRS_start_idx:this_iter_QRS_end_idx) - this_iter_ECG_start_idx + 1; end else % create an annotation struct for this % iteration. this_ann = obj.ECG_annotations; if( ~isempty(this_ann) ) for field_names = rowvec(fieldnames(this_ann)) if( ~isempty( this_ann.(field_names{1}) ) ) this_ann.(field_names{1}) = this_ann.(field_names{1})(this_iter_QRS_start_idx:this_iter_QRS_end_idx); end end this_ann.time = this_ann.time - this_iter_ECG_start_idx + 1; end end % in QRS mode, this is not useful. this_iter_ECG_relative_start_end_idx = [1 (this_iter_ECG_end_idx - this_iter_ECG_start_idx + 1)]; else %% la partici�n se hizo en el registro de ECG this_iter_QRS_start_idx = nan; this_iter_QRS_end_idx = nan; this_iter_cant_QRS = nan; this_iter_ECG_start_idx = max(1, ECG_start_idx - 1 + iter_starts(this_iter) - overlapping_samples); this_iter_ECG_end_idx = min(obj.ECG_header.nsamp, ECG_start_idx - 1 + iter_ends(this_iter) + overlapping_samples); % create an annotation struct for this % iteration. this_ann = obj.ECG_annotations; if( ~isempty(this_ann) ) bAux = this_ann.time > this_iter_ECG_start_idx & this_ann.time < this_iter_ECG_end_idx; for field_names = rowvec(fieldnames(this_ann)) if( ~isempty( this_ann.(field_names{1}) ) ) aux_val = this_ann.(field_names{1}); this_ann.(field_names{1}) = aux_val( bAux ); end end this_ann.time = this_ann.time - this_iter_ECG_start_idx + 1; end %Sample where the ECG starts. Useful for %overlapped mode. this_iter_ECG_relative_start_end_idx = [(ECG_start_idx - 1 + iter_starts(this_iter) - this_iter_ECG_start_idx + 1), (ECG_start_idx - 1 + iter_ends(this_iter) - this_iter_ECG_start_idx + 1) ]; end % create a header struct for this iteration. this_header = obj.ECG_header; this_header.nsamp = this_iter_ECG_end_idx - this_iter_ECG_start_idx + 1; %% ECG Recording reading % Update point pb.checkpoint('ECG Recording reading'); if(strcmpi(obj.ECGtaskHandle.target_units, 'Wrapper') ) % pass this same object to the target class. ECG = obj; else %% read from file ECG = read_ECG(obj.recording_name, this_iter_ECG_start_idx, this_iter_ECG_end_idx, obj.recording_format ); %convert ADC samples (int16) to obj.ECGtaskHandle.target_units volts. if( ~isempty(obj.ECGtaskHandle.target_units) ) if(strcmpi(obj.ECGtaskHandle.target_units, 'ADCu') ) if( any(any((ECG - fix(ECG)) < -(2^15-1))) || any(any((ECG - fix(ECG))) > 2^15 ) || any(any((ECG - fix(ECG)) ~= 0)) ) % outside int16 range -> match range % non-integer values ECG = bsxfun( @minus, ECG, rowvec(median(ECG)) ); aux_val = max(abs(ECG)); ECG = round(bsxfun( @times, ECG, 2^15 ./ rowvec(aux_val) )); end ECG = int16(ECG); else [ECG this_header] = ADC2units(double(ECG), this_header, obj.ECGtaskHandle.target_units); end end end %% User defined function calculation % Update point pb.checkpoint('User function'); % if( strcmpi(obj.partition_mode, 'QRS') ) % fprintf(1, 'ECG from: %d - %d\n', obj.QRS_locations(this_iter_QRS_start_idx) , obj.QRS_locations(this_iter_QRS_end_idx) ); % else % fprintf(1, 'ECG from: %d - %d\n', this_iter_ECG_start_idx + this_iter_ECG_relative_start_end_idx - 1); % end %user-defined execution according to 'process' payload = obj.ECGtaskHandle.Process(ECG, this_iter_ECG_start_idx, this_iter_ECG_relative_start_end_idx, this_header, this_ann, [this_iter_QRS_start_idx this_iter_QRS_end_idx] ); if( ~isempty(payload) ) if( obj.ECGtaskHandle.doPayload ) % Update point pb.checkpoint('Saving results'); % save results save([obj.tmp_path 'tmpfile_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_payload_cantpids_' num2str(obj.cant_pids) '_' num2str(obj.this_pid) '_' num2str(this_iter) '_' num2str(cant_iter) '.mat'], '-struct', 'payload'); % rename results. auxStr = [obj.tmp_path 'tmpfile_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_payload_cantpids_' num2str(obj.cant_pids) '_thispid_' num2str(obj.this_pid) '_iteration_' num2str(this_iter) '_of_' num2str(cant_iter) '.mat']; movefile( [obj.tmp_path 'tmpfile_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_payload_cantpids_' num2str(obj.cant_pids) '_' num2str(obj.this_pid) '_' num2str(this_iter) '_' num2str(cant_iter) '.mat'], ... auxStr, 'f' ); payload_files = [ payload_files; cellstr(auxStr)]; end end pb.end_loop(); end % move to the destination folder if needed if( ~strcmpi(obj.tmp_path, obj.output_path) ) pause_time = 1; for jj = 1:length(payload_files) % sometimes this file move takes long % time, specially in distributed % filesystems. tic_id = tic; if( ~strcmp(fileparts(payload_files{jj}), fileparts(obj.output_path)) ) movefile( payload_files{jj}, obj.output_path, 'f' ); end time_elapsed = toc(tic_id); if( time_elapsed > pause_time ) % network congestion, wait for a while pause(pause_time) pause_time = min(180, 3 * pause_time ); else pause_time = max(1, pause_time / 1.5 ); end end end %indicate end of loop. pb.reset(); %clear some not needed variables clear *ECG this_iter* ECG_annotations if( obj.ECGtaskHandle.doPayload ) % if PIDs generates payloads, try to collect them. %% multiPIDs. Try to build the whole thing from every parts, otherwise exit if( obj.this_pid == obj.cant_pids ) %% Master PID %last pid % Update point pb.checkpoint('Collecting results'); %%Master PID last pid bContinue = true; %Wait for obj.Time2WaitPIDs seconds the finalization of all PIDs. Otherwise exit %with error. Start2Wait = tic(); start_pid = 1; start_iter_this_pid = 1; resume_iter_this_pid = 0; payload_dump_counter = 1; while(bContinue) try % Update point pb.checkpoint('Collecting other PIDs results'); this_header = []; %feature matrices payload = []; payload_idx = 1; % to resume after the last dump to disk. if( resume_iter_this_pid > 0 ) start_iter_this_pid = resume_iter_this_pid; end for ii = start_pid:obj.cant_pids files_this_pid = dir([obj.output_path 'tmpfile_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_payload_cantpids_' num2str(obj.cant_pids) '_thispid_' num2str(ii) '_*.mat' ]); if( isempty(files_this_pid) ) if(obj.cant_pids == 1) % no payload generated bContinue = false; break else % fprintf(2,'%s not found\n', [obj.output_path 'tmpfile_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_payload_cantpids_' num2str(obj.cant_pids) '_thispid_' num2str(ii) '_*.mat' ]); error('ECGwrapper:PIDnotFinished', 'Handled error'); end end cant_iteraciones_this_pid = length(files_this_pid); % por q deberia saberlo ? %obj.ECGtaskHandle.cant_iteration = cant_iteraciones_this_pid; for jj = start_iter_this_pid:cant_iteraciones_this_pid % por q deberia saberlo ? %obj.ECGtaskHandle.this_iteration = jj; aux_FM_filename = [obj.output_path 'tmpfile_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_payload_cantpids_' num2str(obj.cant_pids) '_thispid_' num2str(ii) '_iteration_' num2str(jj) '_of_' num2str(cant_iteraciones_this_pid) '.mat' ]; if( ~exist(aux_FM_filename, 'file')) %Probably this PID not finished yet. error('ECGwrapper:PIDnotFinished', 'Handled error'); end aux = load(aux_FM_filename); if( isprop(obj.ECGtaskHandle, 'signal_payload') && obj.ECGtaskHandle.signal_payload ) % Process the results as a % signal, dump sequentialy % to disk if( isempty(this_header) ) % gain offset calculation to fit in destination class int16 % offset/gain transform % range_conversion_offset = mean(obj.ECGtaskHandle.range_min_max_tracking, 2); % range_conversion_gain = (2^15-1)./(diff(obj.ECGtaskHandle.range_min_max_tracking,1, 2)./2); % gain transform range_conversion_offset = 0; range_conversion_gain = (2^15-1)./(max(abs(obj.ECGtaskHandle.range_min_max_tracking),[],2)); result_signal = cast( round( bsxfun( @times, bsxfun( @minus, aux.result_signal, range_conversion_offset), range_conversion_gain) ) , 'int16'); this_header = obj.ECG_header; % in ADCu / physical units this_header.gain = range_conversion_gain; this_header.offset = range_conversion_offset; this_header.nsig = size(aux.result_signal, 2); if( obj.repetitions > 1) aux_sufix = [ '_repetition_' num2str(repeat_idx)]; else aux_sufix = []; end MIT_filename = [ obj.rec_filename '_' aux_user_prefix obj.ECGtaskHandle.name aux_sufix ]; MIT_filename = regexprep(MIT_filename, '\W*(\w+)\W*', '$1'); MIT_filename = regexprep(MIT_filename, '\W', '_'); this_header.recname = MIT_filename; writeheader(obj.output_path, this_header); result_files = [ result_files; cellstr([obj.output_path MIT_filename '.dat'])]; fidECG = fopen([obj.output_path MIT_filename '.dat'], 'w'); else fidECG = fopen([obj.output_path MIT_filename '.dat'], 'a'); end try fwrite(fidECG, result_signal', 'int16', 0 ); fclose(fidECG); catch MEE fclose(fidECG); rethrow(MEE); end else % Process the results according to the % 'Concatenate' method payload = obj.ECGtaskHandle.Concatenate(payload, aux); %check variable growth payload_var_size = whos('payload'); payload_var_size = payload_var_size.bytes/obj.typical_compression_ratio; if( payload_var_size > obj.payload_max_size ) %force dump to disk. payload.payload_idx = payload_idx; save([obj.output_path 'payloads_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_' num2str(payload_dump_counter) '.mat'], '-struct', 'payload'); %update counter and reset payload. payload_idx = payload_idx + size(payload,1) + 1; payload = []; payload_dump_counter = payload_dump_counter + 1; %to continue from this pid/iteration. start_pid = ii; resume_iter_this_pid = jj+1; end end end %the first time in this loop we resume after the last %file dump, and never again. start_iter_this_pid = 1; end % Update point pb.checkpoint('Generating final results'); if( ~isempty(payload) ) % data remain in memory -> dump 2 disk if( payload_idx > 1 ) payload.payload_idx = payload_idx; end save([obj.output_path 'payloads_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_' num2str(payload_dump_counter) '.mat'], '-struct', 'payload'); %update counter and reset payload. payload = []; end %rename results to put some ordering. files_this_pid = dir([obj.output_path 'payloads_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_*.mat']); cant_iteraciones_this_pid = length(files_this_pid); if( isempty(obj.user_string) ) aux_user_prefix = []; else aux_user_prefix= [obj.user_string '_']; end for jj = 1:cant_iteraciones_this_pid if( cant_iteraciones_this_pid > 1 ) aux_sufix = ['_part_' num2str(jj) '_of_' num2str(cant_iteraciones_this_pid)]; else aux_sufix = []; end if( obj.repetitions > 1) aux_sufix = [aux_sufix '_repetition_' num2str(repeat_idx)]; end auxStr = [obj.output_path obj.rec_filename '_' aux_user_prefix obj.ECGtaskHandle.name aux_sufix '.mat']; movefile( [obj.output_path 'payloads_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_' num2str(jj) '.mat'], ... auxStr, 'f' ); result_files = [ result_files; cellstr(auxStr)]; end % Update point pb.checkpoint('Deleting temporal files'); %clean temporal files delete([obj.output_path 'tmpfile_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_payload_cantpids_' num2str(obj.cant_pids) '*.mat' ]); bContinue = false; catch ME % Error handling if( strfind(ME.identifier, 'ECGwrapper') ) if( obj.cant_pids == 1 || obj.bHaveUserInterface || toc(Start2Wait) > obj.Time2WaitPIDs ) % fprintf(2, 'Timeout. Master give up waitng Slaves.\n'); error('ECGwrapper:PIDnotFinished', 'Master give up waiting for PID %d iteration %d\n', ii, jj); end pause(30); else rethrow(ME) end end end if( isprop(obj.ECGtaskHandle, 'signal_payload') ) if( ~obj.ECGtaskHandle.signal_payload ) %Dump results as standard payload. for result_fn = rowvec(result_files) % Master PID can operate over the global % payload. payload = load(result_fn{1}); payload = obj.ECGtaskHandle.Finish( payload, obj.ECG_header ); save(result_fn{1}, '-struct', 'payload'); end end else %Dump results as standard payload. for result_fn = rowvec(result_files) % Master PID can operate over the global % payload. payload = load(result_fn{1}); payload = obj.ECGtaskHandle.Finish( payload, obj.ECG_header ); save(result_fn{1}, '-struct', 'payload'); end end else %% Slave PIDs if( obj.syncSlavesWithMaster ) % other PIDS sync here after Master build the % results file/s. bContinue = true; %Wait for Time2WaitPIDs seconds the finalization of all PIDs. Otherwise exit %with error. Start2Wait = tic(); while(bContinue) try files_this_pid = dir([obj.tmp_path 'tmpfile_' aux_user_prefix obj.ECGtaskHandle.name '_' obj.rec_filename '_payload_cantpids_' num2str(obj.cant_pids) '_thispid_' num2str(obj.this_pid) '_*.mat' ]); if( ~isempty(files_this_pid) ) error('ECGwrapper:MasterNotFinished', 'Handled error'); end bContinue = false; catch ME if( strfind(ME.identifier, 'ECGwrapper') ) if( obj.bHaveUserInterface || toc(Start2Wait) > 1.5*obj.Time2WaitPIDs ) error('ECGwrapper:MasterNotFinished', 'Timeout. Slave give up waitng Master.'); end pause(30); else rethrow(ME) end end end end end else obj.ECGtaskHandle.Finish( payload, obj.ECG_header ); end else cprintf('[1,0.5,0]', 'Requirements not satisfied in %s for task %s.\n', obj.recording_name, obj.ECGtaskHandle.name); end catch MException %% Error handling obj.Error = true; if( obj.bHaveUserInterface ) %% with UI if( ~isempty(strfind(MException.identifier, 'ECGwrapper')) || isempty(MException.identifier) ) %Our errors if( strfind(MException.identifier, 'ArgCheck') ) %Argument error, try other settings if user interface is %available fprintf(2, '%s', MException.message); fprintf(2, '\nTry a different set of arguments.\n'); else if( isempty(MException.identifier) ) %User break with CTRL-C ?? fprintf(2, '\nUser interruption.\n'); else obj.ErrorReport = [obj.ErrorReport {getReport(MException)}]; %other home-made errors. Make an educated exit ... rethrow(MException) end end else obj.ErrorReport = getReport(MException); %% Other unknown errors rethrow(MException); end else %% No User interface report clearly to log file str_aux = disp_string_framed(0, [ 'Error in ' obj.recording_name ] ); report = getReport(MException); fprintf(2, '\n\n%s\n%s\n%s', str_aux, report, str_aux); rethrow(MException) end end repeat_idx = repeat_idx + 1; % Update point pb.checkpoint('Work done.'); end % if execution did not report errors, mark as processed. obj.Processed = true; obj.Result_files = result_files; if( isempty(result_files) ) if( obj.ECGtaskHandle.started ) if( obj.cant_pids == 1 || obj.this_pid == obj.cant_pids ) obj.Error = true; obj.ErrorReport = [obj.ErrorReport {sprintf('No payload generated for recording %s\n', obj.recording_name )}]; disp_string_framed(2, 'No payload generated'); else disp_string_framed('*Blue', 'Work done!'); end else obj.NoWork2Do = true; disp_string_framed('[1,0.5,0]', 'Nothing to do here'); end else disp_string_framed('*Blue', 'Work done!'); if( nargout > 0 ) % result required if( cant_iteraciones_this_pid > 1 ) result_payload = obj.Result_files; elseif( cant_iteraciones_this_pid == 1 ) result_payload = load(obj.Result_files{1}); else clear result_payload end else clear result_payload fprintf(1, '\n'); cprintf( 'Blue', disp_option_enumeration( 'Results saved in', obj.Result_files)); fprintf(1, '\n'); end end % destroy the progress bar. pb.delete; end function ECG = read_signal(obj, ECG_start_idx, ECG_end_idx) % in case using the this object just as an I/O interface, this % method can read the samples of a recording. if( obj.bArgChanged ) obj = obj.CheckArguments(); obj.bArgChanged = false; end if( nargin < 2 ) ECG_start_idx = 1; end if( nargin < 3 ) ECG_end_idx = ECG_start_idx + 10 * obj.ECG_header.freq; end ECG = read_ECG(obj.recording_name, max(1,ECG_start_idx), min(obj.ECG_header.nsamp,ECG_end_idx), obj.recording_format ); end function result_files = GetCahchedFileName(obj, task_names) % this method is useful to check if there are cached work already % created from an ECGtask if( nargin < 2 || isempty(task_names) ) task_names = {obj.ECGtaskHandle}; end if( ~iscell(task_names) && ~ischar(task_names)) task_names = {obj.ECGtaskHandle}; elseif( ischar(task_names) ) task_names = {task_names}; end result_files = {}; prev_ECGtaskHandle = obj.ECGtaskHandle; for this_name = rowvec(task_names) obj.ECGtaskHandle = this_name{1}; if( obj.bArgChanged ) obj = obj.CheckArguments(); obj.bArgChanged = false; end if( isempty(obj.user_string) ) aux_user_prefix = []; else aux_user_prefix= [obj.user_string '_']; end files_this_pid = dir([obj.output_path obj.rec_filename '_' aux_user_prefix obj.ECGtaskHandle.name '.mat']); if( ~isempty(files_this_pid) ) result_files = [ result_files; strcat(obj.output_path, colvec({files_this_pid(:).name})) ]; end end % leave things as we found. obj.ECGtaskHandle = prev_ECGtaskHandle; if( obj.bArgChanged ) obj = obj.CheckArguments(); obj.bArgChanged = false; end end function ReportErrors(obj) % error reporting method. if( obj.Processed ) if( obj.Error ) fprintf(2, 'Some error happened in %s\n', obj.ECGtaskHandle.name ); cant_errors = length(obj.ErrorReport); for ii = 1:cant_errors if( cant_errors > 1) disp_string_framed(2, sprintf('Error %d', ii)); end fprintf(2, '\n%s\n', obj.ErrorReport{ii} ); end end else fprintf(1, 'Task not processed yet. Execute ''Run'' method first\n'); end end function disp(obj) % this method produces a pretty-printed description about the % information stored in the object for ii = 1:length(obj) this_obj = obj(ii); if( this_obj.bCreated ) disp_string_framed( '*Blue', 'ECGwrapper object config' ); fprintf(1, '+ECG recording: '); if( isempty(this_obj.recording_name) ) cprintf('*Red', 'None selected\n', this_obj.recording_name, this_obj.recording_format); else cprintf('*Blue', '%s (%s)\n', this_obj.recording_name, this_obj.recording_format); end fprintf(1,[ ... '+PID: %d/%d\n' ... '+Repetitions: %d\n' ... '+Partition mode: %s\n'], ... this_obj.this_pid, ... this_obj.cant_pids, ... this_obj.repetitions, ... this_obj.partition_mode); fprintf(1, '+Function name: '); if( isobject(this_obj.ECGtaskHandle) ) if( strcmpi( this_obj.ECGtaskHandle.name, 'Null task' ) ) cprintf('*Red', '%s\n', this_obj.ECGtaskHandle.name); else fprintf(1, '%s\n', this_obj.ECGtaskHandle.name); end else cprintf('*Red', 'Task not defined\n'); end fprintf(1, '+Processed: '); if( this_obj.Processed) cprintf('*Magenta', 'true\n'); else cprintf('*Red', 'false\n'); end if( ~isempty(this_obj.tmp_path) ) fprintf(1, '+TMP: %s\n', adjust_string(this_obj.tmp_path, 20) ); end if( this_obj.Processed ) fprintf(1, disp_option_enumeration( '+Result files:', this_obj.Result_files)); end fprintf(1, '\n'); else fprintf(1, 'Object not created yet.'); end end end %% property access methods. function set.tmp_path(obj,value) if( isempty(value) ) obj.tmp_path = value; elseif( (ischar(value) && exist(value, 'dir') ) ) if( value(end) == filesep ) obj.tmp_path = value; else obj.tmp_path = [value filesep]; end obj.bArgChanged = true; else warning('ECGwrapper:BadArg', 'tmp_path must be a string.'); end end function set.user_string(obj,value) if( isempty(value) ) obj.user_string = value; elseif(ischar(value) ) obj.user_string = value; else warning('ECGwrapper:BadArg', 'user_string must be a string.'); end end function set.output_path(obj,value) if( isempty(value) ) obj.output_path = value; elseif(ischar(value) ) if( ~exist(value, 'dir') ) if( ~mkdir(value) ) warning('ECGwrapper:BadArg', 'output_path must exist, or privileges should be granted to this script.'); return end end if( value(end) == filesep ) obj.output_path = value; else obj.output_path = [value filesep]; end obj.bArgChanged = true; else warning('ECGwrapper:BadArg', 'output_path must be a string.'); end end function set.recording_name(obj,value) if( ischar(value) || isempty(value) ) obj.recording_name = value; obj.bArgChanged = true; obj.bECG_rec_changed = true; else warning('ECGwrapper:BadArg', 'recording_name must be a string.'); end end function set.recording_format(obj,value) if( isempty(value) ) obj.recording_format = 'auto'; obj.bArgChanged = true; elseif( ischar(value) ) obj.recording_format = value; obj.bArgChanged = true; obj.bECG_rec_changed = true; else warning('ECGwrapper:BadArg', 'recording_format must be a string.'); end end function set.ECG_annotations(obj,value) if( isempty(value) ) obj.ECG_annotations = []; obj.QRS_locations = []; else if( isstruct(value) ) if( all(isfield(value, obj.cAnnotationsFieldNamesRequired )) ) obj.ECG_annotations = value; obj.QRS_locations = value.time; else warning( 'ECGwrapper:BadArg', disp_option_enumeration( 'Please provide the following fields in the annotations struct:', obj.cAnnotationsFieldNamesRequired) ); end else warning('ECGwrapper:BadArg', 'ECG_annotations must be a struct.'); end end end function value = get.ECG_annotations(obj) if( obj.bECG_rec_changed ) obj = obj.CheckECGrecording(); end value = obj.ECG_annotations; end function value = get.ECG_header(obj) if( obj.bECG_rec_changed ) obj = obj.CheckECGrecording(); end value = obj.ECG_header; end function set.cant_pids(obj,value) if( value > 0 ) obj.cant_pids = value; obj.bArgChanged = true; else warning('ECGwrapper:BadArg', 'cant_pids must be > 0.'); end end function set.this_pid(obj,value) [tp, cp] = parse_pids( value ); if( all(~isnan([tp, cp])) && tp > 0 && tp <= cp) obj.this_pid = tp; obj.cant_pids = cp; %#ok obj.bArgChanged = true; %#ok else warning('ECGwrapper:BadArg', 'Incorrect format, use ''1/3'' or [1 3]'); end end function set.repetitions(obj,value) if( value > 0 ) obj.repetitions = value; obj.bArgChanged = true; else warning('ECGwrapper:BadArg', 'repetitions must be > 0.'); end end function set.ECGtaskHandle(obj,value) if( isa(value, 'ECGtask') ) obj.ECGtaskHandle = value; obj.bArgChanged = true; elseif( ischar(value) ) aux_idx = find(strcmpi(obj.cKnownECGtasks, value)); if( isempty(aux_idx) ) cprintf( 'Red', disp_option_enumeration( 'Invalid ECGtask name. ECGtaskHandle must be one of these strings:', obj.cKnownECGtasks )); obj.ECGtaskHandle = ECGtask_do_nothing(); else obj.ECGtaskHandle = obj.cKnownECGtasksHdl{aux_idx}; obj.bArgChanged = true; end elseif( isempty(value) ) obj.ECGtaskHandle = ECGtask_do_nothing(); else warning('ECGwrapper:BadArg', 'ECGtaskHandle must be a ECGtask object.'); end end function set.cacheResults(obj,value) if( islogical(value) ) obj.cacheResults = value; else warning('ECGwrapper:BadArg', 'cacheResults must be boolean.'); end end function set.syncSlavesWithMaster(obj,value) if( islogical(value) ) obj.syncSlavesWithMaster = value; else warning('ECGwrapper:BadArg', 'syncSlavesWithMaster must be boolean.'); end end end methods (Access = private) function obj = CheckArguments(obj) %Object parsing if( isobject(obj.ECGtaskHandle) ) for ii = 1:length(obj.cObjMethodsRequired) if( ~ismethod(obj.ECGtaskHandle, obj.cObjMethodsRequired{ii}) ) error( 'ECGwrapper:ArgCheck:UserObjHdl', ['Method ' obj.cObjMethodsRequired{ii} ' not implemented in UserObjHdl.\n\n'] ); end end for ii = 1:length(obj.cObjPropsRequired) if( ~isprop(obj.ECGtaskHandle, obj.cObjPropsRequired{ii}) ) error( 'ECGwrapper:ArgCheck:UserObjHdl', ['Property ' obj.cObjPropsRequired{ii} ' not present in UserObjHdl.\n\n'] ); end end else error( 'ECGwrapper:ArgCheck:UserObjHdl', 'ECGtaskHandle is not a valid ECGtask handle.' ); end if( isprop(obj.ECGtaskHandle, 'min_heartbeats_required') || isprop(obj.ECGtaskHandle, 'max_heartbeats_per_iter') ) obj.partition_mode = 'QRS'; elseif( isprop(obj.ECGtaskHandle, 'min_length_required') || isprop(obj.ECGtaskHandle, 'max_length_per_iter') ) obj.partition_mode = 'ECG_overlapped'; end %ECG parsing obj = CheckECGrecording(obj); if( isempty(obj.tmp_path) ) obj.tmp_path = tempdir ; % obj.tmp_path = [fileparts(mfilename('fullpath')) filesep 'tmp' filesep ]; % obj.tmp_path = [fileparts(obj.recording_name) filesep ]; end if( isempty(obj.output_path) ) obj.output_path = obj.rec_path; end %check path integrity. if(~exist(obj.tmp_path, 'dir')) %try to create it if( ~mkdir(obj.tmp_path) ) error('ECGwrapper:ArgCheck:InvalidPath', 'Invalid obj.tmp_path. Please provide a valid path.\n' ); end end if( strcmpi(obj.partition_mode, 'QRS') ) %partition using QRS detections if( obj.overlapping_time < 5 ) %seconds warning('ECGwrapper:ArgCheck:Overlapp_too_low', 'The overlapping time between iterations is too low, consider increasing.\n' ); end else if( strcmpi(obj.partition_mode, 'ECG_contiguous') ) %One segment after the other. obj.overlapping_time = 0; end end end function obj = CheckECGrecording(obj) % internal method to check the correctness of the user input. if( isempty(obj.recording_name) ) error( 'ECGwrapper:ArgCheck:InvalidECGarg', 'Please provide an ECG recording as described in the documentation, help(''ECGwrapper'') maybe could help you.\n' ); else % ECG to be read if( exist(obj.recording_name, 'file') ) [obj.rec_path, obj.rec_filename] = fileparts(obj.recording_name); obj.rec_path = [obj.rec_path filesep]; else [obj.rec_path, obj.rec_filename] = fileparts(obj.recording_name); obj.rec_path = [obj.rec_path filesep]; if( ~exist(obj.rec_path, 'dir') ) obj.rec_path = [pwd filesep obj.rec_path]; end aux_files = dir([obj.rec_path obj.rec_filename '.*' ]); if( isempty(aux_files) ) error( 'ECGwrapper:ArgCheck:RecNotFound', 'Can not find recording : %s', obj.recording_name ); else % the bigger probably would have the ECG data [~, aux_idx] = max(cell2mat({aux_files(:).bytes})); obj.recording_name = [obj.rec_path aux_files(aux_idx).name]; end [~, obj.rec_filename] = fileparts(obj.recording_name); end if( strcmp(obj.recording_format, 'auto') ) %Try guessing the ECG format aux_fmt = ECGformat(obj.recording_name); if( isempty(aux_fmt) ) str_aux = disp_option_enumeration( sprintf('Could not guess the format of:\n\n%s\n\nChoose one of the following:', strrep(obj.recording_name, '\', '\\')), obj.cKnownFormats); error( 'ECGwrapper:ArgCheck:InvalidFormat', str_aux); else obj.recording_format = aux_fmt; end end if( strcmp(obj.recording_format, 'MIT') ) strAnnExtension = {'ari' 'atr' 'ecg'}; annFileName = {}; bAnnotationFound = false; for ii = 1:length(strAnnExtension) aux_str = [obj.recording_name(1:end-3) strAnnExtension{ii}]; if( exist(aux_str, 'file') ) annFileName = [ annFileName aux_str ]; bAnnotationFound = true; end end if(~bAnnotationFound) ann_aux = []; else for aux_str = annFileName ann_aux = readannot(aux_str{1}); if( ~isempty(ann_aux) ) break; end end end obj.ECG_header = readheader([obj.recording_name(1:end-3) 'hea']); elseif( strcmp(obj.recording_format, 'ISHNE') ) annFileName = [obj.recording_name(1:end-3) 'ann']; if( exist(annFileName, 'file') ) ann_aux = read_ishne_ann(annFileName); else ann_aux = []; end obj.ECG_header = read_ishne_header(obj.recording_name); elseif( strcmp(obj.recording_format, 'Mortara') ) ann_aux = []; obj.ECG_header = read_Mortara_header(obj.recording_name); [obj.rec_path, obj.rec_filename] = fileparts(obj.recording_name); obj.rec_path = [obj.rec_path filesep]; elseif( strcmp(obj.recording_format, 'HES') ) ann_aux = read_HES_ann([obj.recording_name(1:end-3) 'lst']); header_aux = read_HES_header(obj.recording_name); ann_aux.time = round(ann_aux.time * header_aux.freq); obj.ECG_header = header_aux; elseif( strcmp(obj.recording_format, 'AHA') ) ann_aux = read_AHA_ann(obj.recording_name); obj.ECG_header = read_AHA_header(obj.recording_name); elseif( strcmp(obj.recording_format, 'MAT') ) [~, obj.ECG_header, ann_aux ] = read_ECG(obj.recording_name, [], [], obj.recording_format); end end if(isempty(ann_aux)) obj.ECG_annotations = []; obj.QRS_locations = []; else % discard non-beats and finish annotations parsing. if( isfield(ann_aux, 'anntyp') ) ann_aux = AnnotationFilterConvert(ann_aux, obj.recording_format, obj.class_labeling); end obj.QRS_locations = ann_aux.time; obj.ECG_annotations = ann_aux; end obj.bECG_rec_changed = false; end end end