classdef ECGtask_heartbeat_classifier < 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/2/2013
% Last update: 18/2/2013
       
    properties(GetAccess = public, Constant)
        name = 'ECG_heartbeat_classifier';
        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)
        
        fig_hdl = 1;
        cKnownModesOfOperation = {'auto' 'slightly-assisted', 'assisted'};
        
    end
    
    properties( Access = private )
        
        
    end
    
    properties
        
        progress_handle
        payload
        mode 
        tmp_path
        
        % minimum amount of heartbeats required
        min_heartbeats_required = 500;
        % maximum amount of heartbeats to process each iteration
        max_heartbeats_per_iter = 4000;
        % qrs locations provided to the task, result of previous QRS
        % detections / correction
        annotations = [];
                
    end
    
    methods
           
        function obj = ECGtask_heartbeat_classifier(obj)

            if( isempty(obj.mode) )
                obj.mode = obj.cKnownModesOfOperation{1};
            end
            
        end
        
        function Start(obj, ECG_header, ECG_annotations)

            if( ECG_header.nsig == 1 )
                return
            end

            lead_idx = get_ECG_idx_from_header(ECG_header);

            if( length(lead_idx) <= 1 )
                return
            end
            
            obj.annotations = ParsePayloadAnnotations(obj);
            
            if( (~isempty(obj.annotations) && length(obj.annotations) > obj.min_heartbeats_required ) || ( isfield(ECG_annotations, 'time') && ~isempty(ECG_annotations.time))  )
                obj.started = true;
            end
            
        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
            
%             aux_val = ECG_sample_start_end_idx + ECG_start_offset - 1;
%             disp_string_title(1, sprintf( 'Classifying from %d to %d', aux_val(1), aux_val(2) ) );
            
            [lead_idx, ECG_header ] = get_ECG_idx_from_header(ECG_header);
            
            [aux_val, ~, lablist ] = a2hbc('ECG', ECG(:,lead_idx), ...
                            'ECG_header', ECG_header, ...
                            'ECG_annotations', ECG_annotations, ...
                            'op_mode', obj.mode );

            bAux = ECG_annotations.time > ECG_sample_start_end_idx(1) & ECG_annotations.time < ECG_sample_start_end_idx(2);
            ECG_annotations.anntyp = lablist(colvec(aux_val(bAux)),1);
            ECG_annotations.time = ECG_annotations.time(bAux) + ECG_start_offset - 1;
            
            payload_out = ECG_annotations;
            
        end
        
        function payload = Finish(obj, payload, ECG_header)
           
        end
        
        function payload = Concatenate(obj, plA, plB)

            if( isempty(plA) )
                
                payload = plB;
                
            else
                for fn = rowvec(fieldnames(plA))
                    
                    if( isfield(plA, fn{1}) && isfield(plB, fn{1}) )
                        payload.(fn{1}) = [ plA.(fn{1}); plB.(fn{1}) ];
                    elseif( ~isfield(plA, fn{1}) && isfield(plB, fn{1}) )
                        payload.(fn{1}) = plB.(fn{1});
                    end
                    
                end            
            end
            
        end

        %% property restriction functions
        
        function set.mode(obj,x)
            if( ischar(x) && ~isempty(strcmpi(x, obj.cKnownModesOfOperation)) )
                obj.mode = x;
            else
                warning('ECGtask_QRS_detection:BadArg', 'Invalid detectors.');
            end
        end

        
    end
    
    methods ( Access = private )
        
        function aux_val = ParsePayloadAnnotations(obj)

            aux_val = [];
            
            if( isstruct(obj.payload) )
                
                aux_fn = fieldnames(obj.payload);
                
                jj = 1;
                
                while( isempty( aux_val ) )
                    
                    % prefer manually reviewed annotations corrected 
                    aux_idx = find(cell2mat( cellfun(@(a)(~isempty(strfind(a, 'corrected_'))), aux_fn, 'UniformOutput', false)));

                    if( isempty(aux_idx) )

                        if( isfield(obj.payload, 'series_quality') )
                            % choose the best ranked automatic detection
                            [~, aux_idx] = sort(obj.payload.series_quality.ratios, 'descend' );
                            if( jj <= length(aux_idx))
                                aux_val = obj.payload.(obj.payload.series_quality.AnnNames{aux_idx(jj),1} ).(obj.payload.series_quality.AnnNames{aux_idx(jj),2});
                                delineation_chosen = obj.payload.series_quality.AnnNames{aux_idx(jj),1};
                            else
                                break
                            end
                        else
                            % choose the first of wavedet
                            aux_idx = find(cell2mat( cellfun(@(a)(~isempty(strfind(a, 'wavedet_'))), aux_fn, 'UniformOutput', false)));
                            if( isempty(aux_idx) )
                                disp_string_framed(2, 'Could not identify QRS detections in the input payload.');
                                return
                            else
                                if( jj <= length(aux_idx))
                                    aux_val = obj.payload.(aux_fn{aux_idx(jj)}).time;
                                    delineation_chosen = aux_fn{aux_idx(jj)};
                                else
                                    break
                                end
                            end
                        end
                    else
                        % choose the first manually audited
                        if( jj <= length(aux_idx))
                            aux_val = obj.payload.(aux_fn{aux_idx(jj)}).time;
                            delineation_chosen = aux_fn{aux_idx(jj)};
                        else
                            break
                        end
                    end

                    cprintf('blue', [' + Using ' delineation_chosen ' detections.\n' ] );
                    aux_val = sort(unique(aux_val));
                    jj = jj + 1;
                end
                
            elseif( isnumeric(obj.payload) )
                aux_val = sort(unique(obj.payload));
            end
            
        end        
        
    end
    
end
