classdef ECGtask_QRS_detections_post_process < 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 = 'QRS_detections_post_process';
        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)
        
    end
    
    properties( Access = private )
        
        tmp_path_local
        ud_func_pointer
        
    end
    
    properties
        post_proc_func
        progress_handle
        payload
        tmp_path
        CalculatePerformance = false;
        
    end
    
    methods
           
        function obj = QRS_detections_post_process (obj)
            
        end
        
        function Start(obj, ECG_header, ECG_annotations)
            
            obj.ud_func_pointer = eval(['@' obj.post_proc_func]);
            
            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) )

                AnnNames = obj.payload.series_quality.AnnNames(:,1);
                
                for fn = rowvec(AnnNames)
                    aux_val = obj.payload.(fn{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) );
                    obj.payload.(fn{1}).time = aux_val;
                end

            end

            try

                obj.progress_handle.checkpoint([ 'User defined function: ' obj.post_proc_func])

                payload_out = obj.ud_func_pointer( obj.payload, ECG_header, ECG_sample_start_end_idx );

                obj.progress_handle.checkpoint('Adding quality metrics')
                
                % Add QRS detections quality metrics, Names, etc.
                payload_out = calculateSeriesQuality(payload_out, ECG_header, ECG_sample_start_end_idx );

                % offset in time
                AnnNames = payload_out.series_quality.AnnNames(:,1);
                for fn = rowvec(AnnNames)
                    aux_val = payload_out.(fn{1}).time + ECG_start_offset - 1;
                    payload_out.(fn{1}).time = aux_val;
                end

                % calculate artificial leads
                if( obj.CalculatePerformance )
                    AnnNames = payload_out.series_quality.AnnNames(:,1);
                    cant_lead_name = size(AnnNames,1);
                    payload_out.series_performance.conf_mat = zeros(2,2,cant_lead_name);

                    if(isempty(ECG_annotations)) 
                        disp_string_framed(2, sprintf('Trusted references not found for %s', ECG_header.recname) );
                    else
                        % offset refs, produced anns were already shifted
                        ECG_annotations.time = ECG_annotations.time + ECG_start_offset - 1;
                        for kk = 1:cant_lead_name
                            payload_out.series_performance.conf_mat(:,:,kk) = bxb(ECG_annotations, payload_out.(AnnNames{kk}).time, ECG_header );
                        end            
                    end

                end

                
                obj.progress_handle.checkpoint('Done')
                
            catch aux_ME

                disp_string_framed(2, sprintf('User-function "%s" failed in recording %s', obj.post_proc_func, ECG_header.recname ) );                                

                report = getReport(aux_ME);
                fprintf(2, 'Error report:\n%s', report);

            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)

            payload = ConcatenateQRSdetectionPayloads(obj, plA, plB);

        end
        
        %% property restriction functions

        function set.post_proc_func(obj, x)
            
            if( ischar(x) )
                
                if( exist(x) == 2 )

                    obj.post_proc_func = x;
                
                else
                    disp_string_framed(2, sprintf('Function "%s" is not reachable in path.', x));  
                    fprintf(1, 'Make sure that exist(%s) == 2\n',x);
                end
                
            else
                warning('ECGtask_QRS_detections_post_process:BadArg', 'post_proc_func must be a string.');
            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_detections_post_process:BadArg', ['Could not create ' x ]);
                    end
                end
                
            else
                warning('ECGtask_QRS_detections_post_process: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_detections_post_process:BadArg', ['Could not create ' x ]);
                    end
                end
                
            else
                warning('ECGtask_QRS_detections_post_process:BadArg', 'tmp_path_local must be a string.');
            end
        end
        
    end
    
    methods ( Access = private )
        
        
    end
    
end
