ECG-Kit 1.0
(115,073 bytes)
%% (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<MCHLP>
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