%% (Internal) GUI for correcting QRS detections % % Description: % This function implements a graphical user interface (GUI) to correct annotations % possibly previous performed by an automatic algorithm. The idea is to % easily visualize, compare and correct annotations. For this purpose the % GUI presents several representations of the time evolution of the events % (possibly but not necessarily heartbeats). % % Arguments: % % +ECG: [numeric or cell] % % [numeric]: signal matrix of dimension [sig_length sig_size % repetitions_size] where: % - sig_length: time length in samples % - sig_size: number of ECG leads or number of signals. % - repetitions_size: number of repetitions of the same % signals. Typically used when time-synchronized events, like % heartbeats. % % [cell]: cell array of length repetitions_size, where each cell % is (probably a time alligned event) a signal of dimension % [sig_length sig_size] % % +QRS_locations: [numeric] OPTIONAL. Default values enclosed in () % Synchronization sample. In ECG context, this values are % the QRS fiducial point. (empty) % % % +ECG_header: [struct] OPTIONAL. % % Description of the ECG typically available in the % header. Structure with fields: % % -freq: Sampling rate in Hz. (1) % % -nsig: Number of ECG leads. (size(ECG,2)) % % -nsamp: Number of ECG samples. (size(ECG,1)) % % -adczero: ADC offset (e.g. 2^(adc_res-1) when % using unsigned integers). ( repmat(0, ECG_header.nsig , 1) ) % % -adcgain: ADC gain in units/adc_sample % (typically uV/adc_unit). ( repmat(1, ECG_header.nsig , 1) ) % % -units: Measurement units. ( repmat('uV', ECG_header.nsig , 1) ) % % -desc: Signal description. ( num2str(colvec(1:ECG_header.nsig)) ) % % +QRS_annotations: [cell] OPTIONAL. Default values enclosed in () % Annotations to be included in the mosaic. The funcion % accepts 2 type of annotations: points and lines. % % Example: % % https://www.youtube.com/watch?v=qgWjvsvafVg&list=PLlD2eDv5CIe9sA2atmnb-DX48FIRG46z7&index=3 % % See also ECGtask_QRS_corrector % % Limits and Known bugs: % Probably a lot :( ... but dont panic! send me feedback if you need help. % % Author: Mariano Llamedo Soria (llamedom at {electron.frba.utn.edu.ar; unizar.es} % Version: 0.1 beta % Birthdate : 16/2/2012 % Last update: 7/2/2014 % Copyright 2008-2015 % function ann_output = QRScorrector(varargin) %% Constants cached_filename = 'tmp_QRScorrector_cache.mat'; cHeaderFieldNamesRequired = {'freq' 'nsamp' 'nsig' 'gain' 'adczero' }; cAnnotationsFieldNamesRequired = {'time', 'qrs' }; ann_output = []; %% Argument parsing %argument definition p = inputParser; % Create instance of inputParser class. p.addParamValue('FilterSignals', true, @(x)( islogical(x))); p.addParamValue('BuildArtificial', false, @(x)( islogical(x))); p.addParamValue('recording', [], @(x)( ischar(x))); p.addParamValue('recording_path', [], @(x)( ischar(x))); p.addParamValue('recording_indexes', [], @(x)( isnumeric(x) && all(x > 0) ) ); p.addParamValue('ECG', [], @(x)(isnumeric(x) || isa(x, 'ECGwrapper') ) ); p.addParamValue('ECG_header', [], @(x)(isstruct(x)) ); p.addParamValue('QRS_annotations', [], @(x)( (isnumeric(x) && all(x > 0)) || isstruct(x) ) ); p.addParamValue('OutputVarName', 'payload', @(x)( ischar(x))); p.addParamValue('tmp_path', [], @(x)(ischar(x)) ); p.addParamValue('Figure', [], @(x)(ishandle(x)) ); try p.parse( varargin{:} ); catch MyError rethrow(MyError); end hb_detail_window = 3; bFilter = p.Results.FilterSignals; bBuildArtificial = p.Results.BuildArtificial; recording = p.Results.recording; recording_path = p.Results.recording_path; recording_indexes = p.Results.recording_indexes; ECG = p.Results.ECG; ECG_header = p.Results.ECG_header; ECG_annotations = p.Results.QRS_annotations; tmp_path = p.Results.tmp_path; OutputVarName = p.Results.OutputVarName; fig_hdl = p.Results.Figure; title_efimero_hdl = []; RRserie_zoombars_hdl = []; RRserie_zoom_zoombars_hdl = []; Scatter_axes_hdl = []; RRserie_axes_hdl = []; RRserie_global_axes_hdl = []; RRserie_zoom_axes_hdl = []; ECG_axes_hdl = []; RRserie_selection_hdl = []; RRscatter_selection_hdl = []; copy_paste_buffer = []; % Dont know why this variable uses a lot of bytes to store at disk. clear p %% Argument parsing bLoadECG = true; ECG_w = []; start_idx = 1; win_size = 30; % minutes min_win_size = 1; % minutes max_win_size = 60; % minutes win_size_zoom = 5; % seconds min_win_size_zoom = 0.5; % seconds max_win_size_zoom = 30; % seconds if( ~isempty(ECG) ) %% ECG already read if( isempty(ECG_annotations)) disp_string_framed('*[1,0.5,0]', 'No annotations provided' ); else if( isstruct(ECG_annotations) ) if( any(isfield(ECG_annotations, cAnnotationsFieldNamesRequired )) ) % only one ann struct provided ECG_struct.provided_anns = ECG_annotations; else bFieldFound = false; for fn = rowvec(fieldnames(ECG_annotations)) if( ~isempty(intersect( fieldnames(ECG_annotations.(fn{1})), cAnnotationsFieldNamesRequired )) ) bFieldFound = true; break end end if( bFieldFound ) ECG_struct = ECG_annotations; else error( 'QRScorrector:ArgCheck:InvalidAnnotations', disp_option_enumeration( 'Please provide the following fields in the annotations struct:', cAnnotationsFieldNamesRequired) ); end end else ECG_struct.provided_anns.time = ECG_annotations; end end if( isa(ECG, 'ECGwrapper') ) % parse ECGwrapper object ECG_w = ECG; if( isempty(ECG_header) ) ECG_struct.header = ECG_w.ECG_header; end ECG_struct.signal = ECG_w.read_signal(start_idx, start_idx + round(win_size * 60 *ECG_struct.header.freq) + 10 * ECG_struct.header.freq ); else if( isempty(ECG_header)) error( 'QRScorrector: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( 'QRScorrector:ArgCheck:InvalidHeader', ['Please provide the following fields in the header struct:\n ' rowvec(strAux') ] ); end end ECG_struct.signal = ECG; ECG_struct.header = ECG_header; end clear ECG ECG_header ECG_annotations recording_indexes = 1; rec_names.name = ECG_struct.header.recname; if( isempty(recording_path) ) recording_path = [fileparts(mfilename('fullpath')) filesep 'tmp' filesep ]; end ratios = []; annotations_ranking = []; bLoadECG = false; elseif( ~isempty(recording) ) recording_indexes = 1; if( isempty(recording_path) ) [recording_path, rec_names.name, aux_ext] = fileparts(recording); else [~, rec_names.name, aux_ext] = fileparts(recording); end rec_names.name = [rec_names.name aux_ext]; recording_path = [recording_path filesep]; elseif( ~isempty(recording_path) ) if( ~exist(recording_path, 'dir') ) error('QRScorrector:ArgCheck:InvalidFolder', 'Folder does not exist.') end if( recording_path(end) ~= filesep ) recording_path = [ recording_path filesep ]; end rec_names = dir([recording_path '*.mat' ]); lrec_names = length(rec_names); if( lrec_names == 0) error([ 'No files found in ' recording_path ] ) else if( isempty( recording_indexes ) ) recording_indexes = 1:lrec_names; end lrec_names = length(recording_indexes); filename = [ recording_path 'tmp' filesep cached_filename ]; bAux = (exist(filename, 'file') > 0); if( bAux ) update_title_efimero(sprintf('Loading cached ratios from %s.\n', filename), 5 ); aux_val2 = load(filename); ratios = aux_val2.ratios; end if( ~bAux || length(ratios) ~= lrec_names) ratios = nan(lrec_names,1); pb = progress_bar( 'Processing ratios', sprintf( '%d recordings found', lrec_names ) ); pb.Loops2Do = lrec_names; for ii2 = recording_indexes pb.start_loop(); pb.checkpoint(sprintf( 'Loading recording %d', ii2 )); pepe = load([recording_path rec_names(ii2).name ], 'series_quality'); ratios(ii2) = max(pepe.series_quality.ratios); pb.end_loop(); end clear pb save(filename, 'ratios') end [ratios, worst_detections_idx] = sort(ratios); recording_indexes = recording_indexes(worst_detections_idx); end else % strAux = help('QRScorrector'); %#ok error( 'QRScorrector:ArgCheck:InvalidECGarg', 'Please provide an ECG recording as described in the documentation, help(''QRScorrector'') maybe could help you.\n' ); end end_idx = start_idx + round((win_size * 60)*ECG_struct.header.freq); if( isempty(tmp_path) ) tmp_path = recording_path; end %check path integrity. if(~exist(tmp_path, 'dir')) %try to create it if( ~mkdir(tmp_path) ) error('QRScorrector:ArgCheck:InvalidPath', 'Invalid tmp_path. Please provide a valid path.\n' ); end end major_tick_values_time = round([0.5 1 2 5 10 30 60]*ECG_struct.header.freq); % seconds major_tick_values_time = unique([major_tick_values_time major_tick_values_time*60 major_tick_values_time*60*60 ]); bAnnsEdited = false; bRecEdited = false; bSeries = false; all_annotations_selected = []; all_annotations_selected_serie_location = []; serie_location_mask = []; bFirstLoad = []; bSeriesChange = true; AnnNames = []; AnnNames_idx = []; all_annotations = []; estimated_labs = []; hb_idx = 1; lead_idx = 1; selected_hb_idx = []; RRscatter_hb_idx_hdl = []; RRserie_hb_idx_hdl = []; undo_buffer = []; undo_buffer_idx = 1; Pattern_hdl = []; anns_under_edition = []; RRserie = {}; RR_idx = {}; anns_under_edition_idx = []; if( isfield(ECG_struct.header, 'btime') ) aux_val2 = datevec(datenum(ECG_struct.header.btime, 'HH:MM:SS')); base_start_time = round((aux_val2(4) * 60 * 60 + aux_val2(5) * 60 + aux_val2(6)) * ECG_struct.header.freq); else base_start_time = 1; end ColorOrder = my_colormap( 12 ); all_markers = {'+','o','*','.','x','s','d','^','v','<','>','p','h'}; side_plot = []; fIsDragTimeAllowed = false; x_timeScroll_units = []; drag_timeScroll_start_x = []; k_timeScroll_units_pixel = []; bChangeWin = false; PrevStateWindowButtonMotionFcn = []; fIsDragAllowed = false; drag_start_x = []; drag_start_y = []; x_units = []; k_units_pixel = []; max_x_drag = []; min_y_drag = []; max_y_drag = []; filtro = []; rec_names = rec_names(recording_indexes,:); rec_idx = 1; recording_ratios = ratios; Xoffset = 0.09; Yoffset = 0.08; Xscale = 1.05; Yscale = 1.05; if( ECG_struct.header.nsamp > (win_size * 60 * ECG_struct.header.freq) ) win_size = min(win_size, ECG_struct.header.nsamp / 60 / ECG_struct.header.freq / 3); size_y_RR_global = 0.13; else size_y_RR_global = 0; end annotation_list_control = []; leads_control = []; recordings_control = []; annotation_under_edition_label = []; maximized_size = []; my_timer = timer('TimerFcn',@timer_fcn, 'StartDelay', 10); if( isempty(fig_hdl) ) fig_hdl = figure(); else clf(fig_hdl,'reset') end set(fig_hdl, 'WindowButtonDownFcn', @WindowButtonDownCallback2D); set(fig_hdl, 'WindowButtonUpFcn', @WindowButtonUpCallback2D); set(fig_hdl, 'WindowKeyPressFcn', @KeyPress); maximize(fig_hdl); maximized_size = get(fig_hdl, 'Position'); set(fig_hdl, 'Position', [ maximized_size(3:4) maximized_size(3:4) ] .* [ 0.05 0.13 0.95 0.9] ); set(fig_hdl,'CloseRequestFcn',@my_closefcn) DoRecording(); function my_closefcn(obj,event_obj) if( bRecEdited ) selection = questdlg('Unsaved results, edition will be lost. Exit anyway ?',... 'Close Request Function',... 'Yes','No','Yes'); switch selection, case 'Yes', stop(my_timer) delete(my_timer) delete(fig_hdl) case 'No' return end else stop(my_timer) delete(my_timer) delete(fig_hdl) end end function DoRecording() [~, rec_name] = fileparts(rec_names(rec_idx).name); rec_path = [tmp_path rec_name ]; if(bLoadECG) ECG_struct = load(rec_path); ECG_struct.header.recname = rec_name; end bAnnsEdited = false; bRecEdited = false; hb_idx = 1; lead_idx = 1; selected_hb_idx = []; RRscatter_hb_idx_hdl = []; RRserie_hb_idx_hdl = []; undo_buffer = []; undo_buffer_idx = 1; Pattern_hdl = []; % [~, ~, inv_dower_idx] = intersect({'I','II','V1','V2','V3','V4','V5','V6'}, cellstr(ECG_struct.header.desc)); % if( length(inv_dower_idx) == 8 ) % bUseDower = true; % else % inv_dower_idx = 1:ECG_struct.header.nsig; % bUseDower = false; % end ratios = []; AnnNames = []; all_annotations = []; estimated_labs = []; bFirstLoad = true; if( bFilter && isempty(filtro) ) filtro = bandpass_filter_design( ECG_struct.header.freq ); end % if( isfield(ECG_struct, 'noise_power') ) % noise_power = ECG_struct.noise_power; % else % noise_power = []; % end if( isfield(ECG_struct, 'series_quality' ) ) AnnNames = ECG_struct.series_quality.AnnNames; ratios = ECG_struct.series_quality.ratios; estimated_labs = ECG_struct.series_quality.estimated_labs; cant_anns = size(AnnNames,1); all_annotations = cell(cant_anns,1); for jj = 1:cant_anns all_annotations{jj} = ECG_struct.(AnnNames{jj,1}).(AnnNames{jj,2}); end else calc_ratios(); end if( isempty(ratios) ) annotations_ranking = []; else [ratios, best_detections_idx] = sort(ratios, 'descend'); aux_idx = find(cell2mat( cellfun(@(a)(~isempty(strfind(a, 'artificial'))), AnnNames(:,1), 'UniformOutput', false))); if( bBuildArtificial && isempty(aux_idx) && ~isempty(estimated_labs) ) % generate artificial annotations combining K best annotations aux_idx = best_detections_idx(1:min(10, length(best_detections_idx))); artificial_annotations = combine_anns(all_annotations(aux_idx), estimated_labs(aux_idx), ECG_struct.header); if( ~isempty(artificial_annotations) ) for ii = length(artificial_annotations):-1:1 aux_str = ['artificial_' num2str(ii)]; ECG_struct.(aux_str) = artificial_annotations(ii); end calc_ratios(); [ratios, best_detections_idx] = sort(ratios, 'descend'); end end aux_val = 1:length(ratios); [~, annotations_ranking] = sort(aux_val(best_detections_idx)); end % if( isfield(ECG_struct, 'corrected_anns' ) ) % AnnNames_idx = find(best_detections_idx == find(strcmp(AnnNames(:,1), 'corrected_anns') )); % if( isfield(ECG_struct.corrected_anns, 'time' ) ) % anns_under_edition = unique(colvec(ECG_struct.corrected_anns.time)); % else % anns_under_edition = unique(colvec(ECG_struct.corrected_anns)); % end % else % AnnNames_idx = 1; % anns_under_edition = unique(round(colvec( ECG_struct.(AnnNames{AnnNames_idx,1}).(AnnNames{AnnNames_idx,2}) ))); % end AnnNames_idx = 1; if( isempty(AnnNames) ) anns_under_edition = []; else aux_val = ECG_struct.(AnnNames{AnnNames_idx,1}).(AnnNames{AnnNames_idx,2}); if( size(aux_val, 1) > 1 && size(aux_val, 2) > 1 ) bSeries = true; [anns_under_edition, aux_idx] = unique(round(colvec( aux_val(:,1) ))); all_annotations_selected_serie_location = {aux_val(aux_idx,1) + round(aux_val(aux_idx,2) * ECG_struct.header.freq) }; else anns_under_edition = unique(round(colvec( aux_val ))); end end all_annotations_selected = {anns_under_edition}; Redraw(); set(annotation_list_control, 'Value', 1); set(leads_control, 'Value', 1); end function update_q_ratios(obj,event_obj) calc_ratios(); cant_anns = size(AnnNames,1); aux_str = repmat( ' - ',cant_anns,1); annotation_list_control = uicontrol( ... 'style','listbox', ... 'units','normalized', ... 'string', [ char(cellstr(num2str((1:cant_anns)'))) aux_str char(AnnNames(:,1)) repmat( ' (',cant_anns,1) num2str(round(colvec(ratios * 1000))) aux_str num2str(colvec(annotations_ranking)) repmat( ')',cant_anns,1) ] , ... 'position', [0.865 0.11 0.13 0.18] , ... 'min', 2, ... 'max', 4, ... 'callback', @ChangeAnnotationsSelected); end function calc_ratios() this_AnnNames = []; for fname = rowvec(fieldnames(ECG_struct)) if( isfield(ECG_struct.(fname{1}), 'time') ) this_AnnNames = [this_AnnNames; cellstr(fname{1}) cellstr('time')]; end if( isfield(ECG_struct.(fname{1}), 'qrs') ) this_AnnNames = [this_AnnNames; cellstr(fname{1}) cellstr('qrs')]; end end if( isempty(AnnNames) ) new_AnnNames_idx = 1:size(this_AnnNames,1); else [~, new_AnnNames_idx] = setdiff(this_AnnNames(:,1), AnnNames(:,1) ); end cant_anns = length(new_AnnNames_idx); aux_val = cell(cant_anns,1); jj = 1; for ii = rowvec(new_AnnNames_idx) aux_val{jj} = ECG_struct.(this_AnnNames{ii,1}).(this_AnnNames{ii,2}); jj = jj + 1; end if( isempty(aux_val) ) this_ratios = []; this_estimated_labs = []; else % ratios = CalcRRserieQuality(ECG_struct.signal, ECG_struct.header, all_annotations); [ this_ratios, this_estimated_labs ] = CalcRRserieRatio(aux_val, ECG_struct.header); end all_annotations = [all_annotations; aux_val ]; ratios = [ratios; this_ratios ]; estimated_labs = [estimated_labs; this_estimated_labs]; AnnNames = [AnnNames; this_AnnNames(new_AnnNames_idx,:)]; [ratios, best_detections_idx] = sort(ratios, 'descend'); aux_val = 1:length(ratios); [~, annotations_ranking] = sort(aux_val(best_detections_idx)); AnnNames = AnnNames(best_detections_idx,:); all_annotations = all_annotations(best_detections_idx); estimated_labs = estimated_labs(best_detections_idx); end function Redraw() % update only the one that can be edited if( bSeries ) aux_val = all_annotations_selected_serie_location{1}; % update the series values aux_RR = (aux_val - anns_under_edition) / ECG_struct.header.freq; all_annotations_selected(1) = {anns_under_edition}; this_all_anns = all_annotations_selected_serie_location; aux_val = this_all_anns{1}; aux_val(serie_location_mask) = nan; this_all_anns{1} = aux_val; RR_idx(1) = { find( isnan(anns_under_edition) | anns_under_edition >= start_idx & anns_under_edition <= end_idx ) } ; else if( length(anns_under_edition) < 2 ) aux_RR = []; this_all_anns = []; else aux_RR = colvec(diff(anns_under_edition)); aux_RR = [aux_RR(1); aux_RR] * 1/ECG_struct.header.freq; all_annotations_selected(1) = {anns_under_edition}; this_all_anns = all_annotations_selected; end RR_idx(1) = { find( anns_under_edition >= start_idx & anns_under_edition <= end_idx ) } ; end RRserie(1) = {aux_RR}; anns_under_edition_idx = RR_idx{1}; if( isempty(aux_RR) || all(isnan(aux_RR)) ) limits = [0.6 0.7]; else limits = prctile(aux_RR(anns_under_edition_idx), [1 99]); end aux_val = diff(limits); aux_val = max(0.01, 0.2*aux_val); limits(1) = limits(1) - aux_val; limits(2) = limits(2) + aux_val; % if( bLockScatter ) % x_lims_scatter = get(Scatter_axes_hdl, 'Xlim'); % y_lims_scatter = get(Scatter_axes_hdl, 'Ylim'); % end % if( bLockRRserie ) % x_lims_RRserie = get(RRserie_axes_hdl, 'Xlim'); % y_lims_RRserie = get(RRserie_axes_hdl, 'Ylim'); % end % figure(fig_hdl); % maximize(fig_hdl); % maximized_size = get(fig_hdl, 'Position'); % set(fig_hdl, 'Position', [ maximized_size(3:4) maximized_size(3:4) ] .* [ 0.05 0.13 0.95 0.9] ); % set(fig_hdl, 'Toolbar', 'none'); %% Axis de Poincaré if( isempty(Scatter_axes_hdl) ) if(size_y_RR_global > 0 ) Scatter_axes_hdl = axes('Position', ([0.55 0.41 0.355 0.52 ] - [ Xoffset Yoffset 0 0 ]) .* [ 1 1 Xscale Yscale ] - [0 0 0 size_y_RR_global], 'ColorOrder', ColorOrder, 'ButtonDownFcn',@inspect_scatter ) ; else Scatter_axes_hdl = axes('Position', ([0.55 0.46 0.355 0.52 ] - [ Xoffset Yoffset 0 0 ]) .* [ 1 1 Xscale Yscale ], 'ColorOrder', ColorOrder, 'ButtonDownFcn',@inspect_scatter ) ; end end aux_val_sc_op = get(Scatter_axes_hdl, 'OuterPosition'); aux_val_sc = get(Scatter_axes_hdl, 'Position'); cla(Scatter_axes_hdl) bRRempty = all(cellfun( @(a)(isempty(a)), RRserie)); if( bRRempty ) RRscatter_hdl = {}; hold(Scatter_axes_hdl, 'on') else hold(Scatter_axes_hdl, 'on') RRscatter_hdl = cellfun( @(this_rr_serie, this_rr_idx, ii)( plot(Scatter_axes_hdl, this_rr_serie(this_rr_idx) , [this_rr_serie(this_rr_idx(2:end)); this_rr_serie(this_rr_idx(end)) ], 'Marker', all_markers{ii}, 'LineStyle', 'none', 'MarkerEdgeColor', ColorOrder(ii,:), 'Color', ColorOrder(ii,:), 'ButtonDownFcn',@inspect_scatter ) ), RRserie, RR_idx, num2cell((1:length(RRserie))'), 'UniformOutput', false ); end if( ~bRRempty ) aux_RR = RRserie{1}; if( ~isempty(aux_RR) ) aux_RR = aux_RR(anns_under_edition_idx); aux_RRnext = [aux_RR(2:end); aux_RR(end)]; hold(Scatter_axes_hdl, 'on') RRscatter_selection_hdl = plot(Scatter_axes_hdl, aux_RR(selected_hb_idx), aux_RRnext(selected_hb_idx), 'xg', 'ButtonDownFcn',@inspect_scatter ); plot(Scatter_axes_hdl, limits, limits, ':r', 'LineWidth', 0.5, 'ButtonDownFcn',@inspect_scatter ); hold(Scatter_axes_hdl, 'off') end end % if( bLockScatter ) % set(Scatter_axes_hdl, 'Xlim', x_lims_scatter); % set(Scatter_axes_hdl, 'Ylim', y_lims_scatter); % else set(Scatter_axes_hdl, 'Xlim', limits); set(Scatter_axes_hdl, 'Ylim', limits); % % zoom reset % end title(Scatter_axes_hdl, ['Poincaré plot interval of ' Seconds2HMS((end_idx-start_idx+1)/ECG_struct.header.freq) ]); xlabel(Scatter_axes_hdl, 'Current value') ylabel(Scatter_axes_hdl, 'Next value') %% Axis de RR serie if( isempty(RRserie_axes_hdl) ) RRserie_axes_hdl = axes('Position', [(aux_val_sc(1) - 0.375)/2 (aux_val_sc(2)+aux_val_sc(4)/2) 0.375 aux_val_sc(4)/2 ] , 'ColorOrder', ColorOrder, 'ButtonDownFcn',@inspect_RRserie ); end cla(RRserie_axes_hdl) if( isempty(aux_RR) ) RRserie_hdl = {}; hold(RRserie_axes_hdl, 'on') else hold(RRserie_axes_hdl, 'on') RRserie_hdl = cellfun( @(this_anns, this_rr_serie, this_rr_idx, ii)( plot(RRserie_axes_hdl, this_anns(this_rr_idx) , this_rr_serie(this_rr_idx), 'Marker', all_markers{ii}, 'LineStyle', ':', 'MarkerEdgeColor', ColorOrder(ii,:), 'Color', ColorOrder(ii,:), 'ButtonDownFcn',@inspect_RRserie ) ), all_annotations_selected, RRserie, RR_idx, num2cell((1:length(RRserie))'), 'UniformOutput', false ); end if( ~isempty(aux_RR) ) aux_RR = RRserie{1}; RRserie_selection_hdl = plot(RRserie_axes_hdl, anns_under_edition(anns_under_edition_idx(selected_hb_idx)), aux_RR(anns_under_edition_idx(selected_hb_idx)), 'og' ); end if( isempty(anns_under_edition) ) k_units_pixel = []; else prev_units = get(RRserie_axes_hdl, 'Units'); set(RRserie_axes_hdl, 'Units', 'pixels'); this_pos = get(RRserie_axes_hdl, 'Position'); k_units_pixel = (anns_under_edition(anns_under_edition_idx(end)) - anns_under_edition(anns_under_edition_idx(1))) / this_pos(3); set(RRserie_axes_hdl, 'Units', prev_units); end % if( bLockRRserie ) % xlim(RRserie_axes_hdl, x_lims_RRserie); % ylim(RRserie_axes_hdl, y_lims_RRserie); % this_xlims = x_lims_RRserie; % this_ylims = y_lims_RRserie; % else if( isempty(aux_RR) ) x_max = 1; x_min = 0; this_ylims = [0.3 2]; else ylim(RRserie_axes_hdl, limits); this_ylims = limits; x_max = max(anns_under_edition(anns_under_edition_idx)); x_min = min(anns_under_edition(anns_under_edition_idx)); x_range = x_max - x_min; xlim(RRserie_axes_hdl, [ x_min - 0.05 * x_range x_max + 0.05 * x_range ]); end this_xlims = [ x_min x_max ]; % end % red box around this_xlims = this_xlims + 0.01*[ -diff(this_xlims) diff(this_xlims) ]; this_ylims = this_ylims + 0.01*[ diff(this_ylims) -diff(this_ylims) ]; set(fig_hdl,'CurrentAxes', RRserie_axes_hdl); aux_hdl = patch([this_xlims(1) this_xlims(1) this_xlims(2) this_xlims(2) this_xlims(1) ], [this_ylims(1) this_ylims(2) this_ylims(2) this_ylims(1) this_ylims(1)], [1 1 1], 'EdgeColor', [1 0 0], 'LineWidth', 1.5, 'ButtonDownFcn', @inspect_RRserie); set(aux_hdl, 'FaceColor', 'none') uistack(aux_hdl, 'bottom'); hold(RRserie_axes_hdl, 'off') if( length(aux_RR) > 5 ) cant_ticks = 5; aux_idx = round(linspace(x_min, x_max, cant_ticks)); set(RRserie_axes_hdl, 'XTick', rowvec(aux_idx) ); aux_str = cellstr(Seconds2HMS((aux_idx+base_start_time-1)*1/ECG_struct.header.freq)); set(RRserie_axes_hdl, 'XTickLabel', char(aux_str) ); end % xlabel(RRserie_axes_hdl, 'Time') ylabel(RRserie_axes_hdl, 'Serie value') title(RRserie_axes_hdl, [ 'Interval of ' Seconds2HMS((end_idx-start_idx+1)/ECG_struct.header.freq) ]); %% Axis de RR serie ZOOM if( isempty(RRserie_zoom_axes_hdl) ) RRserie_zoom_axes_hdl = axes('Position', [(aux_val_sc(1) - 0.375)/2 aux_val_sc(2) 0.375 aux_val_sc(4)/2.3 ], 'ColorOrder', ColorOrder ); end aux_val_RR_zoom = get(RRserie_zoom_axes_hdl, 'Position'); max_x_drag = aux_val_RR_zoom(1) + aux_val_RR_zoom(3); min_y_drag = aux_val_RR_zoom(2); aux_val_RR = get(RRserie_axes_hdl, 'Position'); max_y_drag = aux_val_RR(2)+aux_val_RR(4); if( isempty(RRserie) ) cla(RRserie_zoom_axes_hdl) else UpdateRRserieZoom(); end %% Axis de RR serie global if( bSeriesChange && size_y_RR_global > 0 ) bSeriesChange = false; if( isempty(RRserie_global_axes_hdl) ) RRserie_global_axes_hdl = axes('Position', [aux_val_RR(1) aux_val_sc(2)+1.13*aux_val_sc(4) aux_val_sc(1)+aux_val_sc(3)-aux_val_RR(1) size_y_RR_global ], 'ColorOrder', ColorOrder, 'ButtonDownFcn', @TimeOffsetClick ); end set(fig_hdl, 'CurrentAxes', RRserie_global_axes_hdl); cla(RRserie_global_axes_hdl) if( isempty(aux_RR) ) RRserie_hdl = []; hold(RRserie_global_axes_hdl, 'on') else hold(RRserie_global_axes_hdl, 'on') prev_units = get(RRserie_global_axes_hdl, 'Units'); set(RRserie_global_axes_hdl, 'Units', 'pixels'); % Downsample version: For efficient marks visualization and printing only target_res = 5; % samples/pixel axes_size = get(RRserie_global_axes_hdl, 'Position'); nsamp_target = axes_size(3) * target_res; set(RRserie_global_axes_hdl, 'Units', prev_units); max_length = max(cellfun( @(this_rr_serie)( length(this_rr_serie) ), RRserie )); down_factor = max(1, ceil( max_length / nsamp_target)); % RRserie_aux = cellfun( @(this_rr_serie)( resample(this_rr_serie, 1, down_factor, 60 ) ), RRserie, 'UniformOutput', false ); % cellfun( @(this_anns, this_rr_serie, ii)( plot(RRserie_global_axes_hdl, this_anns( round(linspace(1,length(this_anns),length(this_rr_serie))) ) , this_rr_serie, 'Marker', all_markers{ii}, 'LineStyle', ':', 'MarkerEdgeColor', ColorOrder(ii,:), 'Color', ColorOrder(ii,:), 'ButtonDownFcn', @TimeOffsetClick ) ), all_annotations_selected, RRserie_aux, num2cell((1:length(RRserie))') ); % downsample this way to deal with NaN values. not_nan_idx = cellfun( @(this_rr_serie)( ~isnan(this_rr_serie) ), RRserie, 'UniformOutput', false ); RRserie_aux = cellfun( @(this_anns, this_rr_serie, this_non_nan_idx)( interp1(this_anns(this_non_nan_idx), this_rr_serie(this_non_nan_idx), this_anns( round(linspace(1,length(this_anns), ceil(sum(this_non_nan_idx)/down_factor) ))), 'pchip') ), all_annotations_selected, RRserie, not_nan_idx, 'UniformOutput', false ); cellfun( @(this_anns, this_rr_serie, ii)( plot(RRserie_global_axes_hdl, this_anns( round(linspace(1,length(this_anns),length(this_rr_serie))) ) , this_rr_serie, 'Marker', all_markers{ii}, 'LineStyle', ':', 'MarkerEdgeColor', ColorOrder(ii,:), 'Color', ColorOrder(ii,:), 'ButtonDownFcn', @TimeOffsetClick ) ), all_annotations_selected, RRserie_aux, num2cell((1:length(RRserie))') ); limits = prctile(aux_RR, [1 99]); ylim(RRserie_global_axes_hdl, limits); % RRserie_zoombars_hdl = plot(RRserie_global_axes_hdl, repmat([ start_idx end_idx], 2, 1), repmat(limits,2,1)', 'LineWidth', 3, 'Color', 'r', 'ButtonDownFcn', @TimeOffsetClick ); RRserie_zoombars_hdl = patch([start_idx start_idx end_idx end_idx start_idx ], [limits(1) limits(2) limits(2) limits(1) limits(1)], [241 183 171]/255, 'EdgeColor', [1 0 0], 'ButtonDownFcn', @TimeOffsetClick, 'LineWidth', 0.5); uistack(RRserie_zoombars_hdl, 'bottom'); hold(RRserie_global_axes_hdl, 'off') max_end = min(cellfun( @(this_ann)( this_ann(end) ), all_annotations_selected )); min_start = min(cellfun( @(this_ann)( this_ann(1) ), all_annotations_selected )); max_range = max_end - min_start; xlim(RRserie_global_axes_hdl, [ min_start - 0.02*max_range max_end + 0.02*max_range ]); if( length(aux_RR) > 5 ) cant_ticks = 8; [~, major_tick_idx] = sort( abs(((anns_under_edition(end)-anns_under_edition(1))./major_tick_values_time - cant_ticks))); major_tick = major_tick_values_time(major_tick_idx(1)); aux_idx = round(1:major_tick:anns_under_edition(end)); set(RRserie_global_axes_hdl, 'XTick', rowvec(aux_idx) ); aux_str = cellstr(Seconds2HMS((aux_idx+base_start_time-1)*1/ECG_struct.header.freq)); set(RRserie_global_axes_hdl, 'XTickLabel', char(aux_str) ); end % xlabel(RRserie_global_axes_hdl, 'Time') ylabel(RRserie_global_axes_hdl, 'Serie value') k_timeScroll_units_pixel = (anns_under_edition(end) - anns_under_edition(1)) / this_pos(3); title(RRserie_global_axes_hdl, [ 'Interval of ' Seconds2HMS(ECG_struct.header.nsamp/ECG_struct.header.freq) ]); end end %% Axis de ECG if( isempty(ECG_axes_hdl) ) if(size_y_RR_global > 0 ) ECG_axes_hdl = axes('Position', [aux_val_RR_zoom(1) 0.07*aux_val_RR_zoom(2) aux_val_sc(1)+aux_val_sc(3)-aux_val_RR_zoom(1) 0.75*aux_val_RR_zoom(2)], 'ColorOrder', ColorOrder ); else ECG_axes_hdl = axes('Position', [aux_val_RR_zoom(1) 0.07*aux_val_RR_zoom(2) aux_val_sc(1)+aux_val_sc(3)-aux_val_RR_zoom(1) 0.75*aux_val_RR_zoom(2)], 'ColorOrder', ColorOrder ); end end if( isempty(anns_under_edition_idx) ) ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, [] , hb_detail_window, ECG_struct.header, filtro, ECG_axes_hdl); else ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, anns_under_edition_idx(hb_idx) , hb_detail_window, ECG_struct.header, filtro, ECG_axes_hdl); end if( length(lead_idx) > 1 ) aux_str = rowvec(colvec([repmat(',', length(lead_idx), 1) ECG_struct.header.desc(lead_idx,:) ]')); title(ECG_axes_hdl, ['Heartbeat ' num2str(hb_idx) ' : Leads ' aux_str(2:end) ] ) else title(ECG_axes_hdl, ['Heartbeat ' num2str(hb_idx) ' : Lead ' ECG_struct.header.desc(lead_idx,:)] ) end % xlabel(ECG_axes_hdl, 'Sample #') ylabel(ECG_axes_hdl, 'ECG') % zoom reset %% Controls if( isempty(annotation_list_control) || bFirstLoad ) bFirstLoad = false; %% Recording list aux_str = repmat( ' - ', length(recording_indexes),1); recordings_control = uicontrol( ... 'style','listbox', ... 'units','normalized', ... 'string', [ char(cellstr(num2str(colvec(recording_indexes)))) aux_str num2str(round(recording_ratios*1000)) aux_str char(rec_names.name) ] , ... 'position', [0.865 0.75 0.13 0.2] , ... 'min', 1, ... 'max', 1, ... 'Value', rec_idx, ... 'callback', @ChangeRecordingSelected); uicontrol( ... 'style','text', ... 'string', 'Available Recordings', ... 'units','normalized', ... 'position', [0.865 0.96 0.13 0.025] ); %% Signal list leads_control = uicontrol( ... 'style','listbox', ... 'units','normalized', ... 'string', [ char(cellstr(num2str((1:ECG_struct.header.nsig)'))) repmat( ' - ',ECG_struct.header.nsig,1) ECG_struct.header.desc ] , ... 'position', [0.865 0.4 0.13 0.3] , ... 'min', 2, ... 'max', 4, ... 'Value', lead_idx, ... 'callback', @ChangeLeadsSelected); uicontrol( ... 'style','text', ... 'string', 'Available signals', ... 'units','normalized', ... 'position', [0.865 0.71 0.13 0.025] ); %% Annotation list if( isempty(AnnNames) ) aux_str = 'manually_created'; aux_label_str = 'Annotation under edition: manually-created (NaN)'; AnnNames = [ {aux_str} {'time'} ]; ECG_struct.(aux_str).time = []; else cant_anns = size(AnnNames,1); aux_str = repmat( ' - ',cant_anns,1); aux_str = [ char(cellstr(num2str((1:cant_anns)'))) aux_str char(AnnNames(:,1)) repmat( ' (',cant_anns,1) num2str(round(colvec(ratios * 1000))) aux_str num2str(colvec(annotations_ranking)) repmat( ')',cant_anns,1) ]; aux_label_str = [ 'Annotation under edition: ' char(AnnNames( AnnNames_idx ,1)) ' (' num2str(ratios(AnnNames_idx)) ')' ]; end annotation_list_control = uicontrol( ... 'style','listbox', ... 'units','normalized', ... 'string', aux_str, ... 'position', [0.865 0.11 0.13 0.18] , ... 'min', 2, ... 'max', 4, ... 'callback', @ChangeAnnotationsSelected); uicontrol( ... 'style','text', ... 'string', 'Annotations available', ... 'units','normalized', ... 'position', [0.865 0.30 0.13 0.025] ); annotation_under_edition_label = uicontrol( ... 'style','text', ... 'string', aux_label_str, ... 'units','normalized', ... 'position', [0.865 0.35 0.13 0.05] ); uicontrol( ... 'style','pushbutton', ... 'string', 'Delete Annotations', ... 'units','normalized', ... 'position', [0.865 0.07 0.063 0.03], ... 'callback', @DeleteAnnotations); uicontrol( ... 'style','pushbutton', ... 'string', 'Upd Q ratios', ... 'units','normalized', ... 'position', [0.93 0.07 0.063 0.03], ... 'callback', @update_q_ratios); end % if( ~isempty(RRscatter_hdl) ) % set(RRscatter_hdl,'ButtonDownFcn',@inspect_scatter); % end % set(Scatter_axes_hdl,'ButtonDownFcn',@inspect_scatter); % if( ~isempty(RRserie_hdl) ) % set(RRserie_hdl, 'ButtonDownFcn',@inspect_RRserie); % end % set(RRserie_axes_hdl,'ButtonDownFcn',@inspect_RRserie); cellfun(@(a)( set(a,'ButtonDownFcn',@inspect_ECG)), ECG_hdl); set(ECG_axes_hdl,'ButtonDownFcn',@inspect_ECG); end function DragMouseBegin() %DragMouseBegin begin draging if ( ~fIsDragAllowed ) [drag_start_x, drag_start_y] = GetCursorCoordOnWindow(); fIsDragAllowed = true; PrevStateWindowButtonMotionFcn = get(fig_hdl, 'WindowButtonMotionFcn'); set(fig_hdl, 'WindowButtonMotionFcn', @WindowButtonMotionCallback2D); % fprintf(1, 'on\n'); end end function DragMouseEnd() %DragMouseEnd end draging if fIsDragAllowed fIsDragAllowed = false; set(fig_hdl, 'WindowButtonMotionFcn', PrevStateWindowButtonMotionFcn); % fprintf(1, 'off\n'); if ( ~fIsDragTimeAllowed ) if( bSeries ) this_all_anns = all_annotations_selected_serie_location; aux_val = this_all_anns{1}; aux_val(serie_location_mask) = nan; this_all_anns{1} = aux_val; else this_all_anns = all_annotations_selected; end ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, anns_under_edition_idx(hb_idx) , hb_detail_window, ECG_struct.header, filtro, ECG_axes_hdl); cellfun(@(a)( set(a,'ButtonDownFcn',@inspect_ECG)), ECG_hdl); end end end function [xp, yp ] = GetCursorCoordOnWindow() %GetCursorCoordOnWindow dfltUnits = get(fig_hdl, 'Units'); set(fig_hdl, 'Units', 'pixels'); crd = get(fig_hdl, 'CurrentPoint'); xp = crd(1); yp = crd(2); set(fig_hdl, 'Units', dfltUnits); end function WindowButtonMotionCallback2D(src, evnt) %#ok %WindowButtonMotionCallback2D % RR serie part if( fIsDragAllowed && ~isempty(x_units) ) [drag_x, drag_y] = GetCursorCoordOnWindow(); deltax = drag_x - drag_start_x; if( bChangeWin ) win_size_zoom = min(max_win_size_zoom, max( min_win_size_zoom, win_size_zoom + (( deltax * 0.1 ) * k_units_pixel / ECG_struct.header.freq ) )); % set(RRserie_zoombars_hdl, 'Xdata', [start_idx start_idx end_idx end_idx start_idx ]); update_title_efimero( sprintf('%s', Seconds2HMS(win_size_zoom)), 5 ); UpdateRRserieZoom(); else this_x_units = x_units + ( deltax * 0.1 ) * k_units_pixel; [~, aux_val] = sort( abs( this_x_units - anns_under_edition(anns_under_edition_idx)) ); aux_val = aux_val(1); hb_idx = max(1, min(length(anns_under_edition_idx), aux_val )); % disp(hb_idx) UpdateRRserieZoom(); % if( ishandle(RRserie_hb_idx_hdl) ) % delete(RRserie_hb_idx_hdl); % end aux_RR = RRserie{1}; % hold(RRserie_axes_hdl, 'on') % RRserie_hb_idx_hdl = plot(RRserie_axes_hdl, anns_under_edition(anns_under_edition_idx(hb_idx)), aux_RR(anns_under_edition_idx(hb_idx)), 'or' ); % hold( RRserie_axes_hdl, 'off') set(RRserie_hb_idx_hdl, 'Xdata', anns_under_edition(anns_under_edition_idx(hb_idx)) ); set(RRserie_hb_idx_hdl, 'Ydata', aux_RR(anns_under_edition_idx(hb_idx)) ); % ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, all_annotations_selected, start_idx, anns_under_edition_idx(hb_idx) , hb_detail_window, ECG_struct.header, filtro, ECG_axes_hdl); end else % fprintf(1, 'wait\n'); end % RR global part if( fIsDragTimeAllowed ) [drag_x, drag_y] = GetCursorCoordOnWindow(); deltax = drag_x - drag_timeScroll_start_x; UpdateStartX(deltax); end end function UpdateStartX(deltax) if( bChangeWin ) win_size = min(max_win_size, max( min_win_size, win_size + (( deltax * 0.1 ) * k_timeScroll_units_pixel * 1/ECG_struct.header.freq/60 ) )); update_title_efimero( sprintf('%s', Seconds2HMS(win_size*60)), 5); else start_idx = max(1, round(min( ECG_struct.header.nsamp - (win_size * 60 * ECG_struct.header.freq), x_timeScroll_units + ( deltax ) * k_timeScroll_units_pixel)) ); end end_idx = max((min_win_size * 60 * ECG_struct.header.freq), min( ECG_struct.header.nsamp, start_idx + round((win_size * 60)*ECG_struct.header.freq))); set(RRserie_zoombars_hdl, 'Xdata', [start_idx start_idx end_idx end_idx start_idx ]); aux_idx = get(RRserie_axes_hdl, 'XTick' ); aux_str = repmat({''},length(aux_idx),1); aux_str(1) = {Seconds2HMS((start_idx+base_start_time-1)*1/ECG_struct.header.freq)}; aux_str(end) = {Seconds2HMS((end_idx+base_start_time-1)*1/ECG_struct.header.freq)}; set(RRserie_axes_hdl, 'XTickLabel', char(aux_str) ); end function inspect_scatter(obj,event_obj) if( strcmp(get(fig_hdl,'SelectionType'),'alt')) % Delete annotation aux_RR = RRserie{1}; aux_RR = aux_RR(anns_under_edition_idx); point = get(gca,'CurrentPoint'); [~, hb_idx] = min(sum( bsxfun(@minus, point(1,1:2), [aux_RR, [aux_RR(2:end); aux_RR(end)]]).^2,2)); PushUndoAction(); if( bSeries ) anns_under_edition(hb_idx) = nan; else anns_under_edition(hb_idx) = []; end selected_hb_idx = []; Redraw(); bAnnsEdited = true; bRecEdited = true; else if (strcmp(get(fig_hdl,'SelectionType'),'extend')) point1 = get(gca,'CurrentPoint'); % button down detected rbbox; point2 = get(gca,'CurrentPoint'); % button up detected if( ~isempty(selected_hb_idx) ) delete(RRserie_selection_hdl) delete(RRscatter_selection_hdl) end xlims = sort([ point1(1,1) point2(1,1) ]); ylims = sort([ point1(1,2) point2(1,2) ]); aux_RRcurr = RRserie{1}; aux_RRcurr = aux_RRcurr(anns_under_edition_idx); aux_RRnext = [aux_RRcurr(2:end); aux_RRcurr(end)]; selected_hb_idx = find( aux_RRcurr > xlims(1) & aux_RRcurr < xlims(2) & aux_RRnext > ylims(1) & aux_RRnext < ylims(2) ); update_title_efimero([num2str(length(selected_hb_idx)) ' heartbeats selected.'], 5 ); hold(Scatter_axes_hdl, 'on') RRnext = [RRserie(2:end); RRserie(end)]; RRscatter_selection_hdl = plot(Scatter_axes_hdl, aux_RRcurr(anns_under_edition_idx(selected_hb_idx)), aux_RRnext(anns_under_edition_idx(selected_hb_idx)), 'xg' ); hold(Scatter_axes_hdl, 'off') hold(RRserie_axes_hdl, 'on') RRserie_selection_hdl = plot(RRserie_axes_hdl, anns_under_edition(anns_under_edition_idx(selected_hb_idx)) , aux_RRcurr(selected_hb_idx), 'og' ); hold(RRserie_axes_hdl, 'off') min_hb_idx = min(selected_hb_idx); max_hb_idx = max(selected_hb_idx); cant_hb_idx = max(selected_hb_idx) - min_hb_idx + 1; if( (anns_under_edition(anns_under_edition_idx(max_hb_idx)) - anns_under_edition(anns_under_edition_idx(min_hb_idx))) <= (10*ECG_struct.header.freq) ) if( bSeries ) this_all_anns = all_annotations_selected_serie_location; aux_val = this_all_anns{1}; aux_val(serie_location_mask) = nan; this_all_anns{1} = aux_val; else this_all_anns = all_annotations_selected; end ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, anns_under_edition_idx(hb_idx) , hb_detail_window, ECG_struct.header, filtro, ECG_axes_hdl); cellfun(@(a)( set(a,'ButtonDownFcn',@inspect_ECG)), ECG_hdl); end if( length(lead_idx) > 1 ) aux_str = rowvec(colvec([repmat(',', length(lead_idx), 1) ECG_struct.header.desc(lead_idx,:) ]')); title(ECG_axes_hdl, ['Heartbeat ' num2str(hb_idx) ' : Leads ' aux_str(2:end) ] ) else title(ECG_axes_hdl, ['Heartbeat ' num2str(min_hb_idx) ' : Lead ' ECG_struct.header.desc(lead_idx,:)] ) end % zoom reset set(ECG_axes_hdl,'ButtonDownFcn',@inspect_ECG); % Zoom RR serie UpdateRRserieZoom(); else % Show ECG point = get(gca,'CurrentPoint'); if( ishandle(RRscatter_hb_idx_hdl) ) delete(RRscatter_hb_idx_hdl) end if( ishandle(RRserie_hb_idx_hdl) ) delete(RRserie_hb_idx_hdl) end aux_RRcurr = RRserie{1}; aux_RRcurr = aux_RRcurr(anns_under_edition_idx); [~, hb_idx] = min(sum( bsxfun(@minus, point(1,1:2), [aux_RRcurr(1:end-1), aux_RRcurr(2:end)]).^2,2)); hold(Scatter_axes_hdl, 'on') RRscatter_hb_idx_hdl = plot(Scatter_axes_hdl, aux_RRcurr(hb_idx), aux_RRcurr(hb_idx+1), 'or' ); hold(Scatter_axes_hdl, 'off') hold(RRserie_axes_hdl, 'on') RRserie_hb_idx_hdl = plot(RRserie_axes_hdl, anns_under_edition(anns_under_edition_idx(hb_idx)) , aux_RRcurr(hb_idx), 'or' ); hold(RRserie_axes_hdl, 'off') if( bSeries ) this_all_anns = all_annotations_selected_serie_location; aux_val = this_all_anns{1}; aux_val(serie_location_mask) = nan; this_all_anns{1} = aux_val; else this_all_anns = all_annotations_selected; end ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, anns_under_edition_idx(hb_idx) , hb_detail_window, ECG_struct.header, filtro, ECG_axes_hdl); if( length(lead_idx) > 1 ) aux_str = rowvec(colvec([repmat(',', length(lead_idx), 1) ECG_struct.header.desc(lead_idx,:) ]')); title(ECG_axes_hdl, ['Heartbeat ' num2str(hb_idx) ' : Leads ' aux_str(2:end) ] ) else title(ECG_axes_hdl, ['Heartbeat ' num2str(hb_idx) ' : Lead ' ECG_struct.header.desc(lead_idx,:)] ) end % zoom reset cellfun(@(a)( set(a,'ButtonDownFcn',@inspect_ECG)), ECG_hdl); set(ECG_axes_hdl,'ButtonDownFcn',@inspect_ECG); % Zoom RR serie UpdateRRserieZoom(); end end end function inspect_ECG(obj,event_obj) % disp(get(fig_hdl,'SelectionType')) if (strcmp(get(fig_hdl,'SelectionType'),'alt')) % Delete annotation point = get(gca,'CurrentPoint'); point(1) = point(1) + start_idx - 1; [~, hb_idx] = min(abs( point(1) - anns_under_edition(anns_under_edition_idx))); PushUndoAction(); if( bSeries ) serie_location_mask(anns_under_edition_idx(hb_idx)) = true; else anns_under_edition(anns_under_edition_idx(hb_idx)) = []; end selected_hb_idx( selected_hb_idx > length(anns_under_edition_idx) | selected_hb_idx == hb_idx ) = []; if( isempty(anns_under_edition) ) hb_idx = []; else hb_idx = max(1, hb_idx - 1); end Redraw(); bAnnsEdited = true; bRecEdited = true; elseif (strcmp(get(fig_hdl,'SelectionType'),'normal')) % Show ECG point = get(gca,'CurrentPoint'); point(1) = point(1) + start_idx - 1; PushUndoAction(); if( bSeries ) % only modify the location, not adding allowed. aux_val = all_annotations_selected_serie_location{1}; [~, hb_idx] = min(abs( point(1) - aux_val(anns_under_edition_idx))); % refine the point, and enable it. aux_val(anns_under_edition_idx(hb_idx)) = round(point(1)); serie_location_mask(anns_under_edition_idx(hb_idx)) = false; all_annotations_selected_serie_location{1} = aux_val; else % add a regular event when the anns are not series. aux_val = round(point(1)); anns_under_edition = sort([anns_under_edition; aux_val ]); selected_hb_idx = find(aux_val == anns_under_edition(anns_under_edition_idx), 1); hb_idx = selected_hb_idx; end Redraw(); bAnnsEdited = true; bRecEdited = true; elseif (strcmp(get(fig_hdl,'SelectionType'),'extend')) % Show ECG point = get(gca,'CurrentPoint'); aux_val = round(point(1)); aux_val = aux_val + start_idx - 1; if( isempty(side_plot) || ~ishandle(side_plot) ) side_plot = figure(); set(side_plot, 'Position', [ maximized_size(3:4) maximized_size(3:4) ] .* [ 0 0 0.95 0.9] ); else figure(side_plot); end plot_ecg_strip(ECG_struct.signal, ... 'ECG_header', ECG_struct.header, ... 'QRS_locations', anns_under_edition, ... 'Start_time', aux_val/ECG_struct.header.freq - 1 , ... 'End_time', aux_val/ECG_struct.header.freq + 1 ); figure(fig_hdl); end end function KeyPress(obj,event_obj) if (strcmp(event_obj.Key,'c') && strcmp(event_obj.Modifier,'control')) % copy selection if( ~isempty(selected_hb_idx) ) copy_paste_buffer = anns_under_edition(selected_hb_idx); update_title_efimero('Selection copied', 5 ); end elseif (strcmp(event_obj.Key,'v') && strcmp(event_obj.Modifier,'control')) % paste selection if( ~isempty(copy_paste_buffer) ) bAnnsEdited = true; bRecEdited = true; PushUndoAction(); anns_under_edition = sort( unique([anns_under_edition; colvec(copy_paste_buffer) ]) ); selected_hb_idx = []; Redraw(); end elseif (strcmp(event_obj.Key,'delete')) % delete selection if( ~isempty(selected_hb_idx) ) bAnnsEdited = true; bRecEdited = true; PushUndoAction(); if( isempty(hb_idx) ) hb_idx = 1; else hb_idx = find(anns_under_edition < anns_under_edition(hb_idx), 1, 'first' ); end if( bSeries ) anns_under_edition(anns_under_edition_idx(selected_hb_idx)) = nan; else anns_under_edition(anns_under_edition_idx(selected_hb_idx)) = []; end selected_hb_idx = []; Redraw(); end elseif (strcmp(event_obj.Key,'y') && ~isempty(event_obj.Modifier) && strcmp(event_obj.Modifier,'control')) % redo last change if( undo_buffer_idx < length(undo_buffer) ) undo_buffer_idx = undo_buffer_idx + 1; anns_under_edition = undo_buffer{undo_buffer_idx}; selected_hb_idx = []; Redraw(); end elseif (strcmp(event_obj.Key,'z') && strcmp(event_obj.Modifier,'control')) % undo last change if( undo_buffer_idx > 1 ) undo_buffer_idx = undo_buffer_idx - 1; anns_under_edition = undo_buffer{undo_buffer_idx}; selected_hb_idx = []; Redraw(); end elseif (strcmp(event_obj.Key,'rightarrow') && ~isempty(event_obj.Modifier) && strcmp(event_obj.Modifier,'control')) if(bRecEdited) update_annotations(); if( bLoadECG ) update_title_efimero('Saving data ...', 5 ); save(rec_path, '-struct', 'ECG_struct'); update_title_efimero(['Saved ' rec_path], 5 ); if( rec_idx >= 1 && rec_idx < length(recording_indexes) ) rec_idx = rec_idx + 1; else rec_idx = 1; end set(recordings_control, 'Value', rec_idx ); DoRecording(); else if( isfield(ECG_struct, 'series_quality' ) ) anns_struct.series_quality = ECG_struct.series_quality; end for ii = 1:size(AnnNames,1) anns_struct.(AnnNames{ii,1}) = ECG_struct.(AnnNames{ii,1}); end assignin( 'caller', OutputVarName, anns_struct ); update_title_efimero(sprintf('Saving ''%s'' variable in caller workspace', OutputVarName), 5 ); bAnnsEdited = false; bRecEdited = false; end end elseif (strcmp(event_obj.Key,'leftarrow') && ~isempty(event_obj.Modifier) && strcmp(event_obj.Modifier,'control')) if(bRecEdited) update_annotations(); if( bLoadECG ) update_title_efimero('Saving data ...', 5 ); save(rec_path, '-struct', 'ECG_struct'); update_title_efimero(['Saved ' rec_path], 5 ); if( rec_idx > 1 && rec_idx <= length(recording_indexes) ) rec_idx = rec_idx - 1; else rec_idx = length(recording_indexes); end set(recordings_control, 'Value', rec_idx ); DoRecording(); else if( isfield(ECG_struct, 'series_quality' ) ) anns_struct.series_quality = ECG_struct.series_quality; end for ii = 1:size(AnnNames,1) anns_struct.(AnnNames{ii,1}) = ECG_struct.(AnnNames{ii,1}); end assignin( 'caller', OutputVarName, anns_struct ); update_title_efimero(sprintf('Saving ''%s'' variable in caller workspace', OutputVarName), 5 ); bAnnsEdited = false; bRecEdited = false; end end elseif (strcmp(event_obj.Key,'p') ) update_title_efimero('Click and drag the time interval where the pattern is ...', 10 ); set(obj,'CurrentAxes', ECG_axes_hdl); waitforbuttonpress; SearchPattern(); elseif ( ~isempty(event_obj.Modifier) && strcmp(event_obj.Key,'g') && strcmp(event_obj.Modifier,'control')) if(bRecEdited) update_annotations(); if( bLoadECG ) update_title_efimero('Saving data ...', 5 ); save(rec_path, '-struct', 'ECG_struct'); update_title_efimero(['Saved ' rec_path], 5 ); else if( isfield(ECG_struct, 'series_quality' ) ) anns_struct.series_quality = ECG_struct.series_quality; end for ii = 1:size(AnnNames,1) anns_struct.(AnnNames{ii,1}) = ECG_struct.(AnnNames{ii,1}); end assignin( 'caller', OutputVarName, anns_struct ); update_title_efimero(sprintf('Saving ''%s'' variable in caller workspace', OutputVarName), 5 ); bAnnsEdited = false; bRecEdited = false; end end elseif (strcmp(event_obj.Key,'t')) %% Toggle ECG lead if( length(lead_idx) > 1 ) lead_idx = 1; else if( lead_idx < ECG_struct.header.nsig ) lead_idx = lead_idx + 1; else lead_idx = 1:ECG_struct.header.nsig; end end cla(ECG_axes_hdl); if( bSeries ) this_all_anns = all_annotations_selected_serie_location; aux_val = this_all_anns{1}; aux_val(serie_location_mask) = nan; this_all_anns{1} = aux_val; else this_all_anns = all_annotations_selected; end ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, anns_under_edition_idx(hb_idx) , hb_detail_window, ECG_struct.header, filtro, ECG_axes_hdl); if( length(lead_idx) > 1 ) aux_str = rowvec(colvec([repmat(',', length(lead_idx), 1) ECG_struct.header.desc(lead_idx,:) ]')); title(ECG_axes_hdl, ['Heartbeat ' num2str(hb_idx) ' : Leads ' aux_str(2:end) ] ) else title(ECG_axes_hdl, ['Heartbeat ' num2str(hb_idx) ' : Lead ' ECG_struct.header.desc(lead_idx,:)] ) end % zoom reset cellfun(@(a)( set(a,'ButtonDownFcn',@inspect_ECG)), ECG_hdl); set(ECG_axes_hdl,'ButtonDownFcn',@inspect_ECG); elseif (strcmp(event_obj.Key,'rightarrow')) %% right arrow if(bAnnsEdited) update_annotations(); end AnnNames_idx = AnnNames_idx + 1; if( AnnNames_idx > length(AnnNames) ) AnnNames_idx = 1; end update_title_efimero(['Using ' AnnNames{AnnNames_idx,1} ' annotations (' num2str(ratios(AnnNames_idx)) ')'], 5 ); undo_buffer_idx = 1; anns_under_edition = unique(round(colvec( ECG_struct.(AnnNames{AnnNames_idx,1}).(AnnNames{AnnNames_idx,2}) ))); bAnnsEdited = false; selected_hb_idx = []; Redraw(); elseif (strcmp(event_obj.Key,'leftarrow')) %% left arrow if(bAnnsEdited) update_annotations(); end AnnNames_idx = AnnNames_idx - 1; if( AnnNames_idx < 1 ) AnnNames_idx = length(AnnNames); end update_title_efimero(['Using ' AnnNames{AnnNames_idx,1} ' annotations (' num2str(ratios(AnnNames_idx)) ')'], 5 ); undo_buffer_idx = 1; anns_under_edition = unique(round(colvec( ECG_struct.(AnnNames{AnnNames_idx,1}).(AnnNames{AnnNames_idx,2}) ))); bAnnsEdited = false; selected_hb_idx = []; Redraw(); else % bLockRRserie = false; % bLockScatter = false; % lead_idx = 1:ECG_struct.header.nsig; end end function PushUndoAction() if( undo_buffer_idx < length(undo_buffer) ) %me cargo todo lo que está por rehacerse undo_buffer = undo_buffer(1:undo_buffer_idx); end undo_buffer{undo_buffer_idx} = anns_under_edition; undo_buffer_idx = undo_buffer_idx + 1; end function this_point = get_current_point() point = get(gca,'CurrentPoint'); lanns_under_ed = length(anns_under_edition_idx); [~, aux_val] = sort( abs(point(1) - anns_under_edition(anns_under_edition_idx)) ); aux_val = aux_val(1); % fprintf(1, '%d %d\n', point(1), aux_val); this_point = max(1, min(lanns_under_ed, aux_val )); end function WindowButtonDownCallback2D(obj,event_obj) if (strcmp(get(fig_hdl,'SelectionType'),'normal')) prev_u = get(fig_hdl, 'units'); set(fig_hdl, 'units','normalized'); crd = get(fig_hdl, 'CurrentPoint'); xp = crd(1); yp = crd(2); set(fig_hdl, 'units', prev_u); if( ~isempty([min_y_drag max_x_drag min_y_drag]) && xp <= max_x_drag && yp <= max_y_drag && yp >= min_y_drag) DragMouseBegin() end end end function WindowButtonUpCallback2D(obj,event_obj) DragMouseEnd() if ( fIsDragTimeAllowed ) fIsDragTimeAllowed = false; ECG_struct.signal = ECG_w.read_signal(start_idx, end_idx + 10 * ECG_struct.header.freq ); hb_idx = 1; Redraw(); set(fig_hdl, 'WindowButtonMotionFcn', PrevStateWindowButtonMotionFcn); end end function inspect_RRserie(obj,event_obj) if (strcmp(get(fig_hdl,'SelectionType'),'extend')) % Show ECG aux_RR = RRserie{1}; aux_RR = aux_RR(anns_under_edition_idx); point1 = get(gca,'CurrentPoint'); % button down detected rbbox; point2 = get(gca,'CurrentPoint'); % button up detected if( ~isempty(selected_hb_idx) ) delete(RRserie_selection_hdl) delete(RRscatter_selection_hdl) end [~, aux_val] = sort( abs(point1(1,1) - anns_under_edition(anns_under_edition_idx)) ); xlims = aux_val(1); [~, aux_val] = sort( abs(point2(1,1) - anns_under_edition(anns_under_edition_idx)) ); xlims = sort([xlims aux_val(1)]); ylims = sort([ point1(1,2) point2(1,2) ]); lanns_under_ed = length(anns_under_edition_idx); bAux = false(lanns_under_ed,1); bAux(max(1,xlims(1)):min(lanns_under_ed,xlims(2))) = true; selected_hb_idx = find( bAux & aux_RR > ylims(1) & aux_RR < ylims(2) ); if( isempty(selected_hb_idx) ) % probar en el rango de x solamente selected_hb_idx = find( bAux ); end if( ~isempty(selected_hb_idx) ) update_title_efimero([num2str(length(selected_hb_idx)) ' heartbeats selected.'], 5 ); hold(Scatter_axes_hdl, 'on') aux_RRnext = [aux_RR(2:end); aux_RR(end)]; RRscatter_selection_hdl = plot(Scatter_axes_hdl, aux_RR(selected_hb_idx), aux_RRnext(selected_hb_idx), 'xg' ); hold(Scatter_axes_hdl, 'off') hold(RRserie_axes_hdl, 'on') RRserie_selection_hdl = plot(RRserie_axes_hdl, anns_under_edition(anns_under_edition_idx(selected_hb_idx)) , aux_RR(selected_hb_idx), 'og' ); hold(RRserie_axes_hdl, 'off') min_hb_idx = min(selected_hb_idx); max_hb_idx = max(selected_hb_idx); cant_hb_idx = max_hb_idx - min_hb_idx + 1; if( (anns_under_edition(anns_under_edition_idx(max_hb_idx)) - anns_under_edition(anns_under_edition_idx(min_hb_idx))) <= (10*ECG_struct.header.freq) ) if( bSeries ) this_all_anns = all_annotations_selected_serie_location; aux_val = this_all_anns{1}; aux_val(serie_location_mask) = nan; this_all_anns{1} = aux_val; else this_all_anns = all_annotations_selected; end ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, anns_under_edition_idx(hb_idx) , hb_detail_window, ECG_struct.header, filtro, ECG_axes_hdl); cellfun(@(a)( set(a,'ButtonDownFcn',@inspect_ECG)), ECG_hdl); end if( length(lead_idx) > 1 ) aux_str = rowvec(colvec([repmat(',', length(lead_idx), 1) ECG_struct.header.desc(lead_idx,:) ]')); title(ECG_axes_hdl, ['Heartbeat ' num2str(hb_idx) ' : Leads ' aux_str(2:end) ] ) else title(ECG_axes_hdl, ['Heartbeat ' num2str(min_hb_idx) ' : Lead ' ECG_struct.header.desc(lead_idx,:)] ) end % zoom reset set(ECG_axes_hdl,'ButtonDownFcn',@inspect_ECG); % Zoom RR serie UpdateRRserieZoom(); end elseif (strcmp(get(fig_hdl,'SelectionType'),'normal')) % Show ECG bChangeWin = false; if( ishandle(RRscatter_hb_idx_hdl) ) delete(RRscatter_hb_idx_hdl) end if( ishandle(RRserie_hb_idx_hdl) ) delete(RRserie_hb_idx_hdl) end point = get(gca,'CurrentPoint'); lanns_under_ed = length(anns_under_edition); [~, aux_val] = sort( abs(point(1) - anns_under_edition) ); aux_val = aux_val(1); x_units = point(1); % fprintf(1, 'xunits\n'); hb_idx = get_current_point(); % x_units = hb_idx; hold(Scatter_axes_hdl, 'on') aux_RR = RRserie{1}; aux_RR = aux_RR(anns_under_edition_idx); RRnext = [aux_RR(2:end); aux_RR(end)]; % RRscatter_selection_hdl = plot(Scatter_axes_hdl, RRserie(selected_hb_idx), RRnext(selected_hb_idx), 'xg' ); RRscatter_hb_idx_hdl = plot(Scatter_axes_hdl, aux_RR(hb_idx), RRnext(hb_idx), 'or' ); hold(Scatter_axes_hdl, 'off') hold(RRserie_axes_hdl, 'on') RRserie_hb_idx_hdl = plot(RRserie_axes_hdl, anns_under_edition(anns_under_edition_idx(hb_idx)), aux_RR(hb_idx), 'or' ); hold(RRserie_axes_hdl, 'off') %Zoom RR serie UpdateRRserieZoom(); if( bSeries ) this_all_anns = all_annotations_selected_serie_location; aux_val = this_all_anns{1}; aux_val(serie_location_mask) = nan; this_all_anns{1} = aux_val; else this_all_anns = all_annotations_selected; end if( isempty(anns_under_edition_idx) ) ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, [] , hb_detail_window, ECG_struct.header, filtro, ECG_axes_hdl); else ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, anns_under_edition_idx(hb_idx) , hb_detail_window, ECG_struct.header, filtro, ECG_axes_hdl); end if( length(lead_idx) > 1 ) aux_str = rowvec(colvec([repmat(',', length(lead_idx), 1) ECG_struct.header.desc(lead_idx,:) ]')); title(ECG_axes_hdl, ['Heartbeat ' num2str(hb_idx) ' : Leads ' aux_str(2:end) ] ) else title(ECG_axes_hdl, ['Heartbeat ' num2str(hb_idx) ' : Lead ' ECG_struct.header.desc(lead_idx,:)] ) end % zoom reset cellfun(@(a)( set(a,'ButtonDownFcn',@inspect_ECG)), ECG_hdl); set(ECG_axes_hdl,'ButtonDownFcn',@inspect_ECG); elseif (strcmp(get(fig_hdl,'SelectionType'), 'alt')) point = get(gca,'CurrentPoint'); x_units = point(1); bChangeWin = true; DragMouseBegin() end end function SearchPattern() point1 = get(ECG_axes_hdl,'CurrentPoint'); % button down detected rbbox; point2 = get(ECG_axes_hdl,'CurrentPoint'); % button up detected xlims = round(sort([ point1(1,1) point2(1,1) ])); aux_seq = xlims(1):xlims(2); if( ishandle(Pattern_hdl) ) delete(Pattern_hdl) end update_title_efimero('Filtering ECG ...', 5 ); llead_idx = length(lead_idx); if( isempty(filtro) ) ECG = ECG_struct.signal(:,lead_idx); else ECG = filter(filtro, flipud(ECG_struct.signal(:,lead_idx)) ); ECG = filter(filtro, flipud(ECG) ); end hold(ECG_axes_hdl, 'on') Pattern_hdl = plot(ECG_axes_hdl, aux_seq, ECG(aux_seq,:), '.g' ); hold(ECG_axes_hdl, 'off') drawnow; pattern2detect = ECG_struct.signal(aux_seq,lead_idx); pattern2detect = bsxfun( @minus, pattern2detect, mean(pattern2detect)); % [nsamp_pattern nsig_pattern] = size(pattern2detect); % max_idx = max_index(pattern2detect); % cross_corr = 1/sqrt(var(ECG)*var(pattern2detect) )*conv(ECG, flipud(pattern2detect) , 'valid'); % pattern2detect_var = var(pattern2detect); update_title_efimero('Looking for the pattern ...', 5 ); % similarity = arrayfun( @(a)( 1/sqrt(var(ECG(a-nsamp_pattern+1:a, :))* pattern2detect_var)*(rowvec(ECG(a-nsamp_pattern+1:a, :)-mean(ECG(a-nsamp_pattern+1:a, :))) * colvec(pattern2detect) ) ), colvec(nsamp_pattern:ECG_struct.header.nsamp)); % similarity = arrayfun( @(a)( 1/sqrt(var(ECG(a-nsamp_pattern+1:a))* pattern2detect_var)*(rowvec(ECG(a-nsamp_pattern+1:a)-mean(ECG(a-nsamp_pattern+1:a))) * colvec(pattern2detect) ) ), colvec(nsamp_pattern:ECG_struct.header.nsamp)); % similarity = [repmat(similarity(1,:),nsamp_pattern-1,1); similarity]; if( isempty(ECG_w) ) % short or easy memory handlable signals similarity = cellfun( @(a,b)( diff(conv( a, b, 'same' )) ), mat2cell(ECG_struct.signal(:,lead_idx), ECG_struct.header.nsamp, ones(1,llead_idx)), mat2cell(pattern2detect, diff(xlims)+1, ones(1,llead_idx)), 'UniformOutput', false); similarity = cell2mat(cellfun( @(a,b)( diff(conv( a, b, 'same' )) ), similarity, mat2cell(flipud(pattern2detect), diff(xlims)+1, ones(1,llead_idx)), 'UniformOutput', false)); % similarity = [repmat(similarity(1,:),round((nsamp_pattern-1)/2),1); similarity]; similarity = abs(mean(similarity,2)); else aux_w = ECGwrapper('recording_name', ECG_w.recording_name); aux_w.ECGtaskHandle = 'arbitrary_function'; aux_w.cacheResults = false; aux_w.ECGtaskHandle.lead_idx = lead_idx; aux_w.ECGtaskHandle.signal_payload = true; aux_w.user_string = ['similarity_calc_for_lead_' num2str(sort(lead_idx)) ]; aux_w.ECGtaskHandle.function_pointer = @similarity_calculation; aux_w.ECGtaskHandle.payload = pattern2detect; aux_w.Run end prev_fig = gcf(); fig2_hdl = figure(2); clf(); set(fig2_hdl, 'Position', [ maximized_size(3:4) maximized_size(3:4) ] .* [ 0.05 0.13 0.95 0.9] ); win_sample = 3*ECG_struct.header.freq; break_sample = round(1*ECG_struct.header.freq); n_excerpts = 5; aux_idx = 1:win_sample; aux_windows = linspace(0, ECG_struct.header.nsamp-win_sample, n_excerpts); sig_breaks = nan(break_sample, llead_idx + 1 ); if( isempty(ECG_w) ) aux_val = sig_breaks; for aux_start = (aux_windows+1) % aux_val1 = [bsxfun( @minus, ECG(aux_start:(aux_start+win_sample),:), mean(ECG(aux_idx))) similarity(aux_start:(aux_start+win_sample))-mean(similarity(aux_start:(aux_start+win_sample))) ]; aux_val1 = [bsxfun( @minus, ECG(aux_start:(aux_start+win_sample),:), mean(ECG(aux_idx))) similarity(aux_start:(aux_start+win_sample)) ]; aux_val = [aux_val; aux_val1; sig_breaks ]; end else aux_w = ECGwrapper('recording_name', char(aux_w.Result_files)); aux_val = sig_breaks; for aux_start = (aux_windows+1) aux_val1 = [ECG_w.read_signal( aux_start, aux_start + win_sample ) aux_w.read_signal( aux_start, aux_start + win_sample ) ]; aux_val1 = [aux_val1(:, lead_idx) aux_w.read_signal( aux_start, aux_start + win_sample ) ]; aux_val = [aux_val; [bsxfun(@minus, aux_val1(:,1:end-1), mean(aux_val1(:,1:end-1)) ) aux_val1(:,end)]; sig_breaks ]; end end aux_thr_scale = max(abs(aux_val)); aux_val = bsxfun(@times, aux_val, 1./aux_thr_scale); aux_val(:,1:llead_idx) = (aux_val(:,1:llead_idx)*0.5) - 0.5; aux_hdls = plot(aux_val); axes_hdl = gca(); set(axes_hdl, 'Position', [ 0.015 0.025 0.97 0.92] ); title('Select the detection threshold to use in the similarity function') detection_threshold = 0.3; % seconds dt_samples = round(detection_threshold*ECG_struct.header.freq); xlims = get(axes_hdl, 'xlim'); ylims = get(axes_hdl, 'ylim'); dt_yloc = ylims(1) + 0.05*diff(ylims); dt_xloc = xlims(1) + 0.1*diff(xlims); hold(axes_hdl, 'on'); arrow( [dt_xloc; dt_yloc], [dt_xloc + dt_samples; dt_yloc], 2, 0.5, [0 0 0], axes_hdl ) text( dt_xloc, dt_yloc+0.01*diff(ylims), ['Min QRS sep ' Seconds2HMS(detection_threshold,2) ]) hold(axes_hdl, 'off'); set(axes_hdl, 'Ytick', []); aux_val = sort([ break_sample+(0:(win_sample+break_sample):(n_excerpts-1)*(win_sample+break_sample)) break_sample+win_sample+(0:(win_sample+break_sample):(n_excerpts-1)*(win_sample+break_sample)) length(aux_val) ]); set(axes_hdl, 'Xtick', aux_val ); aux_val = sort([ aux_windows (aux_windows + win_sample) ECG_struct.header.nsamp]); set(axes_hdl, 'XtickLabel', Seconds2HMS( aux_val ./ ECG_struct.header.freq )); legend(aux_hdls, {'ECG'; 'Similarity'} ); update_title_efimero('Select the threshold to use.', 5 ); [~, thr] = ginput(1); thr = thr * aux_thr_scale(2); QRSxlims = 1; bContinue = true; while(bContinue) if( isempty(ECG_w) ) % short or easy memory handlable signals ECG_struct.pattern_match.time = modmax(similarity, QRSxlims, thr, 1, round(detection_threshold*ECG_struct.header.freq) ); else aux_w.ECGtaskHandle = 'arbitrary_function'; aux_w.cacheResults = false; aux_w.ECGtaskHandle.lead_idx = lead_idx; % generate QRS detections aux_w.ECGtaskHandle.signal_payload = false; aux_w.user_string = ['modmax_calc_for_leads_' num2str(sort(lead_idx)) ]; aux_w.ECGtaskHandle.function_pointer = @(a)(modmax(a,QRSxlims, thr, 1, round(detection_threshold*ECG_struct.header.freq))); aux_w.Run % asume that the whole series keep in mem. aux_val = load(aux_w.Result_files{1}); ECG_struct.pattern_match.time = aux_val.result; end % aux_idx = 1; % while( ~isempty(aux_idx) ) % aux_idx = find(diff(ECG_struct.pattern_match.time) < round(0.15 * ECG_struct.header.freq)); % aux_idx2 = setdiff(1:length(ECG_struct.pattern_match.time), [colvec(aux_idx); colvec(aux_idx+1)]); % [~, merged_times] = max([similarity(ECG_struct.pattern_match.time(aux_idx)) similarity(ECG_struct.pattern_match.time(aux_idx+1))],[],2); % ECG_struct.pattern_match.time = sort([colvec(ECG_struct.pattern_match.time(aux_idx2)); colvec(ECG_struct.pattern_match.time(aux_idx( find(merged_times == 1) ))); colvec(ECG_struct.pattern_match.time(aux_idx( find(merged_times == 2) )+1 )) ]); % end if( strcmp(AnnNames(end,1), cellstr('pattern_match') ) ) aux_all_anns = all_annotations; aux_all_anns{end} = ECG_struct.pattern_match.time; else AnnNames = [AnnNames; cellstr('pattern_match') cellstr('time')]; aux_all_anns = [all_annotations; {ECG_struct.pattern_match.time}]; end if( isempty(ECG_w) ) % only for short signals [ ratios, estimated_labs ] = CalcRRserieRatio(aux_all_anns, ECG_struct.header); else % ignore ratios and q measurements in long recordings. ratios = zeros(size(AnnNames,1),1); estimated_labs = cell(size(AnnNames,1),1); end all_annotations = aux_all_anns; AnnNames_idx = size(AnnNames,1); anns_under_edition = unique(round(colvec( ECG_struct.pattern_match.time ))); hb_idx = 1; selected_hb_idx = []; undo_buffer_idx = 1; aux_val = anns_under_edition; bAnnsEdited = false; if( isempty(aux_val) ) anns_under_edition = []; RRserie = {[]}; all_annotations_selected_serie_location = []; serie_location_mask = []; else anns_under_edition = unique(round(colvec( aux_val ))); RRserie = colvec(diff(anns_under_edition)); RRserie = {[RRserie(1); RRserie] * 1/ECG_struct.header.freq}; end all_annotations_selected = {anns_under_edition}; RR_idx = { find( anns_under_edition >= start_idx & anns_under_edition <= end_idx ) }; bSeriesChange = true; figure(prev_fig); Redraw(); figure(2); ocurrences = length(ECG_struct.pattern_match.time); update_title_efimero(['Threshold: ' num2str(thr) ' - found ' num2str(ocurrences) ' heartbeats with quality ' num2str(ratios(end)) ], 5 ); key = input(['[rt] to refine the QRS detection threshold.\n' ... '[rx] to refine the time window to perform QRS detection.\n' ... '[rh] to refine the minimum time between heartbeats.\n' ... 'any key to continue.\n' ... ], 's'); if( strcmp(key, 'rt') ) figure(2) update_title_efimero('Select the threshold to use.', 5 ); [~, thr] = ginput(1); thr = thr * aux_thr_scale(2); elseif( strcmp(key, 'rh') ) key = input( 'Enter the minimum time between heartbeats:\n' , 's'); detection_threshold = str2double(key); elseif( strcmp(key, 'rx') ) fig_hdl = figure(1); update_title_efimero('Click and drag the time interval to perform QRS detection in the RR series interval ...', 5 ); set(fig_hdl, 'CurrentAxes', RRserie_axes_hdl); waitforbuttonpress; point1 = get(RRserie_axes_hdl,'CurrentPoint'); % button down detected rbbox; point2 = get(RRserie_axes_hdl,'CurrentPoint'); % button up detected QRSxlims = round(sort([ point1(1,1) point2(1,1) ])); QRSxlims = [ QRSxlims(1) QRSxlims(1) + max(diff(QRSxlims), 10 * ECG_struct.header.freq )]; else bContinue = false; end end update_title_efimero('Search pattern finished.', 5 ); cant_anns = size(AnnNames,1); aux_str = repmat( ' - ',cant_anns,1); [~, best_detections_idx] = sort(ratios, 'descend'); aux_val = 1:length(ratios); [~, annotations_ranking] = sort(aux_val(best_detections_idx)); set(annotation_list_control, 'string', [ char(cellstr(num2str((1:cant_anns)'))) aux_str char(AnnNames(:,1)) repmat( ' (',cant_anns,1) num2str(round(colvec(ratios * 1000))) aux_str num2str(colvec(annotations_ranking)) repmat( ')',cant_anns,1) ] ); set(annotation_under_edition_label, 'string', [ 'Annotation under edition: ' char(AnnNames( AnnNames_idx ,1)) ' (' num2str(ratios(AnnNames_idx)) ')' ]) set(annotation_list_control, 'Value', AnnNames_idx); close(2) end function UpdateRRserieZoom() if( isempty(hb_idx) ) return end bRRempty = all(cellfun( @(a)(isempty(a)), RRserie)); if( bRRempty ) cla(RRserie_zoom_axes_hdl, 'reset'); else aux_RR = RRserie{1}; aux_idx = find( anns_under_edition(anns_under_edition_idx) >= (anns_under_edition(anns_under_edition_idx(hb_idx)) - round(win_size_zoom/2*ECG_struct.header.freq)) & anns_under_edition(anns_under_edition_idx) <= (anns_under_edition(anns_under_edition_idx(hb_idx)) + round(win_size_zoom/2*ECG_struct.header.freq)) ); if(length(aux_idx) < 3 ) min_idx = find(~isnan(anns_under_edition(anns_under_edition_idx)),1); max_idx = find(~isnan(anns_under_edition(anns_under_edition_idx)),1, 'last'); aux_idx = max(min_idx, hb_idx - 1 ); aux_idx = aux_idx:min( max_idx, max(hb_idx, aux_idx) + 1 ); else hb_detail_window = round( length(aux_idx) / 2 ); end aux_idx2 = cellfun( @(this_anns)( find( this_anns >= anns_under_edition(anns_under_edition_idx(aux_idx(1))) & this_anns <= anns_under_edition(anns_under_edition_idx(aux_idx(end))) ) ), all_annotations_selected, 'UniformOutput', false); aux_idx3 = find(~all(cellfun( @(a)(isempty(a)), aux_idx2))); cla(RRserie_zoom_axes_hdl, 'reset'); if( ~isempty(aux_idx3) ) % RRserie2 = cellfun( @(this_anns)( colvec(diff(this_anns)) ), all_annotations_selected, 'UniformOutput', false); % RRserie2 = cellfun( @(this_rr_serie)( [this_rr_serie(1); this_rr_serie] ), RRserie, 'UniformOutput', false); hold(RRserie_zoom_axes_hdl, 'on') RRserie_zoom_hdl = cellfun( @(this_anns, this_rr_serie, this_idx, ii)( plot(RRserie_zoom_axes_hdl, this_anns(this_idx) , this_rr_serie(this_idx), 'LineStyle', ':', 'Marker', all_markers{ii}, 'MarkerEdgeColor', ColorOrder(ii,:), 'Color', ColorOrder(ii,:) ) ), all_annotations_selected(aux_idx3), RRserie(aux_idx3), aux_idx2(aux_idx3), num2cell(colvec(aux_idx3)), 'UniformOutput', false ); this_ylims = get(RRserie_axes_hdl, 'Ylim' ); this_xlims_orig = get(RRserie_zoom_axes_hdl, 'Xlim' ); set(RRserie_zoom_axes_hdl, 'Ylim', this_ylims ); % blue box around this_xlims = this_xlims_orig + 0.01*[ diff(this_xlims_orig) -diff(this_xlims_orig) ]; this_ylims = this_ylims + 0.015*[ diff(this_ylims) -diff(this_ylims) ]; set(fig_hdl,'CurrentAxes', RRserie_zoom_axes_hdl); aux_hdl = patch([this_xlims(1) this_xlims(1) this_xlims(2) this_xlims(2) this_xlims(1) ], [this_ylims(1) this_ylims(2) this_ylims(2) this_ylims(1) this_ylims(1)], [1 1 1], 'EdgeColor', [0 0 1], 'LineWidth', 1.5, 'ButtonDownFcn', @inspect_RRserie ); set(aux_hdl, 'FaceColor', 'none') uistack(aux_hdl, 'bottom'); if( isempty(RRserie_zoom_zoombars_hdl) || ~ishandle(RRserie_zoom_zoombars_hdl) ) set(fig_hdl, 'CurrentAxes', RRserie_axes_hdl); RRserie_zoom_zoombars_hdl = patch([this_xlims(1) this_xlims(1) this_xlims(2) this_xlims(2) this_xlims(1) ], [this_ylims(1) this_ylims(2) this_ylims(2) this_ylims(1) this_ylims(1)], [190 238 238]/255, 'EdgeColor', [0 0 1], 'LineWidth', 0.5, 'ButtonDownFcn', @inspect_RRserie); uistack(RRserie_zoom_zoombars_hdl, 'bottom'); else set(RRserie_zoom_zoombars_hdl, 'Xdata', [this_xlims(1) this_xlims(1) this_xlims(2) this_xlims(2) this_xlims(1) ]); end if( ~isempty(aux_RR) ) [~, aux_idx2] = intersect(selected_hb_idx, aux_idx); RRserie_zoom_hdl = [RRserie_zoom_hdl; colvec(arrayfun(@(a,b)( plot(RRserie_zoom_axes_hdl, a, b, 'og')), anns_under_edition(anns_under_edition_idx(selected_hb_idx(aux_idx2))), aux_RR(anns_under_edition_idx(selected_hb_idx(aux_idx2))), 'UniformOutput', false ) ) ]; end if( ~isempty(aux_RR) && hb_idx < length(aux_RR) ) RRserie_zoom_hdl = [RRserie_zoom_hdl; colvec(arrayfun(@(a,b)( plot(RRserie_zoom_axes_hdl, a, b, 'or')), anns_under_edition(anns_under_edition_idx(hb_idx)), aux_RR(anns_under_edition_idx(hb_idx)), 'UniformOutput', false ) )]; end set(RRserie_zoom_axes_hdl, 'Xlim', this_xlims_orig ); hold(RRserie_zoom_axes_hdl, 'off'); xlabel(RRserie_zoom_axes_hdl, 'Time'); ylabel(RRserie_zoom_axes_hdl, 'Serie value'); set(RRserie_zoom_axes_hdl,'ButtonDownFcn',@inspect_RRserie); cellfun( @(a)(set(a, 'ButtonDownFcn', @inspect_RRserie ) ), RRserie_zoom_hdl ); aux_hb_idx = find(hb_idx == aux_idx); if( length(aux_idx) > 5 ) cant_ticks = 5; if( isempty(aux_hb_idx) ) aux_idx = round(linspace(aux_idx(1), aux_idx(end), cant_ticks)); else cant_ticks = cant_ticks - 1; aux_idx = sort(unique([ round(linspace(aux_idx(1), aux_idx(end), cant_ticks)) aux_idx(aux_hb_idx) ])); aux_hb_idx = find(hb_idx == aux_idx); end end set(RRserie_zoom_axes_hdl, 'XTick', rowvec(anns_under_edition(anns_under_edition_idx(aux_idx))) ); aux_str = cellstr(num2str(colvec(anns_under_edition_idx(aux_idx)))); if( ~isempty(anns_under_edition) && ~isnan(anns_under_edition(anns_under_edition_idx(hb_idx))) && hb_idx <= length(anns_under_edition_idx) ) aux_str{aux_hb_idx} = [aux_str{aux_hb_idx} ' (' Seconds2HMS((anns_under_edition(anns_under_edition_idx(hb_idx)) + base_start_time - 1)*1/ECG_struct.header.freq) ')']; set(RRserie_zoom_axes_hdl, 'XTickLabel', char(aux_str) ); end end end end function DeleteAnnotations(obj,event_obj) cant_anns = size(AnnNames,1); if( cant_anns == 0 ) return; end if( strcmpi(questdlg('Are you sure ?', 'Delete annotations', 'No'), 'yes') ) ann_idx = get(annotation_list_control, 'Value'); ECG_struct = rmfield(ECG_struct, AnnNames{ann_idx,1}); if( cant_anns == 1) % last annotation deleted ECG_struct.Default.time = []; AnnNames = {'Default' 'time'}; set(annotation_list_control, 'string', '1 - Default' ); set(annotation_under_edition_label, 'string', 'Annotation under edition: Default' ) AnnNames_idx = 1; anns_under_edition = []; elseif( cant_anns > 1) aux_idx = 1:cant_anns; aux_idx(ann_idx) = []; AnnNames = AnnNames(aux_idx,:); ratios = ratios(aux_idx); annotations_ranking = annotations_ranking(aux_idx); cant_anns = size(AnnNames,1); aux_str = repmat( ' - ',cant_anns,1); set(annotation_list_control, 'string', [ char(cellstr(num2str((1:cant_anns)'))) aux_str char(AnnNames(:,1)) repmat( ' (',cant_anns,1) num2str(round(colvec(ratios * 1000))) aux_str num2str(colvec(annotations_ranking)) repmat( ')',cant_anns,1) ] ); set(annotation_under_edition_label, 'string', [ 'Annotation under edition: ' char(AnnNames( AnnNames_idx ,1)) ' (' num2str(ratios(AnnNames_idx)) ')' ]) AnnNames_idx = 1; set(annotation_list_control, 'Value', AnnNames_idx); anns_under_edition = unique(round(colvec( ECG_struct.(AnnNames{AnnNames_idx,1}).(AnnNames{AnnNames_idx,2}) ))); end if( isfield(ECG_struct, 'series_quality' ) ) ECG_struct.series_quality.AnnNames = AnnNames; ECG_struct.series_quality.ratios = ratios; end undo_buffer_idx = 1; bRecEdited = true; bAnnsEdited = false; selected_hb_idx = []; Redraw(); end end function ChangeRecordingSelected(obj,event_obj) if (strcmp(get(fig_hdl,'SelectionType'),'open')) %Double click else %Single click rec_selected = get(obj, 'Value'); if( rec_selected ~= rec_idx ) % change of annotation if(bRecEdited) update_annotations(); if( bLoadECG ) update_title_efimero('Saving data ...', 5 ); save(rec_path, '-struct', 'ECG_struct'); update_title_efimero(['Saved ' rec_path], 5 ); else if( isfield(ECG_struct, 'series_quality' ) ) anns_struct.series_quality = ECG_struct.series_quality; end for ii = 1:size(AnnNames,1) anns_struct.(AnnNames{ii,1}) = ECG_struct.(AnnNames{ii,1}); end assignin( 'caller', OutputVarName, anns_struct ); update_title_efimero(sprintf('Saving ''%s'' variable in caller workspace', OutputVarName), 5 ); bAnnsEdited = false; bRecEdited = false; end end rec_idx = get(recordings_control, 'Value' ); DoRecording(); end end end function ChangeLeadsSelected(obj,event_obj) if (strcmp(get(fig_hdl,'SelectionType'),'open')) %Double click else %Single click leads_selected = get(obj, 'Value'); if(length(leads_selected ) == 1 && ( length(lead_idx) ~= length(leads_selected) || leads_selected ~= lead_idx) ) % change of annotation standarize_ECG_view = false; lead_idx = leads_selected; cla(ECG_axes_hdl); if( bSeries ) this_all_anns = all_annotations_selected_serie_location; aux_val = this_all_anns{1}; aux_val(serie_location_mask) = nan; this_all_anns{1} = aux_val; else this_all_anns = all_annotations_selected; end if( isempty(anns_under_edition_idx) ) ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, [] , hb_detail_window, ECG_struct.header, filtro, ECG_axes_hdl); else ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, anns_under_edition_idx(hb_idx) , hb_detail_window , ECG_struct.header, filtro, ECG_axes_hdl); end title(ECG_axes_hdl, ['Heartbeat ' num2str(hb_idx) ' : Lead ' ECG_struct.header.desc(lead_idx,:)] ) cellfun(@(a)( set(a,'ButtonDownFcn',@inspect_ECG)), ECG_hdl); set(ECG_axes_hdl,'ButtonDownFcn',@inspect_ECG); elseif(length(leads_selected ) > 1) lead_idx = leads_selected; cla(ECG_axes_hdl); if( bSeries ) this_all_anns = all_annotations_selected_serie_location; aux_val = this_all_anns{1}; aux_val(serie_location_mask) = nan; this_all_anns{1} = aux_val; else this_all_anns = all_annotations_selected; end if( isempty(anns_under_edition_idx) ) ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, [] , hb_detail_window, ECG_struct.header, filtro, ECG_axes_hdl); else ECG_hdl = plot_ecg_heartbeat(ECG_struct.signal, lead_idx, this_all_anns, start_idx, anns_under_edition_idx(hb_idx) , hb_detail_window , ECG_struct.header, filtro, ECG_axes_hdl); end aux_str = rowvec(colvec([repmat(',', length(lead_idx), 1) ECG_struct.header.desc(lead_idx,:) ]')); title(ECG_axes_hdl, ['Heartbeat ' num2str(hb_idx) ' : Leads ' aux_str(2:end) ] ) cellfun(@(a)( set(a,'ButtonDownFcn',@inspect_ECG)), ECG_hdl); set(ECG_axes_hdl,'ButtonDownFcn',@inspect_ECG); end end end function update_annotations() corrected_prefix = 'corrected_'; if( bSeries ) aux_val = all_annotations_selected_serie_location{1}; aux_val(serie_location_mask) = nan; aux_val = ( aux_val - anns_under_edition) / ECG_struct.header.freq; aux_val = [anns_under_edition aux_val]; else aux_val = unique(round(colvec( anns_under_edition ))); end if( isempty(strfind(AnnNames{AnnNames_idx,1}, corrected_prefix )) ) % modifying an original annotation, duplicate annotation ii = 1; while( isfield(ECG_struct, [ corrected_prefix AnnNames{AnnNames_idx,1}]) ) corrected_prefix = [corrected_prefix 'v' num2str(ii) '_' ]; ii = ii+1; end ECG_struct.([ corrected_prefix AnnNames{AnnNames_idx,1}]).(AnnNames{AnnNames_idx,2}) = aux_val; % ECG_struct.([ corrected_prefix AnnNames{AnnNames_idx,1}]) = ECG_struct.(AnnNames{AnnNames_idx,1}); % ECG_struct = rmfield(ECG_struct, AnnNames{AnnNames_idx,1}); % add it to the ann list % AnnNames( AnnNames_idx , : ) = { [ corrected_prefix AnnNames{AnnNames_idx,1}] AnnNames{AnnNames_idx,2} }; AnnNames = [{ [ corrected_prefix AnnNames{AnnNames_idx,1}] AnnNames{AnnNames_idx,2} }; AnnNames]; ratios = [ ratios(AnnNames_idx); ratios ]; AnnNames_idx = 1; annotations_ranking = [ 1; colvec(annotations_ranking+1) ]; cant_anns = size(AnnNames,1); aux_str = repmat( ' - ',cant_anns,1); set(annotation_list_control, 'string', [ char(cellstr(num2str((1:cant_anns)'))) aux_str char(AnnNames(:,1)) repmat( ' (',cant_anns,1) num2str(round(colvec(ratios * 1000))) aux_str num2str(colvec(annotations_ranking)) repmat( ')',cant_anns,1) ] ); if( isfield(ECG_struct, 'series_quality' ) ) ECG_struct.series_quality.AnnNames = AnnNames; ECG_struct.series_quality.ratios = ratios; end else % updating an already corrected ann. ECG_struct.(AnnNames{AnnNames_idx,1}).(AnnNames{AnnNames_idx,2}) = aux_val; end end function bAux = IsClicked(this_hdl, this_xy ) dfltUnits = get(this_hdl, 'Units'); set(this_hdl, 'Units', 'pixels'); aux_pos = get( this_hdl, 'Position'); bAux = this_xy(1) >= aux_pos(1) & this_xy(1) <= (aux_pos(1) + aux_pos(3) ) & this_xy(2) >= aux_pos(2) & this_xy(2) <= (aux_pos(2) + aux_pos(4) ); set(this_hdl, 'Units', dfltUnits); end function TimeOffsetClick(obj,event_obj) if ( ~fIsDragTimeAllowed ) [drag_timeScroll_start_x, drag_timeScroll_start_y ] = GetCursorCoordOnWindow(); if( IsClicked(RRserie_global_axes_hdl, [drag_timeScroll_start_x, drag_timeScroll_start_y ]) ) point = get(RRserie_global_axes_hdl,'CurrentPoint'); elseif( IsClicked(RRserie_axes_hdl, [drag_timeScroll_start_x, drag_timeScroll_start_y ]) ) point = get(RRserie_axes_hdl,'CurrentPoint'); else return end x_timeScroll_units = point(1); if (strcmp(get(fig_hdl,'SelectionType'),'alt')) bChangeWin = true; else bChangeWin = false; end UpdateStartX( 0 ); fIsDragTimeAllowed = true; PrevStateWindowButtonMotionFcn = get(fig_hdl, 'WindowButtonMotionFcn'); set(fig_hdl, 'WindowButtonMotionFcn', @WindowButtonMotionCallback2D); end end function ChangeAnnotationsSelected(obj,event_obj) if (strcmp(get(fig_hdl,'SelectionType'),'open')) %Double click answer = char(inputdlg([ 'Enter the new name of the annotation ' char(AnnNames( AnnNames_idx ,1)) ], 'Change annotation name', 1, AnnNames( AnnNames_idx ,1)) ); if( ~isempty(answer) && ischar(answer) ) ann_idx = get(annotation_list_control, 'Value'); ECG_struct.(answer) = ECG_struct.(AnnNames{ann_idx,1}); ECG_struct = rmfield(ECG_struct, AnnNames{ann_idx,1}); AnnNames{ ann_idx ,1} = answer; if( isfield(ECG_struct, 'series_quality' ) ) ECG_struct.series_quality.AnnNames = AnnNames; end cant_anns = size(AnnNames,1); aux_str = repmat( ' - ',cant_anns,1); if( isempty(ratios) ) set(annotation_list_control, 'string', [ char(cellstr(num2str((1:cant_anns)'))) aux_str char(AnnNames(:,1)) ] ); set(annotation_under_edition_label, 'string', [ 'Annotation under edition: ' char(AnnNames( AnnNames_idx ,1)) ]) else set(annotation_list_control, 'string', [ char(cellstr(num2str((1:cant_anns)'))) aux_str char(AnnNames(:,1)) repmat( ' (',cant_anns,1) num2str(round(colvec(ratios * 1000))) aux_str num2str(colvec(annotations_ranking)) repmat( ')',cant_anns,1) ] ); set(annotation_under_edition_label, 'string', [ 'Annotation under edition: ' char(AnnNames( AnnNames_idx ,1)) ' (' num2str(ratios(AnnNames_idx)) ')' ]) end bAnnsEdited = true; bRecEdited = true; end else %Single click anns_selected = get(obj, 'Value'); if(length(anns_selected ) == 1 && anns_selected ~= AnnNames_idx ) % change of annotation if(bAnnsEdited) update_annotations(); end AnnNames_idx = anns_selected; % disp( ['Using ' AnnNames{AnnNames_idx,1} ' annotations (' num2str(ratios(AnnNames_idx)) ')'] ); set(annotation_under_edition_label, 'string', [ 'Annotation under edition: ' char(AnnNames( AnnNames_idx ,1)) ' (' num2str(ratios(AnnNames_idx)) ')' ]) undo_buffer_idx = 1; aux_val = ECG_struct.(AnnNames{AnnNames_idx,1}).(AnnNames{AnnNames_idx,2}); bAnnsEdited = false; selected_hb_idx = []; if( isempty(aux_val) ) anns_under_edition = []; RRserie = {[]}; all_annotations_selected_serie_location = []; serie_location_mask = []; else if( bSeries ) [anns_under_edition, aux_idx ]= unique(round(colvec( aux_val(:,1) ))); RRserie = { aux_val(aux_idx,2) }; % absolute position all_annotations_selected_serie_location = {anns_under_edition + round( aux_val(aux_idx,2) * ECG_struct.header.freq) }; serie_location_mask = false(size(anns_under_edition)); else anns_under_edition = unique(round(colvec( aux_val ))); RRserie = colvec(diff(anns_under_edition)); RRserie = {[RRserie(1); RRserie] * 1/ECG_struct.header.freq}; end end all_annotations_selected = {anns_under_edition}; RR_idx = { find( anns_under_edition >= start_idx & anns_under_edition <= end_idx ) }; bSeriesChange = true; Redraw(); elseif(length(anns_selected ) > 1) aux_anns = {anns_under_edition}; if( bSeries ) aux_val = ECG_struct.(AnnNames{AnnNames_idx,1}).(AnnNames{AnnNames_idx,2}); aux_anns2 = { aux_val(:,2) }; end anns_selected( anns_selected == AnnNames_idx) = []; for ii = rowvec(anns_selected) aux_val = ECG_struct.(AnnNames{ii,1}).(AnnNames{ii,2}); aux_anns = [aux_anns; ... {aux_val(:,1)} ]; if( bSeries ) aux_anns2 = [aux_anns2; ... {aux_val(:,2)} ]; end end if( bSeries ) all_annotations_selected_serie_location = cellfun( @(a,b)(a + round( b * ECG_struct.header.freq) ), aux_anns, aux_anns2, 'UniformOutput', false); end all_annotations_selected = aux_anns; RR_idx = cellfun( @(this_anns)( find( this_anns >= start_idx & this_anns <= end_idx ) ), all_annotations_selected, 'UniformOutput', false); if( bSeries ) RRserie = aux_anns2; else RRserie = cellfun( @(this_anns)( colvec(diff(this_anns)) ), all_annotations_selected, 'UniformOutput', false); RRserie = cellfun( @(this_rr_serie)( [this_rr_serie(1); this_rr_serie] * 1/ECG_struct.header.freq ), RRserie, 'UniformOutput', false); end bSeriesChange = true; Redraw(); end end end function update_title_efimero( strTitle, delay ) if( ishandle(title_efimero_hdl) ) set(title_efimero_hdl, 'String',strTitle ) set(title_efimero_hdl, 'Visible', 'on'); else title_efimero_hdl = annotation( 'textbox', [0 0.98 0.3 0.01 ], ... 'String', strTitle, ... 'Tag', 'title_efimero', ... 'FontSize', 8, ... 'Interpreter', 'none', ... 'FitBoxToText', 'on', ... 'HorizontalAlignment', 'left', ... 'EdgeColor', 'none', ... 'BackgroundColor', 'r' ); end if( ~isinf(delay) && strcmpi(my_timer.Running, 'off') ) my_timer.StartDelay = delay; start(my_timer) end end function timer_fcn(obj,event_obj) set(title_efimero_hdl, 'Visible', 'off'); % if(~bPreserveFix) % % allow edition of the closer wave % bFixedWave = false; % end end end