ECG-Kit 1.0
(47,046 bytes)
%% Plots multidimentional signal in mosaic style
% This function plots several subplots in the same figure in order to do a
% mosaic with the different leads available in ECG. Annotations can be
% provided to each or for all the mosaic.
%
% Prototype
%
% [ ECG_hdl axes_hdl fig_hdl all_yranges ] = plot_ecg_mosaic( ECG, varargin )
%
% Arguments:
%
% +ECG: [numeric or cell] REQUIRED
%
% [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)
%
% +WinSize: [numeric] OPTIONAL. Default values enclosed in ()
% Width of the window around each fiducial point provided in
% QRS_locations. (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)) )
%
% +MaxECGrange: [numeric or string] OPTIONAL. Force a vertial range in order to
% ease visual comparison of signals in the mosaic.
% [string]
% 'max': force the maximum range to be the range
% for all mosaics.
% 'min', 'mean', 'median': are also available options.
% 'none': Each mosaic with a different range. (Default).
%
% +RowsCols: [numeric] OPTIONAL. Number of rows and columns of the
% mosaic. If ommited or if rows * cols ~= ECG_header.nsig, these values are
% automatically adapted to the best fit mosaic in relation to the
% aspect ratio of the screen.
%
% +FigureHdl: [figure handle] OPTIONAL. Choose the figure to be produced
% the mosaic. (handle produced by gcf)
%
% +ECG_delineation: [struct] OPTIONAL. Default values enclosed in ()
% Annotation struct generated with wavedet.
%
% +ECG_annotations: [cell] OPTIONAL. Default values enclosed in ()
% Annotations to be included in the mosaic. The funcion
% accepts 2 type of annotations: points and lines.
%
% Output:
%
% + ECG_hdl: handle to the plotted signals.
%
% + axes_hdl: handle to the axes.
%
% + fig_hdl: handle to fig.
%
% + all_yranges: vertical ranges of the plotted signals.
%
%
% Example:
%
% This example makes a synchronized plot of several random events (+1 0 -1)
% ocurryng randomly with additive white gaussian noise.
% win_size = 100;
% sig_samp = 10000;
% sig_size = 12;
% event_size = 50;
% x = 0.1*randn(sig_samp,sig_size);
% event_locations = randsample(win_size:sig_samp-win_size, event_size);
% x(event_locations-1,:) = x(event_locations-1,:) + 1;
% x(event_locations+1,:) = x(event_locations+1,:) - 1;
% x_packed = pack_signal(x, event_locations, win_size);
%
% figure(1)
% % estimation of the signal averaged event
% plot_ecg_mosaic( mean(x_packed,3) );
% figure(2)
% % visualization of all events. In this case previous pack_signal call is
% % not needed.
% plot_ecg_mosaic(x, 'QRS_locations', event_locations, 'WinSize', win_size);
%
% figure(3)
% % introducing several kind of marks to the plot
%
% h_line = cell(sig_size,7);
% h2_line = cell(sig_size,7);
% v_line = cell(sig_size,7);
% v2_line = cell(sig_size,7);
% point = cell(sig_size,7);
% a_line = cell(sig_size,7);
%
% h_line(:,1) = {'line'};
% h_line(:,2) = { [ { 'String' 'LineStyle' 'LineWidth' 'Color' 'TextColor' }; ...
% { 'horizontal line text' '--' 1.5 'r' 'r' } ]'};
% h_line(1:sig_size, [6 7]) = num2cell( repmat(-0.5,sig_size,2) );
%
% h2_line(:,1) = {'line'};
% h2_line(:,2) = { [ { 'String' 'LineStyle' 'LineWidth' 'Color' 'TextColor' }; ...
% { 'other h-line' '--' 1.5 'm' 'm' } ]'};
% h2_line(1:sig_size, 4:7) = num2cell( [ repmat(60,sig_size,1) repmat(70,sig_size,1) repmat(0.5,sig_size,2) ] );
%
% v_line(:,1) = {'line'};
% v_line(:,2) = { [ { 'String' 'LineStyle' 'LineWidth' 'Color' 'TextColor' }; ...
% { 'vertical line text' '--' 1.5 'g' 'g' } ]'};
% v_line(1:sig_size, [4 5]) = num2cell( repmat(20,sig_size,2) );
%
% v2_line(:,1) = {'line'};
% v2_line(:,2) = { [ { 'String' 'LineStyle' 'LineWidth' 'Color' 'TextColor' }; ...
% { 'other v-line' '--' 1.5 'b' 'b' } ]'};
% v2_line(1:sig_size, 4:7) = num2cell( [ repmat(80,sig_size,2) repmat(-0.8,sig_size,1) repmat(-0.3,sig_size,1) ] );
%
% point(:,1) = {'point'};
% point(:,2) = { [ { 'String' 'Color' 'TextColor' }; ...
% { 'one-point' [0.2 0.3 0.4] [0.2 0.3 0.4] } ]'};
% point(1:sig_size,4) = num2cell( repmat(50,sig_size,1) );
%
% a_line(:,1) = {'line'};
% a_line(:,2) = { [ { 'String' 'LineStyle' 'LineWidth' 'Color' 'TextColor' }; ...
% { 'line text' '--' 1.5 'k' 'k' } ]'};
% a_line(1:sig_size,4:7) = num2cell( [ repmat(30,sig_size,1) repmat(40,sig_size,1) repmat(0.5,sig_size,1) repmat(-0.5,sig_size,1) ] );
%
% aux_anns = cat(3,h_line,v_line,h2_line,v2_line,point,a_line);
%
% plot_ecg_mosaic(mean(x_packed,3), 'ECG_annotations', aux_anns );
%
% See also plot_ecg_strip
%
% Author: Mariano Llamedo Soria llamedom@electron.frba.utn.edu.ar
% Last update: 19/10/2014
% Birthdate : 16/2/2012
% Copyright 2008-2015
function [ ECG_hdl axes_hdl fig_hdl all_yranges ] = plot_ecg_mosaic( ECG, varargin )
%% constants
cWaveAnnotations = { 'Pon' 'P' 'Poff' 'QRSon' 'Q' 'R' 'S' 'QRSoff' 'Ton' 'T' 'Toff' };
waveMode_idx = 1;
lWaveAnnotations = length(cWaveAnnotations);
cModesMaxYrange = {'max','min','mean','median','none'};
% bgTextColor = [251 248 230]/255;
ColorMinorGrid = [0.8 0.8 0.8];
ann_typ_idx = 1;
plot_options_str_idx = 2;
x1_idx = 4;
x2_idx = 5;
y1_idx = 6;
y2_idx = 7;
n_cant_idx = 7;
PwaveColor = [210 255 189]/255;
QRScplxColor = [255 200 255]/255;
TwaveColor = [255 240 170]/255;
DelineationNotEditedColor = [ 0.8 0.8 0.8];
DelineationEditedColor = [ 1 0 0 ];
%% parse input
p = inputParser; % Create instance of inputParser class.
p.addParamValue('ECG_header', [], @(x)(isstruct(x)) );
p.addParamValue('QRS_locations', [], @(x)( isnumeric(x) && all(x > 0) ) );
p.addParamValue('WinSize', [], @(x)( all(isnumeric(x)) ) );
p.addParamValue('ECG_annotations', [], @(x)( isnumeric(x) || iscell(x) ) );
p.addParamValue('ECG_delineation', [], @(x)(isstruct(x)) );
p.addParamValue('MaxECGrange', [], @(x)(isnumeric(x) || ischar(x) && any(strcmpi(cModesMaxYrange, x)) ) );
p.addParamValue('RowsCols', [], @(x)( all(isnumeric(x)) && all(x > 0) ) );
p.addParamValue('FigureHdl', [], @(x)( ishandle(x) ) );
p.addParamValue('AllowEdition', false, @(x)(islogical(x)) );
try
p.parse( varargin{:} );
catch MyError
fprintf(2, 'Incorrect argval/argvalue, use:\n' );
fprintf(2, 'plot_ecg_mosaic( ECG, ''arg_name'', ''arg_value'');\n\n' );
fprintf(2, 'Valid arg_name are:\n' );
fprintf(2, '+ ECG_header\n' );
fprintf(2, '+ QRS_locations\n' );
fprintf(2, '+ ECG_annotations\n' );
fprintf(2, '+ ECG_delineation\n' );
fprintf(2, '+ WinSize\n' );
fprintf(2, '+ MaxECGrange\n' );
fprintf(2, '+ RowsCols\n' );
fprintf(2, '+ FigureHdl\n' );
fprintf(2, '\nOr run ''doc plot_ecg_mosaic'' for details.\n\n' );
rethrow(MyError);
end
QRS_locations = p.Results.QRS_locations;
winSize = p.Results.WinSize;
ECG_header = p.Results.ECG_header;
annotations = p.Results.ECG_annotations;
positions = p.Results.ECG_delineation;
all_yranges = p.Results.MaxECGrange;
rowscols = p.Results.RowsCols;
fig_hdl = p.Results.FigureHdl;
bAllowEdition = p.Results.AllowEdition;
delete(p)
if( isempty(fig_hdl) )
fig_hdl = gcf;
else
figure(fig_hdl)
end
clf('reset')
if( iscell(ECG) )
[lECG, cant_sig ] = size(ECG{1});
cant_realizations = length(ECG);
ECG = cell2mat(reshape(ECG, 1, 1, cant_realizations));
elseif( isnumeric(ECG) )
if( ~isempty(QRS_locations) )
ECG = pack_signal(ECG, QRS_locations, winSize);
end
[lECG, cant_sig, cant_realizations] = size(ECG);
end
if( isempty(all_yranges) )
bYrange_given = false;
all_yranges = nan(cant_sig,2);
rangeMode = 'none';
else
if(ischar(all_yranges))
rangeMode = lower(all_yranges);
all_yranges = nan(cant_sig,2);
bYrange_given = false;
else
bYrange_given = true;
end
end
% plot arrows when range was not given.
bArrow = ~bYrange_given;
if( isempty(ECG_header) )
ECG_header.freq = nan;
ECG_header.nsamp = lECG;
ECG_header.nsig = cant_sig;
ECG_header.desc = num2str(colvec(1:cant_sig));
ECG_header.units = repmat('#', ECG_header.nsig , 1);
ECG_header.gain = repmat(1, ECG_header.nsig , 1);
ECG_header.adczero = repmat(0, ECG_header.nsig , 1);
bGridScale = false;
else
if( ECG_header.nsig ~= cant_sig )
ECG_header.nsig = cant_sig;
ECG_header.units = repmat(ECG_header.units(1,:), ECG_header.nsig , 1);
ECG_header.gain = repmat(ECG_header.gain(1), ECG_header.nsig , 1);
ECG_header.adczero = repmat(ECG_header.adczero(1), ECG_header.nsig , 1);
end
bGridScale = true;
[ECG ECG_header] = ADC2units(ECG, ECG_header, 'uv');
end
if( isempty(positions) )
all_annotations = annotations;
else
if( isempty(annotations) )
all_annotations = update_annotations_from_positions(positions);
else
all_annotations = cat(3, annotations, update_annotations_from_positions(positions) );
end
end
if( isempty(all_annotations) )
all_annotations = cell(cant_sig,1);
anns_provided = false;
else
if(iscell(all_annotations))
anns_provided = true;
[lannotations, cant_params, cant_type_anns] = size(all_annotations);
if(cant_params ~= n_cant_idx )
error( 'Invalid number of params: \n\nCol1: Ann point location\nCol2: Ann line start\nCol3: Ann line end\nCol4: plot LineSpec or Prop Name/Val\nCol5: Ann legend\nCol6: Annotation type\n' );
end
if(lannotations == 1)
%same ann for all signals
all_annotations = repmat(all_annotations, cant_sig, 1);
elseif( lannotations > 1 && lannotations < cant_sig )
error([ 'Invalid number of annotations: ' num2str(lannotations) ' for ' num2str(cant_sig) ' signals.' ])
end
else
error('Invalid annotation format.')
end
end
if( isempty(rowscols) || prod(rowscols) < cant_sig )
prev_units = get(fig_hdl,'units');
prev_size = get(fig_hdl,'outerposition');
set(fig_hdl,'Visible', 'off');
set(fig_hdl,'units','normalized');
set(fig_hdl,'outerposition',[0 0.03 1 0.97]);
set(fig_hdl,'units','pixels');
screen_size = get(fig_hdl,'outerposition');
% restore originals
set(fig_hdl,'outerposition',prev_size);
set(fig_hdl,'units',prev_units);
set(fig_hdl,'Visible', 'on');
target_aspect = screen_size(3)/screen_size(4);
aux_val = [1:cant_sig; ceil(cant_sig./(1:cant_sig)) ] ;
bExact = (cant_sig - prod(aux_val)) == 0;
if( any(bExact(2:end-1)) )
% non-trivial solution
aux_val = aux_val(:, bExact);
end
aux_aspects = aux_val(1,:)./aux_val(2,:);
[~, aux_idx] = sort(abs(aux_aspects - target_aspect));
n_rows = aux_val(1,aux_idx(1));
n_cols = aux_val(2,aux_idx(1));
else
n_rows = rowscols(1);
n_cols = rowscols(2);
end
%%
db_status = dbstatus();
bRestoreErrorStatus = false;
if( length(db_status) > 1 && strcmpi(db_status(end).cond, 'caught error') )
dbclear if caught error
bRestoreErrorStatus = true;
end
ECG_hdl = [];
axes_hdl = nan(cant_sig,1);
anns_hdl = [];
if( isnan(ECG_header.freq) )
xTimeGridSpacing = round(lECG/5); % mseg
xTimeGrid = 0:xTimeGridSpacing:lECG;
else
xTimeGridSpacing = 50; % mseg
xTimeGrid = 0:(xTimeGridSpacing/1e3*ECG_header.freq):lECG;
end
plotXmin = 1;
plotXmax = lECG;
plotXrange = plotXmax - plotXmin;
xTextOffset = 0.005*plotXrange;
if(anns_provided)
lineWidth = 1.3;
else
lineWidth = 1;
end
v_space = 0.02/(n_rows+1);
h_space = 0.02/(n_cols+2);
axe_width = 0.98/n_rows;
axe_height = 0.98/n_cols;
axe_x = h_space;
axe_y = 1-axe_height-v_space;
for ii = 1:cant_sig
txthdl = [];
axes_hdl(ii) = axes();
% AUX ECG plot
% ECG plot
ECG_hdl = [ ECG_hdl; plot( squeeze(ECG(:,ii,:)), 'LineWidth', lineWidth )];
if(~bYrange_given)
all_yranges(ii,:) = rowvec(get(axes_hdl(ii), 'ylim'));
end
set(axes_hdl(ii), 'xlim', [1 lECG]);
prev_units = get(axes_hdl(ii),'units');
set(axes_hdl(ii),'units','pixels');
axis_position = get(axes_hdl(ii),'Position');
set(axes_hdl(ii),'units',prev_units);
set(axes_hdl(ii), 'Box', 'off' );
set(axes_hdl(ii), 'Xtick', [] );
set(axes_hdl(ii), 'Ytick', [] );
set(axes_hdl(ii), 'Xcolor', [1 1 1] );
set(axes_hdl(ii), 'Ycolor', [1 1 1] );
[x_loc, y_loc] = ind2sub([n_rows n_cols], ii);
axe_x = h_space * x_loc + axe_width * (x_loc-1);
axe_y = 1-( v_space * y_loc + axe_height * y_loc);
% aux_val = get(axes_hdl(ii), 'Position' );
% set(axes_hdl(ii), 'Position', [ aux_val(1:2) - (k_magnify-1)/2*aux_val(3:4) k_magnify*aux_val(3:4) ] );
set(axes_hdl(ii), 'Position', [ axe_x axe_y axe_width axe_height ] );
end
max_yrange = [min(all_yranges(:,1)) max(all_yranges(:,2)) ];
% min_range = min(all_yranges(:,2) - all_yranges(:,1));
for ii = 1:cant_sig
set(fig_hdl, 'CurrentAxes', axes_hdl(ii));
hold(axes_hdl(ii), 'on');
if(bGridScale)
% grid plot
yTimeGridSpacing = (all_yranges(ii,2) - all_yranges(ii,1)) / 5; % uV
yTimeGrid = max_yrange(1):yTimeGridSpacing:max_yrange(2);
lyTimeGrid = length(yTimeGrid);
if( lyTimeGrid > 10 )
yTimeGrid = yTimeGrid( yTimeGrid >= all_yranges(ii,1) & yTimeGrid <= all_yranges(ii,2) );
end
aux_hdl = plot( repmat([1; lECG], 1, length(yTimeGrid)), repmat(yTimeGrid,2,1), 'Color', ColorMinorGrid, 'LineStyle', ':' );
aux_hdl = [aux_hdl; plot( repmat(xTimeGrid,2,1), repmat(colvec(max_yrange), 1, length(xTimeGrid) ), 'Color', ColorMinorGrid, 'LineStyle', ':' ) ];
set(aux_hdl,'ButtonDownFcn',@WindowButtonDown);
end
% zero line
aux_hdl = plot( [ xTimeGrid(1) xTimeGrid(end)], [0 0], '-k' );
set(aux_hdl,'ButtonDownFcn',@WindowButtonDown);
hold(axes_hdl(ii), 'off');
end
if(~bYrange_given)
switch(rangeMode)
case 'max'
all_yranges = repmat([ min(all_yranges(:,1)) max(all_yranges(:,2)) ], cant_sig, 1 );
case 'min'
all_yranges = repmat([ max(all_yranges(:,1)) min(all_yranges(:,2)) ], cant_sig, 1 );
case 'mean'
all_yranges = repmat([ mean(all_yranges(:,1)) mean(all_yranges(:,2)) ], cant_sig, 1 );
case 'median'
all_yranges = repmat([ median(all_yranges(:,1)) median(all_yranges(:,2)) ], cant_sig, 1 );
end
end
ECG_lgd = [];
for ii = 1:cant_sig
% aux_val = get(axes_hdl(ii), 'ylim');
% aux_val2 = (all_yranges - abs(diff(aux_val)))/2;
% set(axes_hdl(ii), 'ylim', aux_val + [-aux_val2 aux_val2] );
set(axes_hdl(ii), 'ylim', all_yranges(ii,:) );
set(fig_hdl, 'CurrentAxes', axes_hdl(ii));
y_lims = get(axes_hdl(ii), 'ylim');
x_lims = get(axes_hdl(ii), 'xlim');
if( ii <= size(ECG_header.desc,1) )
ECG_lgd = [ECG_lgd; text(x_lims(1) + 0.01*abs(diff(x_lims)), y_lims(2) - 0.05*abs(diff(y_lims)), ECG_header.desc(ii,:), 'EdgeColor', [0 0 0] )];
else
ECG_lgd = [ECG_lgd; text(x_lims(1) + 0.01*abs(diff(x_lims)), y_lims(2) - 0.05*abs(diff(y_lims)), ['Sig ' num2str(ii)], 'EdgeColor', [0 0 0] )];
end
plotXmin = x_lims(1);
plotXmax = x_lims(2);
plotXrange = plotXmax - plotXmin;
plotYmin = y_lims(1);
plotYmax = y_lims(2);
plotYrange = plotYmax - plotYmin;
yTextOffset = 0.01*plotYrange;
yTimeGridSpacing = (all_yranges(ii,2) - all_yranges(ii,1)) / 5; % uV
yTimeGrid = max_yrange(1):yTimeGridSpacing:max_yrange(2);
aux_idx = find(yTimeGrid > plotYmin & yTimeGrid < plotYmax);
width_legend = 0.18*plotXrange;
height_legend = 0.1*plotYrange;
left_legend = plotXmax - width_legend - 5*xTextOffset;
bottom_legend = plotYmax - height_legend - 2*yTextOffset;
% scale X
% plot( [-xTimeGridSpacing/2 -xTimeGridSpacing/2 xTimeGridSpacing/2 xTimeGridSpacing/2] + 5/7*lECG, plotYmin + 0.05*plotYrange + [yTextOffset 0 0 yTextOffset], 'Color', [0 0 0], 'LineWidth', 1 );
if( bArrow )
ECG_lgd = [ECG_lgd; arrow( [ xTimeGrid(2) plotYmin + 0.05*plotYrange], [ xTimeGrid(3) plotYmin + 0.05*plotYrange], 2, 0.5, [0 0 0], axes_hdl(ii) )];
end
if( isnan(ECG_header.freq) )
aux_str = [ num2str(xTimeGridSpacing) ' #' ];
else
aux_str = [ num2str(xTimeGridSpacing) ' ms' ];
end
ECG_lgd = [ECG_lgd; text( mean(xTimeGrid(2:3)), plotYmin + 0.05*plotYrange + 5*yTextOffset, aux_str, 'FontSize', 8, 'HorizontalAlignment', 'center') ];
% scale Y
% plot( [ -xTextOffset 0 0 -xTextOffset] + 0.95*lECG, plotYmin + 5/7*plotYrange + [-yTimeGridSpacing/2 -yTimeGridSpacing/2 yTimeGridSpacing/2 yTimeGridSpacing/2], 'Color', [0 0 0], 'LineWidth', 1 );
if( bArrow )
ECG_lgd = [ECG_lgd; arrow( [0.95*lECG - xTextOffset; yTimeGrid(aux_idx(2))], [0.95*lECG - xTextOffset; yTimeGrid(aux_idx(3))], 2, 0.5, [0 0 0], axes_hdl(ii) )];
end
ECG_lgd = [ECG_lgd; text( 0.95*lECG - 10*xTextOffset, mean(yTimeGrid(aux_idx(2:3))), [ num2str(yTimeGridSpacing) ' ' ECG_header.units(ii,:) ], 'FontSize', 8, 'HorizontalAlignment', 'center', 'Rotation', 90)];
txthdl = [];
if( ~isempty(all_annotations{ii}) )
update_annotations( ii )
end
if ~isempty(positions)
ECG_lgd = [ ECG_lgd; patch([left_legend left_legend [left_legend left_legend]+width_legend left_legend ], [bottom_legend [bottom_legend bottom_legend]+height_legend bottom_legend bottom_legend], [1 1 1], 'EdgeColor', [0 0 0])];
ECG_lgd = [ ECG_lgd; text( left_legend + width_legend/4, bottom_legend + height_legend/2, 'P', 'FontSize', 8, 'HorizontalAlignment', 'center', 'BackgroundColor', PwaveColor) ];
ECG_lgd = [ ECG_lgd; text( left_legend + width_legend/2, bottom_legend + height_legend/2, 'QRS', 'FontSize', 8, 'HorizontalAlignment', 'center', 'BackgroundColor', QRScplxColor ) ];
ECG_lgd = [ ECG_lgd; text( left_legend + width_legend*3/4, bottom_legend + height_legend/2, 'T', 'FontSize', 8, 'HorizontalAlignment', 'center', 'BackgroundColor', TwaveColor ) ];
end
end
if(bRestoreErrorStatus)
dbstop if caught error
end
my_timer = timer('TimerFcn',@timer_fcn, 'StopFcn', @timer_stop_fcn , 'StartDelay', 10);
set(fig_hdl, ...
'WindowScrollWheelFcn', {@ScrollWheel}, ...
'WindowKeyPressFcn', {@KeyPress}, ...
'WindowButtonUpFcn', {@WindowButtonUp} ...
);
set(axes_hdl,...
'ButtonDownFcn', {@WindowButtonDown} ...
);
set(ECG_hdl,...
'ButtonDownFcn', {@WindowButtonDown} ...
);
if( isempty(positions) )
for ii = 1:lWaveAnnotations
manual_edited_position.(cWaveAnnotations{ii}) = nan;
end
manual_edited_position = repmat(manual_edited_position, ECG_header.nsig,1);
else
manual_edited_position = positions;
end
PrevStateWindowButtonMotionFcn = nan;
bIsDragAllowed = false;
lead_move_idx = [];
axes_move = [];
move_hdl = [];
sample_move = [];
bFixedWave = false;
bPreserveFix = false;
manual_edited_position_matrix = positions2matrix(manual_edited_position, cWaveAnnotations);
bDelineationEdited = false;
if( nargout == 0 )
ECG_hdl = [];
end
%% Internal functions
function timer_stop_fcn(obj,event_obj)
if(bPreserveFix)
% never stop when editing
start(my_timer)
end
end
function timer_fcn(obj,event_obj)
delete(findobj('Tag', 'title_efimero' ));
if(~bPreserveFix)
% allow edition of the closer wave
bFixedWave = false;
end
end
function this_annotations = update_annotations_from_positions( this_positions )
[min_vals_pw_x, min_vals_pw_y, max_vals_pw_x, max_vals_pw_y] = CalcMinMax( this_positions, {'Pon' 'P' 'Poff'} );
[min_vals_qrs_x, min_vals_qrs_y, max_vals_qrs_x, max_vals_qrs_y] = CalcMinMax( this_positions, {'QRSon' 'qrs' 'QRSoff'} );
[min_vals_tw_x, min_vals_tw_y, max_vals_tw_x, max_vals_tw_y] = CalcMinMax( this_positions, {'Ton' 'T' 'Toff'} );
Pwave = cell(cant_sig,7);
Pwave_max = cell(cant_sig,7);
QRScplx = cell(cant_sig,7);
Twave = cell(cant_sig,7);
QRScplx_point = cell(cant_sig,7);
Qwave = cell(cant_sig,7);
Rwave = cell(cant_sig,7);
Swave = cell(cant_sig,7);
Twave_max = cell(cant_sig,7);
Pwave(:,1) = {'patch'};
Pwave(:,2) = { [ { 'FaceColor' }; ...
{ PwaveColor } ]'};
Pwave(:,[x1_idx y1_idx x2_idx y2_idx]) = [ num2cell(min_vals_pw_x), num2cell(min_vals_pw_y), num2cell(max_vals_pw_x), num2cell(max_vals_pw_y)];
k_point_color = 0.9;
Pwave_max(:,1) = {'point'};
Pwave_max(:,2) = { [ { 'String' 'Marker' 'MarkerSize' 'Color' }; ...
{ 'P' 'd' 5 k_point_color*PwaveColor } ]'};
Pwave_max(:,4) = {this_positions(:).P};
QRScplx(:,1) = {'patch'};
QRScplx(:,2) = { [ { 'FaceColor' }; ...
{ QRScplxColor } ]'};
QRScplx(:,[x1_idx y1_idx x2_idx y2_idx]) = [ num2cell(min_vals_qrs_x), num2cell(min_vals_qrs_y), num2cell(max_vals_qrs_x), num2cell(max_vals_qrs_y)];
% QRScplx_point(:,1) = {'point'};
% QRScplx_point(:,2) = { [ { 'String' 'Marker' 'MarkerSize' 'Color' }; ...
% { 'qrs' 'd' 5 k_point_color*QRScplxColor } ]'};
% QRScplx_point(:,4) = {this_positions(:).qrs};
QRScplx_point = [];
Qwave(:,1) = {'point'};
Qwave(:,2) = { [ { 'String' 'Marker' 'MarkerSize' 'Color' }; ...
{ 'Q' 'd' 5 k_point_color*QRScplxColor } ]'};
Qwave(:,4) = {this_positions(:).Q};
Rwave(:,1) = {'point'};
Rwave(:,2) = { [ { 'String' 'Marker' 'MarkerSize' 'Color' }; ...
{ 'R' 'd' 5 k_point_color*QRScplxColor } ]'};
Rwave(:,4) = {this_positions(:).R};
Swave(:,1) = {'point'};
Swave(:,2) = { [ { 'String' 'Marker' 'MarkerSize' 'Color' }; ...
{ 'S' 'd' 5 k_point_color*QRScplxColor } ]'};
Swave(:,4) = {this_positions(:).S};
Twave(:,1) = {'patch'};
Twave(:,2) = { [ { 'FaceColor' }; ...
{ TwaveColor } ]'};
Twave(:,[x1_idx y1_idx x2_idx y2_idx]) = [ num2cell(min_vals_tw_x), num2cell(min_vals_tw_y), num2cell(max_vals_tw_x), num2cell(max_vals_tw_y)];
Twave_max(:,1) = {'point'};
Twave_max(:,2) = { [ { 'String' 'Marker' 'MarkerSize' 'Color' }; ...
{ 'T' 'd' 5 k_point_color*TwaveColor } ]'};
Twave_max(:,4) = {this_positions(:).T};
this_annotations = cat(3, Pwave_max, QRScplx_point, Qwave, Rwave, Swave, Twave_max, Pwave, QRScplx, Twave);
end
function update_annotations( lead_idx )
x_lims = get(axes_hdl(lead_idx), 'xlim');
x_range = abs(diff(x_lims));
y_lims = all_yranges(lead_idx,:);
y_range = abs(diff(y_lims));
for jj = 1:cant_type_anns
switch( all_annotations{lead_idx,ann_typ_idx,jj} )
case 'point'
if( ~isnan(all_annotations{lead_idx,x1_idx,jj}) )
aux_hdl = text_arrow(all_annotations{lead_idx,x1_idx,jj}, ECG(all_annotations{lead_idx,x1_idx,jj}, lead_idx), '', all_annotations{lead_idx,plot_options_str_idx,jj}, axes_hdl(lead_idx) );
set(aux_hdl, 'Tag', num2str(lead_idx) );
txthdl = [ txthdl aux_hdl ];
uistack(aux_hdl,'bottom');
if(bAllowEdition)
set(aux_hdl,...
'ButtonDownFcn', {@WindowButtonDown} ...
);
end
% aux_hdl = eval( [ 'plot( all_annotations{' num2str(lead_idx) ',' num2str(x1_idx) ',' num2str(jj) '}, ECG(all_annotations{' num2str(lead_idx) ',' num2str(x1_idx) ',' num2str(jj) '},' num2str(lead_idx) ',1) ' all_annotations{lead_idx,plot_options_str_idx,jj} ')']);
% anns_hdl = [ anns_hdl; colvec(aux_hdl)];
% txthdl = [ txthdl; text( all_annotations{lead_idx,x1_idx,jj} + 0.03*x_range, ECG(all_annotations{lead_idx,x1_idx,jj}, lead_idx, 1), all_annotations{lead_idx,text_idx,jj}, 'BackgroundColor', bgTextColor )];
end
case 'line'
if( isempty(all_annotations{lead_idx,x1_idx,jj}) )
all_annotations{lead_idx,x1_idx,jj} = repmat(x_lims(1),1,length(all_annotations{lead_idx,y1_idx,jj}));
end
if( isempty(all_annotations{lead_idx,x2_idx,jj}) )
all_annotations{lead_idx,x2_idx,jj} = repmat(x_lims(2),1,length(all_annotations{lead_idx,y1_idx,jj}));
end
if(cant_realizations > 1)
if( isempty(all_annotations{lead_idx,y1_idx,jj}) )
all_annotations{lead_idx,y1_idx,jj} = min(squeeze(ECG(all_annotations{lead_idx,x1_idx,jj},lead_idx,:)),[],2);
end
if( isempty(all_annotations{lead_idx,y2_idx,jj}) )
all_annotations{lead_idx,y2_idx,jj} = max(squeeze(ECG(all_annotations{lead_idx,x1_idx,jj},lead_idx,:)),[],2);
end
else
if( isempty(all_annotations{lead_idx,y1_idx,jj}) )
all_annotations{lead_idx,y1_idx,jj} = repmat(min(ECG(:,lead_idx)),1,length(all_annotations{lead_idx,x1_idx,jj}));
end
if( isempty(all_annotations{lead_idx,y2_idx,jj}) )
all_annotations{lead_idx,y2_idx,jj} = repmat(max(ECG(:,lead_idx)),1,length(all_annotations{lead_idx,x1_idx,jj}));
end
end
if( ~isempty(cell2mat(all_annotations(lead_idx,4:7,jj))) )
aux_hdl = text_line(cell2mat(all_annotations(lead_idx,[x1_idx x2_idx],jj)), cell2mat(all_annotations(lead_idx,[y1_idx y2_idx],jj)), '', all_annotations{lead_idx,plot_options_str_idx,jj}, axes_hdl(lead_idx) );
set(aux_hdl, 'Tag', num2str(lead_idx) );
txthdl = [ txthdl aux_hdl ];
for kk = aux_hdl
uistack(kk,'bottom');
if(bAllowEdition)
set(kk,...
'ButtonDownFcn', {@WindowButtonDown} ...
);
end
end
% aux_hdl = eval( [ 'plot( cell2mat(all_annotations(' num2str(lead_idx) ', [' num2str(x1_idx) ' ' num2str(x2_idx) '],' num2str(jj) ')), cell2mat(all_annotations(' num2str(lead_idx) ', [' num2str(y1_idx) ' ' num2str(y2_idx) '],' num2str(jj) '))' all_annotations{lead_idx,plot_options_str_idx,jj} ')']);
% anns_hdl = [ anns_hdl; colvec(aux_hdl)];
% if( ~strcmpi('', all_annotations{lead_idx,text_idx,jj}) )
% txthdl = [ txthdl; text(all_annotations{lead_idx,x1_idx,jj} + 0.03*x_range, all_annotations{lead_idx,y1_idx,jj} + 0.03*y_range, all_annotations{lead_idx,text_idx,jj}, 'VerticalAlignment', 'bottom','BackgroundColor', bgTextColor )];
% % set(txthdl(end), 'Rotation', round(180/pi*atan2( (all_annotations{lead_idx,y2_idx,jj} - all_annotations{lead_idx,y1_idx,jj}) * axis_position(4) / y_range , (all_annotations{lead_idx,x2_idx,jj} - all_annotations{lead_idx,x1_idx,jj}) * axis_position(3) / x_range )) );
% % set(txthdl(end), 'Rotation', 90 );
% end
end
case 'patch'
if( isempty(all_annotations{lead_idx,x1_idx,jj}) )
all_annotations{lead_idx,x1_idx,jj} = repmat(x_lims(1),1,length(all_annotations{lead_idx,y1_idx,jj}));
end
if( isempty(all_annotations{lead_idx,x2_idx,jj}) )
all_annotations{lead_idx,x2_idx,jj} = repmat(x_lims(2),1,length(all_annotations{lead_idx,y1_idx,jj}));
end
if(cant_realizations > 1)
if( isempty(all_annotations{lead_idx,y1_idx,jj}) )
all_annotations{lead_idx,y1_idx,jj} = min(squeeze(ECG(all_annotations{lead_idx,x1_idx,jj},lead_idx,:)),[],2);
end
if( isempty(all_annotations{lead_idx,y2_idx,jj}) )
all_annotations{lead_idx,y2_idx,jj} = max(squeeze(ECG(all_annotations{lead_idx,x1_idx,jj},lead_idx,:)),[],2);
end
else
if( isempty(all_annotations{lead_idx,y1_idx,jj}) )
all_annotations{lead_idx,y1_idx,jj} = repmat(min(ECG(:,lead_idx)),1,length(all_annotations{lead_idx,x1_idx,jj}));
end
if( isempty(all_annotations{lead_idx,y2_idx,jj}) )
all_annotations{lead_idx,y2_idx,jj} = repmat(max(ECG(:,lead_idx)),1,length(all_annotations{lead_idx,x1_idx,jj}));
end
end
if( ~isempty(cell2mat(all_annotations(lead_idx,4:7,jj))) )
aux_hdl = patch( [all_annotations{lead_idx,x1_idx,jj} all_annotations{lead_idx,x1_idx,jj} all_annotations{lead_idx,x2_idx,jj} all_annotations{lead_idx,x2_idx,jj} ], [ all_annotations{lead_idx,y1_idx,jj} all_annotations{lead_idx,y2_idx,jj} all_annotations{lead_idx,y2_idx,jj} all_annotations{lead_idx,y1_idx,jj} ], [1 1 1], 'EdgeColor', 'none');
aux_prop_vals = all_annotations{lead_idx,plot_options_str_idx,jj};
set( aux_hdl, aux_prop_vals(:,1)', aux_prop_vals(:,2)' );
set(aux_hdl, 'Tag', num2str(lead_idx) );
if(bAllowEdition)
set(aux_hdl,...
'ButtonDownFcn', {@WindowButtonDown} ...
);
end
txthdl = [ txthdl aux_hdl ];
uistack(aux_hdl,'bottom');
end
end
end
end
function ScrollWheel(obj,event_obj)
if( ~bAllowEdition )
return
end
if (event_obj.VerticalScrollCount > 0)
waveMode_idx = waveMode_idx + 1;
if(waveMode_idx > lWaveAnnotations)
waveMode_idx = 1;
end
else
waveMode_idx = waveMode_idx - 1;
if(waveMode_idx < 1)
waveMode_idx = lWaveAnnotations;
end
end
bFixedWave = true;
update_title_efimero( cWaveAnnotations{waveMode_idx}, 10 );
end
function KeyPress(obj,event_obj)
if(strcmp(event_obj.Key,'e'))
bAllowEdition = ~bAllowEdition;
if(bAllowEdition)
update_title_efimero( 'Edit mode ON', 2 )
set(ECG_lgd, 'visible', 'off')
else
update_title_efimero( 'Edit mode OFF', 2 )
set(ECG_lgd, 'visible', 'on')
end
elseif (strcmp(event_obj.Key,'s'))
if( bDelineationEdited )
% store manual_edited_position
assignin('caller', 'manual_edited_position', manual_edited_position)
fprintf(1, '[%s] Edited positions stored in caller workspace.\n', datestr(now) );
set( fig_hdl, 'Color', DelineationNotEditedColor )
else
fprintf(2, '[%s] No changes made, edit delineation before saving.\n', datestr(now) );
end
end
end
function [min_vals_x, min_vals_y, max_vals_x, max_vals_y] = CalcMinMax( this_annotation, field_names )
if( isfield(this_annotation, field_names{1} ) )
aux_on = cell2mat({this_annotation(:).(field_names{1})});
else
aux_on = [];
end
if( isfield(this_annotation, field_names{2} ) )
aux_peak = cell2mat({this_annotation(:).(field_names{2})});
else
aux_peak = [];
end
if( isfield(this_annotation, field_names{3} ) )
aux_off = cell2mat({this_annotation(:).(field_names{3})});
else
aux_off = [];
end
start_sample = 1;
% wave start
bOn = ~isnan(aux_on) & aux_on >= start_sample & aux_on <= start_sample+lECG;
% wave end
bOff = ~isnan(aux_off) & aux_off >= start_sample & aux_off <= start_sample+lECG;
% wave peak
bPeak = ~isnan(aux_peak) & aux_peak >= start_sample & aux_peak <= start_sample+lECG;
% wave conection between start-end
bOnOff = bOn & bOff;
aux_complete_idx = find(bOnOff);
aux_complete_idxx = num2cell(nan(ECG_header.nsig,1));
% normal sampled version
aux_complete_idxx(aux_complete_idx) = arrayfun( @(a)( max(1, aux_on(a)):min(lECG,aux_off(a)) ),aux_complete_idx, 'UniformOutput', false);
if( any(~bOnOff & bOn & bPeak) )
% on-peak
aux_complete_idx = find( ~bOnOff & bOn & bPeak);
aux_complete_idxx(aux_complete_idx) = arrayfun( @(a)( aux_on(a):aux_peak(a) ),aux_complete_idx, 'UniformOutput', false);
end
if( any(~bOnOff & bOff & bPeak) )
% peak-off
aux_complete_idx = find(~bOnOff & bPeak & bOff);
aux_complete_idxx(aux_complete_idx) = arrayfun( @(a)( aux_peak(a):aux_off(a) ),aux_complete_idx, 'UniformOutput', false);
end
max_vals_x = cellfun( @(a,lead)( a(end) ), aux_complete_idxx);
max_vals_y = cellfun( @(a,lead)( max(dummyfun(ECG,a, lead))), aux_complete_idxx, num2cell((1:ECG_header.nsig)'));
min_vals_x = cellfun( @(a,lead)( a(1) ), aux_complete_idxx);
min_vals_y = cellfun( @(a,lead)( min(dummyfun(ECG,a, lead))), aux_complete_idxx, num2cell((1:ECG_header.nsig)'));
end
function fun_val = dummyfun(a,b,c)
fun_val = nan;
if( ~any(isnan(b)) && ~any(isnan(c)) )
fun_val = a(b,c);
end
end
function [lead_idx, obj ] = get_lead_idx(obj)
lead_idx = find(obj == axes_hdl);
if( isempty(lead_idx) )
%click on the signal
obj = get(obj, 'Parent');
lead_idx = find(obj == axes_hdl);
end
end
function WindowButtonMotion(obj,event_obj)
if (bIsDragAllowed)
point = get(axes_move,'CurrentPoint');
sample_move = round(point(1));
sample_move = min( lECG, max(1, round(sample_move) ) );
if( ~isempty(move_hdl) )
delete(move_hdl)
end
move_hdl = plot(axes_move, sample_move, ECG(sample_move, lead_move_idx), '+r', 'MarkerSize', 8 );
else
set(fig_hdl, 'WindowButtonMotionFcn', PrevStateWindowButtonMotionFcn );
end
end
function WindowButtonUp(obj,event_obj)
if (bIsDragAllowed)
bIsDragAllowed = false;
bPreserveFix = false;
set(fig_hdl, 'WindowButtonMotionFcn', PrevStateWindowButtonMotionFcn );
hold(axes_move, 'off' )
if( ~isempty(move_hdl) )
delete(move_hdl)
move_hdl = [];
end
manual_edited_position(lead_move_idx).((cWaveAnnotations{waveMode_idx})) = sample_move;
aux_mat = manual_edited_position_matrix{lead_move_idx};
aux_mat(waveMode_idx) = sample_move;
manual_edited_position_matrix{lead_move_idx} = aux_mat;
if( isempty(annotations) )
all_annotations = update_annotations_from_positions(manual_edited_position);
else
all_annotations = cat(3, annotations, update_annotations_from_positions(manual_edited_position) );
end
delete(findobj('Tag', num2str(lead_move_idx) ))
update_annotations( lead_move_idx )
lead_move_idx = [];
bDelineationEdited = true;
set( fig_hdl, 'Color', DelineationEditedColor )
end
end
function WindowButtonDown(obj,event_obj)
if( ~bAllowEdition )
return
end
[lead_idx, obj] = get_lead_idx(obj);
if (strcmp(get(fig_hdl,'SelectionType'),'alt'))
% Delete annotation
point = get(obj,'CurrentPoint');
sample = round(point(1));
if( ~bFixedWave )
[~, waveMode_idx] = sort(abs(manual_edited_position_matrix{lead_idx} - sample));
waveMode_idx = waveMode_idx(1);
end
manual_edited_position(lead_idx).((cWaveAnnotations{waveMode_idx})) = nan;
aux_mat = manual_edited_position_matrix{lead_idx};
aux_mat(waveMode_idx) = nan;
manual_edited_position_matrix{lead_idx} = aux_mat;
if( isempty(annotations) )
all_annotations = update_annotations_from_positions(manual_edited_position);
else
all_annotations = cat(3, annotations, update_annotations_from_positions(manual_edited_position) );
end
delete(findobj('Tag', num2str(lead_idx) ))
update_annotations( lead_idx )
elseif( strcmp(get(fig_hdl,'SelectionType'),'extend') )
point = get(obj,'CurrentPoint');
sample = round(point(1));
if( bFixedWave )
bPreserveFix = true;
else
[~, waveMode_idx] = sort(abs(manual_edited_position_matrix{lead_idx} - sample));
if( sample < manual_edited_position(lead_idx).((cWaveAnnotations{waveMode_idx(1)})) )
waveMode_idx = max(1, waveMode_idx(1) - 1);
elseif( sample == manual_edited_position(lead_idx).((cWaveAnnotations{waveMode_idx(1)})) )
waveMode_idx = waveMode_idx(1);
else
waveMode_idx = min( lWaveAnnotations, waveMode_idx(1) + 1);
end
end
manual_edited_position(lead_idx).((cWaveAnnotations{waveMode_idx})) = sample;
aux_mat = manual_edited_position_matrix{lead_idx};
aux_mat(waveMode_idx) = sample;
manual_edited_position_matrix{lead_idx} = aux_mat;
if( isempty(annotations) )
all_annotations = update_annotations_from_positions(manual_edited_position);
else
all_annotations = cat(3, annotations, update_annotations_from_positions(manual_edited_position) );
end
delete(findobj('Tag', num2str(lead_idx) ))
update_annotations( lead_idx )
bDelineationEdited = true;
set( fig_hdl, 'Color', DelineationEditedColor )
elseif (strcmp(get(fig_hdl,'SelectionType'),'normal'))
if (~bIsDragAllowed)
axes_move = obj;
lead_move_idx = lead_idx;
bIsDragAllowed = true;
point = get(axes_move,'CurrentPoint');
sample = round(point(1));
if( bFixedWave )
bPreserveFix = true;
else
[~, waveMode_idx] = sort(abs(manual_edited_position_matrix{lead_move_idx} - sample));
waveMode_idx = waveMode_idx(1);
end
sample_move = sample;
PrevStateWindowButtonMotionFcn = get(fig_hdl, 'WindowButtonMotionFcn');
set(fig_hdl, 'WindowButtonMotionFcn', @WindowButtonMotion);
hold(axes_move, 'on' )
end
end
end
function update_title_efimero( strTitle, delay )
delete(findobj('Tag', 'title_efimero' ))
for hdl_idx = rowvec(axes_hdl)
set(fig_hdl, 'CurrentAxes', hdl_idx);
y_lims = get(hdl_idx, 'ylim');
x_lims = get(hdl_idx, 'xlim');
plotXmin = x_lims(1);
plotXmax = x_lims(2);
plotXrange = plotXmax - plotXmin;
plotYmin = y_lims(1);
plotYmax = y_lims(2);
plotYrange = plotYmax - plotYmin;
yTextOffset = 0.01*plotYrange;
width_legend = 0.2*plotXrange;
height_legend = 0.1*plotYrange;
left_legend = plotXmax - width_legend - 5*xTextOffset;
bottom_legend = plotYmin;
aux_hdl = text( left_legend + width_legend/2, bottom_legend + height_legend/2, strTitle, 'FontSize', 8, 'HorizontalAlignment', 'center', 'BackgroundColor', 'r' );
set(aux_hdl, 'Tag', 'title_efimero')
end
if( ~isinf(delay) && strcmpi(my_timer.Running, 'off') )
my_timer.StartDelay = delay;
start(my_timer)
end
end
end