function [ Labels, ConfusionMatrix, LabelList ] = a2hbc_main(varargin) % A2HBC main script % ----------------- % See a2hbc.m in the parent directory or the documentation for help. % % Author: Mariano Llamedo Soria (llamedom at {electron.frba.utn.edu.ar; unizar.es} % Birthdate : 16/8/2011 % Last update: 9/2/2012 %% Constants and definitions persistent userNoAnswers if( isempty(userNoAnswers) ) %first use userNoAnswers = 1; end a2hbc_header; % some variable declarations; Labels = []; ConfusionMatrix = []; LabelList = []; true_labels = []; bArgCheckPassed = false; CantSamplesClose = 10; CantSamplesFar = 10; bUserExit = false; bRecordingChanged = true; bLabelingChanged = false; repeat_idx = 1; %% Argument parsing %argument definition p = inputParser; % Create instance of inputParser class. p.addParamValue('recording_name', [], @(x)( ischar(x))); p.addParamValue('recording_format', [], @(x)( ischar(x) && any(strcmpi(x,cKnownFormats))) ); p.addParamValue('ECG', [], @(x)(isnumeric(x)) ); p.addParamValue('ECG_header', [], @(x)(isstruct(x)) ); p.addParamValue('ECG_annotations', [], @(x)( isstruct(x) ) ); p.addParamValue('op_mode', 'auto', @(x)( (isnumeric(x) && x >= 1 && x <= maxOperMode) || any(strcmpi(x,cKnownModesOfOperation))) ); p.addParamValue('cant_pids', 1, @(x)(isnumeric(x) && x > 0 ) ); p.addParamValue('this_pid', 1, @(x)(isnumeric(x) && x > 0 ) ); p.addParamValue('CacheData', true, @(x)(islogical(x)) ); p.addParamValue('InteractiveMode', false, @(x)(islogical(x)) ); p.addParamValue('SimulateExpert', false, @(x)(islogical(x)) ); p.addParamValue('tmp_path', [], @(x)(ischar(x)) ); p.addParamValue('NumOfClusters', 12, @(x)(isnumeric(x) && x > 1 ) ); p.addParamValue('ClusteringRepetitions', 1, @(x)(isnumeric(x) && x > 0 && x <= 10 ) ); p.addParamValue('ClusterPresence', 75, @(x)(isnumeric(x) && x >= 0 && x <= 100 ) ); p.addParamValue('Repetitions', 1, @(x)(isnumeric(x) && x > 0 ) ); %additions p.addParamValue('class_labeling', 'AAMI', @(x)( ischar(x) && any(strcmpi(x,cKnownLabelings))) ); p.addParamValue('class_filter', char({'Normal', 'Supraventricular', 'Ventricular'}), @(x)( ischar(x) || iscell(x) ) ); try p.parse( varargin{:} ); catch MyError rethrow(MyError); end recording_name = p.Results.recording_name; recording_format = p.Results.recording_format; ECG_total = p.Results.ECG; ECG_header = p.Results.ECG_header; ECG_annotations = p.Results.ECG_annotations; bCache = p.Results.CacheData; op_mode = p.Results.op_mode; cant_pids = p.Results.cant_pids; this_pid = p.Results.this_pid; tmp_path = p.Results.tmp_path; bInteractiveMode = p.Results.InteractiveMode; bSimulateExpert = p.Results.SimulateExpert; CantClusters = p.Results.NumOfClusters; iter_times = p.Results.ClusteringRepetitions; cluster_presence = p.Results.ClusterPresence; Repetitions = p.Results.Repetitions; class_labeling = p.Results.class_labeling; class_filter = p.Results.class_filter; % Dont know why this variable uses a lot of bytes to store at disk. clear p %% Start of the algorithm if( bHaveUserInterface ) if( isempty(varargin) ) bInteractiveMode = true; end else bInteractiveMode = false; end typical_lablist = char(typical_lablists{find(strcmpi(class_labeling, cKnownLabelings))}); typical_cant_labels = size(typical_lablist,1); this_iter_cm = nan(typical_cant_labels); ConfusionMatrix = zeros(typical_cant_labels, typical_cant_labels,Repetitions); while ( ~bUserExit && repeat_idx <= Repetitions ) try %% work starts here if( bRecordingChanged ) % new recording, needs processing bRecordingChanged = false; %% Argument check % op_mode parsing if( isnumeric(op_mode) ) op_mode = cKnownModesOfOperation{op_mode}; end % class filtering parsing class_filter = char(intersect( cellstr(class_filter), cellstr(typical_lablist))); if( isempty(class_filter) ) strAux = [ repmat(' + ', size(typical_lablist,1), 1) char(typical_lablist) repmat('\n', size(typical_lablist,1), 1 ) ]; error( 'a2hbc:ArgCheck:InvalidClassFilter', ['Invalid class-filter. Please provide one of these classes:\n' rowvec(strAux')] ); end bECG_sample_provided = false; %ECG parsing if( ~isempty(ECG_total) ) % ECG already read bECG_sample_provided = true; elseif( ~isempty(recording_name) ) [~, rec_filename] = fileparts(recording_name); % ECG to be read, create a wrapper object, later assign tasks to it. ECGw = ECGwrapper( ... 'recording_name', recording_name, ... 'recording_format', recording_format, ... 'tmp_path', tmp_path, ... 'this_pid', sprintf('%d/%d', this_pid, cant_pids) ... ); ECG_header = ECGw.ECG_header; ECG_annotations = ECGw.ECG_annotations; % annotations are already AAMI, this is in order not to % convert again. recording_format = ''; else % strAux = help('a2hbc'); %#ok error( 'a2hbc:ArgCheck:InvalidECGarg', 'Please provide an ECG recording as described in the documentation, help(''a2hbc'') maybe could help you.\n' ); end if( isempty(ECG_header)) error( 'a2hbc:ArgCheck:InvalidHeader', 'Please provide the ECG header.\n\n' ); else if( ~isfield(ECG_header, cHeaderFieldNamesRequired ) ) strAux = [ repmat(' + ', length(cHeaderFieldNamesRequired), 1) char(cHeaderFieldNamesRequired) repmat('\n', length(cHeaderFieldNamesRequired), 1 ) ]; error( 'a2hbc:ArgCheck:InvalidHeader', ['Please provide the following fields in the header struct:\n ' rowvec(strAux') ] ); end end if( isempty(ECG_annotations)) error( 'a2hbc:ArgCheck:InvalidAnnotations', 'Please provide the ECG annotations.\n\n' ); else if( ~isfield(ECG_annotations, cAnnotationsFieldNamesRequired ) ) strAux = [ repmat(' + ', length(cAnnotationsFieldNamesRequired), 1) char(cAnnotationsFieldNamesRequired) repmat('\n', length(cAnnotationsFieldNamesRequired), 1 ) ]; error( 'a2hbc:ArgCheck:InvalidAnnotations', ['Please provide the following fields in the annotations struct:\n ' rowvec(strAux') ] ); end end [QRS_locations, aux_val ] = Annotation_process(ECG_annotations, recording_format, class_labeling); if( isempty(QRS_locations)) error( 'a2hbc:ArgCheck:InvalidAnnotations', 'No QRS annotations available, please provide valid annotations or perform QRS detection.\n\n' ); end true_labels = nan(size(aux_val)); for ii = 1:length(typical_lablists_anntyp) bAux = aux_val == typical_lablists_anntyp{ii}; true_labels(bAux) = ii; end rec_filename = ECG_header.recname; bLabelingChanged = false; lablist_idx = find(strcmpi(class_labeling, cKnownLabelings)); %Update the automatic classifier. global_classifier = global_classifiers{lablist_idx}; if( isempty(tmp_path) ) tmp_path = tempdir; end %check path integrity. if(~exist(tmp_path, 'dir')) %try to create it if( ~mkdir(tmp_path) ) error('a2hbc:ArgCheck:InvalidPath', 'Invalid tmp_path. Please provide a valid path.\n' ); end end bArgCheckPassed = true; %% Calculate accesories signals and coefficients if( this_pid > cant_pids ) %% Classification only PIDs % Wait other PIDS sync here after Master build the featmat % file. %last pid bContinue = true; CachedFeatMatFiles = dir([tmp_path 'tmpfile_a2hbc_' ECGtask_class_fc_hdl.name '_' rec_filename '*.mat']); %Wait for Time2WaitPIDs seconds the finalization of all PIDs. Otherwise exit %with error. Start2Wait = tic(); while(bContinue) try if( isempty( CachedFeatMatFiles ) ) error('a2hbc:PIDnotFinished', 'Handled error'); else % exit the waiting loop. bContinue = false; end catch ME if( strfind(ME.identifier, 'a2hbc') ) if( toc(Start2Wait) > 2*Time2WaitPIDs ) error('a2hbc:PIDnotFinished', 'Timeout. Classification-only Slave give up waitng Master.'); end pause(60); else rethrow(ME) end end end end % one task for calculating the PCA basis over the entire % recording ECGtask_PCA_proj_basis_hdl = ECGtask_PCA_proj_basis(); % new task for calculating the features ECGtask_class_fc_hdl = ECGtask_classification_features_calc(); %% ECG processing CachedFeatMatFiles = dir([tmp_path 'tmpfile_a2hbc_' ECGtask_class_fc_hdl.name '_' rec_filename '*.mat']); if( isempty( CachedFeatMatFiles ) ) if( bECG_sample_provided ) aux_struct.time = QRS_locations; aux_struct.anntyp = true_labels; if( ECG_header.nsig > 1 ) % As no wrapper is needed, I manually execute the task ECGtask_PCA_proj_basis_hdl.Start(ECG_header, aux_struct); ECGtask_PCA_proj_basis_hdl.Process(ECG_total, 1, [1 ECG_header.nsig], ECG_header, aux_struct, [1 length(QRS_locations) ] ); ECGtask_PCA_proj_basis_hdl.Finish([], ECG_header); else % PCA not need. ECGtask_PCA_proj_basis_hdl.autovec = 1; end % Activate the progress_struct bar. pb = progress_bar(rec_filename); ECGtask_class_fc_hdl.progress_handle = pb; ECGtask_class_fc_hdl.autovec = ECGtask_PCA_proj_basis_hdl.autovec; ECGtask_class_fc_hdl.Start(ECG_header, aux_struct); payload = ECGtask_class_fc_hdl.Process(ECG_total, 1, [1 ECG_header.nsamp], ECG_header, aux_struct, [1 length(QRS_locations) ] ); %#ok CachedFeatMatFiles = [tmp_path 'tmpfile_a2hbc_' ECGtask_class_fc_hdl.name '_' rec_filename '.mat']; save(CachedFeatMatFiles, '-struct', 'payload'); clear payload CachedFeatMatFiles = dir(CachedFeatMatFiles); delete(pb) else aux_struct.time = QRS_locations; aux_struct.anntyp = true_labels; ECGw.ECG_annotations = aux_struct; if( ECGw.ECG_header.nsig > 1 ) % if( isempty(ECGw.ECG_annotations) ) % ECGtask_PCA_proj_basis_hdl.cant_QRS_locations = 0; % else % ECGtask_PCA_proj_basis_hdl.cant_QRS_locations = length(ECGw.ECG_annotations.time); % end ECGw.partition_mode = 'QRS'; ECGw.ECGtaskHandle = ECGtask_PCA_proj_basis_hdl; %calculate the basis ECGw.Run(); else % PCA not need. ECGtask_PCA_proj_basis_hdl.autovec = 1; end ECGw.class_labeling = class_labeling; ECGtask_class_fc_hdl.autovec = ECGtask_PCA_proj_basis_hdl.autovec; % assign the task to the wrapper. ECGw.ECGtaskHandle = ECGtask_class_fc_hdl; ECGw.partition_mode = 'QRS'; %% Iterations over the whole ECG recording ECGw.Run; CachedFeatMatFiles = []; [tmp_path, CachedFeatMatFiles.name] = fileparts(ECGw.Result_files{1}); tmp_path = [tmp_path filesep]; end end if( isempty( CachedFeatMatFiles ) ) error( 'a2hbc:FeaturesNotCalc', 'Cached features files not found. Check files in the temporary dir.\n' ); else % Activate the progress_struct bar. pb = progress_bar(rec_filename); % Update point pb.checkpoint('Restoring cached feature matrix'); if( length(CachedFeatMatFiles) > 1 ) warning('TODO: generalizar para featmatrices arb grandes. Leyendo solo una parte de la feat matrix'); CachedFeatMatFileName = CachedFeatMatFiles(1).name; else CachedFeatMatFileName = CachedFeatMatFiles.name; end % restore cached data. load( [ tmp_path CachedFeatMatFileName]); end if( bECG_sample_provided ) ann = ECG_annotations; else ann = ECGw.ECG_annotations; aux_val = ann.anntyp; true_labels = nan(size(aux_val)); for ii = 1:length(typical_lablists_anntyp) bAux = aux_val == typical_lablists_anntyp{ii}; true_labels(bAux) = ii; end end % clear unused objects before continue % clear ECGw % clear ECGtask* % recorrer ECGw.Result_files y armar las matrices de features. %% Feature Matrices building %clean featureMatrix from NAN values. % AnyNaN_idx = find(any(isnan(featMat_clust),2)); % iAllMedian = nanmedian(featMat_clust); % if(any(isnan(iAllMedian))) % error( 'a2hbc:AllNanFeatures', 'Features not calculated in this recording. Check recording or ask help.\n' ); % end % for ii = rowvec(AnyNaN_idx) % bIdx2 = find(isnan(featMat_clust(ii,:))); % featMat_clust(ii, bIdx2) = iAllMedian(bIdx2); % end % % AnyNaN_idx = find(any(isnan(featMat_ldc),2)); % iAllMedian = nanmedian(featMat_ldc); % if(any(isnan(iAllMedian))) % error( 'a2hbc:AllNanFeatures', 'Features not calculated in this recording. Check recording or ask help.\n' ); % end % for ii = rowvec(AnyNaN_idx) % bIdx2 = find(isnan(featMat_ldc(ii,:))); % featMat_ldc(ii, bIdx2) = iAllMedian(bIdx2); % end % por el momento sigo usando el PRtools. % armo datasets. featMat_clust = prdataset(featMat_clust); featMat_ldc = prdataset(featMat_ldc); % Update point %clean featureMatrix from NAN values. pb.checkpoint('Clean featureMatrix from NAN values'); featMat_ldc = deNaN_dataset(featMat_ldc, 'change'); featMat_clust = deNaN_dataset(featMat_clust, 'change'); featMat_ldc = deNaN_dataset(featMat_ldc, 'change', @isinf); featMat_clust = deNaN_dataset(featMat_clust, 'change', @isinf); if( bHaveUserInterface && (~exist('UCP_struct', 'var') || ~ishandle(UCP_struct.fig_hdl)) ) UserControlPanel; ParseUserControlPanelInput; end else %% Only labeling changed ? if( bLabelingChanged ) bLabelingChanged = false; % labeling changed [QRS_locations, aux_val ] = Annotation_process(ann, recording_format, class_labeling); true_labels = nan(size(aux_val)); for ii = 1:length(typical_lablists_anntyp) bAux = aux_val == typical_lablists_anntyp{ii}; true_labels(bAux) = ii; end %change lablists lablist_idx = find(strcmpi(class_labeling, cKnownLabelings)); typical_lablist = char(typical_lablists{lablist_idx}); typical_cant_labels = size(typical_lablist,1); this_iter_cm = nan(typical_cant_labels); ConfusionMatrix = zeros(typical_cant_labels, typical_cant_labels,Repetitions); %Update the automatic classifier. global_classifier = global_classifiers{lablist_idx}; end end %% Classification cant_QRS_locations = size( featMat_ldc,1); already_labeled_idx = []; pending_hb_idx = setdiff((1:cant_QRS_locations)', already_labeled_idx ); bCancel = false; % nasty addons RR_intervals = diff(QRS_locations, 1); RR_intervals = colvec([ RR_intervals(1); RR_intervals ]); if( ~bECG_sample_provided ) ECG_total = ECGw.read_signal(1, ECGw.ECG_header.nsamp); end if( bHaveUserInterface ) DisplayConfiguration; end while( ~bCancel && ~isempty(pending_hb_idx) ) bDataSkiped = false; this_loop_already_labeled_idx = []; this_loop_cant_pending = length(pending_hb_idx); % Update point pb.checkpoint('Clustering data.'); %skip warnings here warning off all; prwarning(0); Clust_Labels = cluster_data_with_EM_clust(featMat_clust(pending_hb_idx,:), qdc_new([],1e-6,1e-6, []), CantClusters, iter_times); warning on all; prwarning(1); [~, sort_idx] = sort( Clust_Labels ); [all_clusters, aux_location] = unique(Clust_Labels(sort_idx,:), 'first'); aux_location = [colvec(aux_location); cant_QRS_locations+1]; cluster_sizes = diff(aux_location); cant_clusters = size(all_clusters,1); if( bHaveUserInterface) set(UCP_struct.Axes_hdl, 'Visible','on' ); % waitbar(0, UCP_struct.fig_hdl, 'Classification progress 0%') hb_percent_done = (length(already_labeled_idx) + length(this_loop_already_labeled_idx))/cant_QRS_locations; waitbar( hb_percent_done, UCP_struct.fig_hdl, [ 'Classification progress ' num2str(round(hb_percent_done*100)) '%' ]) end %% Intento de clasificacion autom�tica. % Aqu� trabaja el GC. % Update point pb.checkpoint('Automatic classification'); if( strcmpi(op_mode, 'slightly-assisted') || strcmpi(op_mode, 'auto') ) dsThisPatient_classification = featMat_ldc * global_classifier; dsThisPatient_classification = labeld(dsThisPatient_classification); dsThisPatient_classification = renumlab(dsThisPatient_classification, typical_lablist); % try to fit on original lablist for ii = 1:cant_clusters %los latidos de este cluster aux_idx = sort_idx(aux_location(ii):(aux_location(ii+1)-1)); %busco las clasificaciones no rechazadas. aux_idx1 = find(dsThisPatient_classification(aux_idx) ~= 0); cantXclase = histc( dsThisPatient_classification(aux_idx(aux_idx1)), 1:typical_cant_labels ); %busco la clase mas predecida [~, class_idx] = max(cantXclase); if( cantXclase(class_idx) > ( cluster_presence * 0.01 * cluster_sizes(ii) ) ) % La mayoria impone su clase. Clust_Labels(aux_idx) = class_idx; this_loop_already_labeled_idx = [ this_loop_already_labeled_idx; colvec(aux_idx) ]; if( bHaveUserInterface) hb_percent_done = (length(already_labeled_idx) + length(this_loop_already_labeled_idx))/cant_QRS_locations; waitbar( hb_percent_done, UCP_struct.fig_hdl, [ 'Classification progress ' num2str(round(hb_percent_done*100)) '%' ]) end else %for the slightly assisted, in the next section the %expert will do the labeling. if( strcmpi(op_mode, 'auto') ) %se deja la clasificacion autom�tica. Clust_Labels(aux_idx) = dsThisPatient_classification(aux_idx); this_loop_already_labeled_idx = [ this_loop_already_labeled_idx; colvec(aux_idx) ]; if( bHaveUserInterface) hb_percent_done = (length(already_labeled_idx) + length(this_loop_already_labeled_idx))/cant_QRS_locations; waitbar( hb_percent_done, UCP_struct.fig_hdl, [ 'Classification progress ' num2str(round(hb_percent_done*100)) '%' ]) end end end end end %% Clasificacion manual. % Aca consultamos al ORACLE. % Update point pb.checkpoint('Expert assistance'); if( any(strcmpi(op_mode, {'assisted' 'slightly-assisted'}) ) ) %Clustering + global classifier (GC) + oracle %Clustering + oracle %busco los centroides o algun elemento %representativo de cada cluster not_labeled_idx = setdiff(1:this_loop_cant_pending, this_loop_already_labeled_idx ); [~, sort_idx] = sort( Clust_Labels(not_labeled_idx) ); [all_clusters, aux_location] = unique(Clust_Labels(not_labeled_idx(sort_idx)), 'first'); m_not_labeled = length(not_labeled_idx); aux_location = [colvec(aux_location); m_not_labeled+1]; cluster_sizes = diff(aux_location); cant_clusters = size(all_clusters,1); ii = 1; while( ii <= cant_clusters ) %los latidos de este cluster aux_idx = sort_idx(aux_location(ii):(aux_location(ii+1)-1)); %si es muy grande lo sampleo uniformemente para %no generar mucho costo computacional. laux_idx = length(aux_idx); if( laux_idx > 5000 ) aux_idx2 = randsample(laux_idx, 5000); else aux_idx2 = 1:laux_idx; end %busco el centroide del cluster para %etuiquetarlo. clust_data_m = length(aux_idx2); if( clust_data_m > 1 ) clust_data = [ featMat_clust(pending_hb_idx(not_labeled_idx(aux_idx(aux_idx2))),:); featMat_clust(pending_hb_idx(not_labeled_idx(aux_idx(aux_idx2))),:) ]; clust_data = setlabels(clust_data,[ones(clust_data_m,1);2*ones(clust_data_m,1)] ); clust_data = setprior(clust_data,[1 1]./2 ); clust_data_model = qdc(clust_data, 1e-6, 1e-6); clust_data_posterior = clust_data * clust_data_model; clust_dist2mu = +clust_data_posterior(1:clust_data_m,1); [~, clust_dist2mu_sorted_idx ] = sort(clust_dist2mu, 'descend'); else clust_dist2mu_sorted_idx = 1; end if( ~bHaveUserInterface || bSimulateExpert ) clust_sample_centroid_idx = clust_dist2mu_sorted_idx(1); %a cada cluster le asignamos la etiqueta %VERDADERA del elemento mas cercano al centroide. Clust_Labels(not_labeled_idx(aux_idx)) = true_labels(not_labeled_idx(aux_idx(aux_idx2(clust_sample_centroid_idx)))); this_loop_already_labeled_idx = [ this_loop_already_labeled_idx; colvec(not_labeled_idx(aux_idx)) ]; if( bHaveUserInterface ) hb_percent_done = (length(already_labeled_idx) + length(this_loop_already_labeled_idx))/cant_QRS_locations; waitbar( hb_percent_done, UCP_struct.fig_hdl, [ 'Classification progress ' num2str(round(hb_percent_done*100)) '%' ]) end ii = ii + 1; else clust_sample_centroid_idx = clust_dist2mu_sorted_idx(1); fprintf(1, ['Suggestion: ' typical_lablist(true_labels(not_labeled_idx(aux_idx(aux_idx2(clust_sample_centroid_idx)))),:) '\n']); %find heartbeat samples to show to the expert. FindClusterExamples; % Ask for expert opinion [ Label bRefresh bCancel ] = ExpertUserInterface(cCentroid, cCloserExamples, cDistantExamples); if(bCancel) fprintf(2, 'Canceling current classification.\n'); if( bHaveUserInterface ) if( ishandle(UCP_struct.fig_hdl) ) waitbar(0, UCP_struct.fig_hdl, 'Classification progress 0%') set(UCP_struct.Axes_hdl, 'Visible','off' ); end end break; end if( ~bRefresh ) if( isempty(Label) ) bDataSkiped = true; disp(['Skipping ' num2str(clust_data_m) ' heartbeats to re-cluster.']) else Clust_Labels(not_labeled_idx(aux_idx)) = find( strcmpi(cellstr(typical_lablist), Label) ); this_loop_already_labeled_idx = [ this_loop_already_labeled_idx; colvec(not_labeled_idx(aux_idx)) ]; hb_percent_done = (length(already_labeled_idx) + length(this_loop_already_labeled_idx))/cant_QRS_locations; waitbar( hb_percent_done, UCP_struct.fig_hdl, [ 'Classification progress ' num2str(round(hb_percent_done*100)) '%' ]) end ii = ii + 1; end end end end if( ~bCancel ) %save the labeling done Labels(pending_hb_idx) = Clust_Labels; already_labeled_idx = [already_labeled_idx; colvec(pending_hb_idx(this_loop_already_labeled_idx))]; pending_hb_idx = setdiff(1:cant_QRS_locations, already_labeled_idx ); if(bHaveUserInterface) if( bDataSkiped ) %Maybe some fine tuning is necesary ... if( ~exist('UCP_struct', 'var') || ~ishandle(UCP_struct.fig_hdl) ) UserControlPanel; end cant_hb_pending = length(pending_hb_idx); fprintf(1, [num2str(cant_hb_pending) ' heartbeats remaining. Any fine tuning needed ? Change controls freely\n' ] ); if( cant_hb_pending < 90 ) % 10 * (k+1) * clusters recommended_clusters = max(2, round(cant_hb_pending/90)); fprintf(2, [ 'Too few heartbeats. Recommended clusters: ' num2str(recommended_clusters) ]); set(UCP_struct.CantClusters, 'Value', recommended_clusters); end % set(UCP_struct.fig_hdl, 'Visible','on' ); EnableControPanel; uiwait(UCP_struct.fig_hdl); if( ishandle(UCP_struct.fig_hdl) ) DisableControPanel; % set(UCP_struct.fig_hdl, 'Visible','off' ); else bCancel = true; fprintf(2, 'Canceling current classification.\n'); end end %to update current user interaction. ParseUserControlPanelInput; end end end %% Results generation if( bCancel ) %Cancelation of expert input ends here. bCancel = false; else % Update point pb.checkpoint('Results generation'); if( ~isempty(true_labels) ) [~, this_iter_cm] = DisplayResults( 'TrueLabels', typical_lablist(true_labels,:), ... 'class_filter', class_filter, ... 'ClassifiedLabels', typical_lablist(Labels,:), ... 'ClassLabels', typical_lablist); else %not possible to build a confusion matrix without the true labels. this_iter_cm = nan(typical_cant_labels); end end % All done if( bHaveUserInterface ) if( ishandle(pb) ) pb.checkpoint('Saving error report'); end end catch MException %% Error handling if( bHaveUserInterface) %% with UI if( isempty(MException.identifier) || ~isempty(strfind(MException.identifier, 'a2hbc')) || ~isempty(strfind(MException.identifier, 'ECGwrapper')) ) %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 with the user interface.\n'); else %other home-made errors. Make an educated exit ... rethrow(MException) end else %% Other unknown errors -> Report it to me if( userNoAnswers <= maxNoAnswers && strcmpi( 'Yes', questdlg('Oh no! An unexpected error ocurred. Please take a minute to report it so I can fix it and improve A2HBC. Report error information through Internet ?', 'Unhandled error', 'Yes', 'No', 'Yes')) ) %report an error if( ~exist('pb', 'var') ) % Activate the progress_struct bar. pb = progress_bar(rec_filename); end pb.checkpoint('Saving error report'); timestamp = datestr(now, 'HH-MM-SS_dd-mm-yy'); report_filename = [tmp_path 'error_report_' timestamp '_' rec_filename '.mat' ]; %clean heavy variables variables = whos(); [var_size, var_idx] = sort(cell2mat({variables(:).bytes})); cum_var_size = cumsum(var_size); last_var_idx = find( (cum_var_size/typical_compression_ratio) <= max_report_filesize, 1, 'last'); %save does not accept cell arrays as input -> workaround var_names = {variables(var_idx(1:last_var_idx)).name}; feval( @save, report_filename, var_names{:} ) pb.checkpoint('Compressing'); report_filename_gz = gzip( report_filename ); delete( report_filename ); pb.checkpoint('Reporting'); setpref('Internet','E_mail', 'a2hbc.errors@gmail.com'); setpref('Internet','SMTP_Server','smtp.gmail.com'); setpref('Internet','SMTP_Username','a2hbc.errors'); setpref('Internet','SMTP_Password', 'Any_password'); props = java.lang.System.getProperties; props.setProperty('mail.smtp.auth','true'); props.setProperty('mail.smtp.socketFactory.class', 'javax.net.ssl.SSLSocketFactory'); props.setProperty('mail.smtp.socketFactory.port','465'); % local_host = 'unknown'; % ip_address = 'unknown'; % location = 'unknown'; %just to identify reports local_host = java.net.InetAddress.getLocalHost; try ip_address = urlread('http://automation.whatismyip.com/n09230945.asp'); catch dummyME ip_address = ''; end try location = urlread('http://www.ipaddresslocation.org/my-ip-address.php'); catch dummyME location = ''; end computer_arch = computer(); computer_installation = ver(); lcomputer_installation = length(computer_installation); tab_aux = cellstr(repmat('\t\t', lcomputer_installation, 1)); crlf_aux = repmat('\n', lcomputer_installation, 1); computer_installation = strcat( colvec({computer_installation(:).Name}), tab_aux, colvec({computer_installation(:).Version}), tab_aux, colvec({computer_installation(:).Release}) ); computer_installation = [char(computer_installation) crlf_aux]; computer_installation = cellstr(sprintf(colvec(computer_installation'))); sendmail({'llamedom@unizar.es' 'llamedom@electron.frba.utn.edu.ar'}, ... %recipients ['[A2HBC Error reporting] ' char(local_host)], ... %subject [ cellstr(location) ; cellstr(ip_address) ; cellstr(computer_arch) ; computer_installation], ... %body report_filename_gz); %attach pb.checkpoint('Report sent successfully'); delete( char(report_filename_gz) ); fprintf(1, '\n------------------------\nReport sent successfully\n------------------------\n\nThis is the error information:\n\n'); else %try to convince user for the next time. fprintf( 2, '\nOk, thank you anyway. Please consider sending a report in case this error happens again.\n\n') if(userNoAnswers <= maxNoAnswers) userNoAnswers = userNoAnswers + 1; end end rethrow(MException); end else %% No User interface % this is obsolete now since ECGwrapper takes care about error reporting % % fprintf(2,'\n\n') % fprintf(2,'###########\n') % fprintf(2,'## ERROR ##\n') % fprintf(2,'###########\n') % % fprintf(2,'Recording: %s (%d/%d) \n', recording_name, this_pid, cant_pids); % % strAux = GetFunctionInvocation(mfilename, varargin); % % local_host = getenv('HOSTNAME'); % computer_arch = computer(); % % fprintf(2,'Computer: %s (%s) \n', local_host, computer_arch); % % fprintf(2, '%s\n\n', strAux); % % report = getReport(MException); % fprintf(2, '%s', report); % fprintf(2,'###########\n') % fprintf(2,'## ERROR ##\n') % fprintf(2,'###########\n') rethrow(MException) end end ConfusionMatrix(:,:,repeat_idx) = this_iter_cm; if( bHaveUserInterface && ( ~bArgCheckPassed || bInteractiveMode ) ) %% Ask User interaction % only if: the arguments were incorrect or I want to use a2hbc % in interactive mode. UserInteraction; else %If not user interface available -> unatended mode, just exit the loop. %bUserExit = true; repeat_idx = repeat_idx + 1; end end LabelList = typical_lablist; if( bECG_sample_provided ) delete([ tmp_path CachedFeatMatFileName]) end %save results when running without user interface. if( ~bHaveUserInterface ) save([tmp_path 'tmpfile_' rec_filename '_results_' op_mode '_' num2str(CantClusters) '_' num2str(iter_times) '_' num2str(cluster_presence) '_' num2str(this_pid) '.mat' ], 'Labels', 'ConfusionMatrix', 'LabelList'); % flag that the program ended correctly setenv('A2HBC_ES', '0'); end %%%%%%%%%%%%%%%%%% % Other functions %%%%%%%%%%%%%%%%%% function posterior = CalcLDCposterior(data, ldc_struct) [ cant_classes k ] = size(ldc_struct.mean); posterior = nan(size(data,1), cant_classes); for ii = 1:cant_classes centered_data = bsxfun( @minus, data, ldc_struct.mean(ii,:)); posterior(:,ii) = exp(-0.5*sum(centered_data'.*(ldc_struct.cov(:,:,ii)*centered_data'),1)' - (ldc_struct.det(ii) + k*log(2*pi))*0.5); end function RunNow(obj, event_obj) fig_hnd = gcf(); user_data = get(fig_hnd , 'User'); uiresume(fig_hnd); function SliderFunc(obj, event_obj) fig_hnd = gcf(); user_data = get(fig_hnd , 'User'); NewVal = round(get(obj , 'Value')); set(obj , 'Value', NewVal ); set(user_data.SliderLabel , 'String', [ num2str(NewVal) ' Clusters'] ); function SliderFunc2(obj, event_obj) fig_hnd = gcf(); user_data = get(fig_hnd , 'User'); NewVal = round(get(obj , 'Value')); set(obj , 'Value', NewVal ); strAux = [ num2str(NewVal) ' repetition']; if(NewVal > 1) strAux= [strAux 's']; end set(user_data.SliderLabel2 , 'String', strAux ); function SliderFunc3(obj, event_obj) fig_hnd = gcf(); user_data = get(fig_hnd , 'User'); NewVal = round(get(obj , 'Value')); set(obj , 'Value', NewVal ); set(user_data.SliderLabel3 , 'String', [ 'Cluster majority ' num2str(NewVal) ' %'] ); function UpdateClasses(obj, event_obj) fig_hnd = gcf(); user_data = get(fig_hnd , 'User'); Selection = get(obj , 'Value'); aux_val = get(user_data.class_filter , 'Value'); aux_str = get(user_data.class_filter , 'String'); class_filter = cellstr(aux_str(aux_val,:)); [~, aux_val] = intersect(user_data.typical_lablists{Selection}, class_filter ); set(user_data.class_filter , 'String', char(user_data.typical_lablists{Selection}) ); set(user_data.class_filter , 'Value', aux_val ); function OpModeSelect(obj, event_obj) fig_hnd = gcf(); user_data = get(fig_hnd , 'User'); Selection = get(obj , 'Value'); if( Selection > 2 ) set(user_data.cluster_presence , 'Enable', 'off' ); else set(user_data.cluster_presence , 'Enable', 'on' ); end function FormatCheck(obj, event_obj) fig_hnd = gcf(); user_data = get(fig_hnd , 'User'); NewVal = round(get(obj , 'Value')); set(obj , 'Value', NewVal ); set(user_data.SliderLabel3 , 'String', [ 'Cluster majority ' num2str(NewVal) ' %'] ); function BrowseRecording(obj, event_obj) fig_hnd = gcf(); user_data = get(fig_hnd , 'User'); recording_name = get(user_data.recording_name , 'String'); [recording_path, ~, recording_ext] = fileparts(recording_name); [recording_name, recording_path] = uigetfile([recording_path filesep '*.*'], 'Select a recording'); if( recording_name ~= 0 ) set(user_data.recording_name , 'String', [ recording_path recording_name]); end function BrowseTMPpath(obj, event_obj) fig_hnd = gcf(); user_data = get(fig_hnd , 'User'); tmp_path = get(user_data.tmp_path , 'String'); tmp_path = uigetdir(tmp_path, 'Select temporal directory'); if( tmp_path ~= 0 ) set(user_data.tmp_path , 'String', tmp_path); end function PathCheck(obj, event_obj) tmp_path = get(obj , 'String'); if( exist( tmp_path, 'dir' ) ) set(obj , 'String', tmp_path) set(obj , 'Tag', tmp_path) else fprintf(2, ['Path not found. ' tmp_path '\n'] ) set(obj , 'String', get(obj , 'Tag') ) end function RecordingCheck(obj, event_obj) recording = get(obj , 'String'); if( exist( recording, 'file' ) ) set(obj , 'String', recording) set(obj , 'Tag', recording) else fprintf(2, ['File not found. ' recording '\n'] ) set(obj , 'String', get(obj , 'Tag') ) end function CheckNumeric(obj, event_obj) number = get(obj , 'String'); if( isnan(str2double(number)) ) fprintf(2, ['Not a valid numeric value: ' number '\n'] ) set(obj , 'String', get(obj , 'Tag') ) else set(obj , 'String', number) set(obj , 'Tag', number) end