%% Plots and interact with ECG signal % This function plot ECG signals, eventually with annotation marks such as % QRS complex locations, or even P, Q, R, S and T wave locations and % boundaries. % % Some of the relevant features: % % + User can interact using mouse shortcuts with several aspects of the % visualization, such as zoom, pan and measurements. % + Information of the multilead wave boundaries can be added to the ECG, % for example the delineation obtained with wavedet. % + It can "pretty" present the ECG charts for printing to pdf % documents % + It can be easily added to your project for debug or result % presentation through its versatile interface. % % The mouse interaction was adapted from the Dragzoom function, by Evgeny % Pr, this can be found in: http://www.mathworks.com/matlabcentral/fileexchange/29276-dragzoom-drag-and-zoom-tool % % Prototype % % function ECG_hdl = plot_ecg_strip( ECG, varargin ) % % Arguments: % % +ECG: [numeric | char | ECGwrapper] REQUIRED. % Signal matrix of dimension [nsamp nsig] where: % % - nsamp: time length in samples % - nsig: number of ECG leads or number of signals. % % Recording filename or ECGwrapper object of the ECG recording % are also accepted. % % +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)) ) % % +Start_time: [numeric] OPTIONAL. Start time in seconds. % % +End_time: [numeric] OPTIONAL. Start time in seconds. % % +QRS_locations: [numeric] OPTIONAL. Default values enclosed in () % Synchronization sample. In ECG context, this values are % the QRS fiducial point. (empty) % % +QRS_start_index: [numeric] OPTIONAL. Default values enclosed in () % Start at the i-th QRS_start_index heartbeat % in QRS_locations, or QRS_locations(QRS_start_index). (empty) % % +QRS_start_complexes: [numeric] OPTIONAL. Default values enclosed in () % Display the amount of QRS_start_complexes heartbeats from the QRS_start_index. (empty) % % +Lead_offset: [numeric] OPTIONAL. Default values enclosed in () % A DC value [nsig 1] to be added to each lead. (0) % % +Lead_gain: [numeric] OPTIONAL. Default values enclosed in () % A value [nsig 1] to be multiplied by each lead. (1) % % +ECG_delineation_single_lead: % [struct array size nsig] OPTIONAL. Default values enclosed in () % Annotation struct of size [nsig 1] with fields: % Pon P Poff QRSon qrs QRSoff Ton T Toff. Each % field of size [1 nhb], being nhb the amount of % heartbeats. (empty) % % +ECG_delineation_multilead: [struct] OPTIONAL. Default values enclosed in () % Annotation struct with the same fields of and % characteristics of ECG_delineation_single_lead. (empty) % % +Title: [string] OPTIONAL. Default values enclosed in () % Description title. (recname - time interval) % % +DetailLevel: [string] OPTIONAL. Default values enclosed in () % The details included in the ECG plot depends % on the zoom level and the data provided. % Possible values: 'all', 'single-lead', % 'multilead' and 'none'. (none) % % +PrettyPrint: [bool] OPTIONAL. Default values enclosed in () % Prepare the plot for printing as a PDF. (false) % % +Figure_handle: [axes handle] OPTIONAL. Choose the figure to display the % plot. (handle produced by figure) % % Limits and Known bugs: % Probably a lot :( ... but dont panic! send me feedback if you need help. % % Example: % % plot_ecg_strip([ ECGkitrootpath '\recordings\example_recording.mat']) % % plot_ecg_strip(ECG) % % plot_ecg_strip(ECG, 'ECG_header', heasig, 'ECG_delineation_single_lead', positions_single_lead); % % plot_ecg_strip(ECG, 'ECG_header', heasig, 'ECG_delineation_single_lead', positions_single_lead, 'Start_time', 10*60, 'End_time', 20*60); % % % Mouse actions: % % Normal mode: % single-click and holding LB : Activation Drag mode % single-click and holding RB : Activation rubber band for region zooming % single-click MB : Activation measuring rubber band mode % scroll wheel MB : Activation Zoom mode % double-click LB, RB, MB : Reset to Original View % % Magnifier mode ('m' key): % single-click LB : Not Used % single-click RB : Not Used % single-click MB : Reset Magnifier to Original View % scroll MB : Change Magnifier Zoom % double-click LB : Increase Magnifier Size % double-click RB : Decrease Magnifier Size % % Hotkeys: % 'h' : Show help % '+' : Zoom plus % '-' : Zoom minus % 'd' : Toggle the detail level of the annotations % 'a' : Toggle the annotations graph mode ... % '0' : Set default axes (reset to original view) % 'c' : On/Off pointer in crosshair mode % 'g' : If pressed and holding, change lead gain with scroll % 'o' : If pressed and holding, change lead offset with scroll % 'x' : If pressed and holding, zoom and drag works only for X axis % 'y' : If pressed and holding, zoom and drag works only for Y axis % 'm' : If pressed and holding, Magnifier mode on % 'p' : On/Off paper mode % 'r' : Export format (PDF/PNG) % 's' : Export current view % % % See also plot_ecg_strip % % Author: Mariano Llamedo Soria llamedom@electron.frba.utn.edu.ar % Last update: 19/11/2014 % Birthdate : 15/8/2012 % Copyright 2008-2015 function returned_handles = plot_ecg_strip( ECG, varargin ) returned_handles = []; %% Constants % Avoid IO greater than MaxIOread MaxIOread = 10; %megabytes %multilead color_Pwave_global = [210 255 189]/255; color_QRScomplex_global = [255 200 255]/255; color_Twave_global = [255 240 170]/255; %single-lead PwaveColor = [210 255 189]/255; QRScplxColor = [255 200 255]/255; TwaveColor = [255 240 170]/255; cBeatLabels = { 'N' 'S' 'V' 'F' 'U' }; cBeatLabelsColorCode = { [0 0 255]/255 [0 255 0]/255 [255 0 0]/255 [0 255 255]/255 [0 0 0]/255 }; cAnnotationsFieldNamesRequired = {'time' 'anntyp'}; cAnnotationFieldNames = { 'Pon' 'P' 'Poff' 'QRSon' 'qrs' 'QRSoff' 'Ton' 'T' 'Tprima' 'Toff' }; cDetailLevels = {'all', 'single-lead', 'multilead', 'none' }; cUnitsVoltages = {'NV' 'UV' 'MV' 'V' 'UV' 'VOLT'}; cKnownReportFormats = {'pdf', 'png'}; % k for reports target_res = 300; % samples/inch target_res = target_res / 2.54; % samples/cm min_cant_samp_seconds = 0.1; % seconds %% argument definition p = inputParser; % Create instance of inputParser class. addRequired(p, 'ECG', @(x)(~isempty(x) && (isnumeric(x) || ischar(x) || isa(x, 'ECGwrapper') ) ) ); p.addParamValue('ECG_header', [], @(x)(isstruct(x)) ); p.addParamValue('QRS_locations', [], @(x)( isnumeric(x) && all(x > 0) || iscell(x) || isa(x, 'ECGwrapper') )); p.addParamValue('Start_time', [], @(x)( isnumeric(x) && x >= 0 ) ); p.addParamValue('End_time', [], @(x)( isnumeric(x) && x > 0 ) ); p.addParamValue('QRS_start_index', [], @(x)( isnumeric(x) && x > 0 ) ); p.addParamValue('QRS_complexes', [], @(x)( isnumeric(x) && all(x > 0) ) ); p.addParamValue('Figure_handle', [], @(x)(ishandle(x)) ); p.addParamValue('Lead_offset', [], @(x)( isnumeric(x) ) ); p.addParamValue('Lead_gain', [], @(x)( isnumeric(x) ) ); p.addParamValue('Heartbeat_classification', [], @(x)(isstruct(x) || isa(x, 'ECGwrapper')) ); p.addParamValue('ECG_delineation_single_lead', [], @(x)(isstruct(x) || isa(x, 'ECGwrapper')) ); p.addParamValue('ECG_delineation_multilead', [], @(x)(isstruct(x) || isa(x, 'ECGwrapper')) ); p.addParamValue('Title', [], @(x)(ischar(x)) ); p.addParamValue('LinkedHandle', [], @(x)(ishandle(x)) ); p.addParamValue('DetailLevel', 'dont_care', @(x)( ischar(x) && any(strcmpi(x,cDetailLevels)) ) ); p.addParamValue('PrettyPrint', false, @(x)(islogical(x)) ); p.addParamValue('OnlyECG', false, @(x)(islogical(x)) ); p.addParamValue('FilterECG', false, @(x)(islogical(x)) ); p.addParamValue('ReportFilename', [], @(x)( isempty(x) ) ); try p.parse( ECG, varargin{:} ); catch MyError fprintf(2, disp_option_enumeration('Incorrect argval/argvalue, use:', p.Parameters )); fprintf(2, '\nOr run ''doc plot_ecg_strip'' for details.\n\n' ); rethrow(MyError); end ECG = p.Results.ECG; heasig = p.Results.ECG_header; QRS_locations = p.Results.QRS_locations; start_time = p.Results.Start_time; end_time = p.Results.End_time; QRS_start_idx = p.Results.QRS_start_index; cant_qrs = p.Results.QRS_complexes; fig_hdl = p.Results.Figure_handle; lead_offset = p.Results.Lead_offset; gains = p.Results.Lead_gain; hb_labels = p.Results.Heartbeat_classification; annotations = p.Results.ECG_delineation_single_lead; %multilead global_annotations = p.Results.ECG_delineation_multilead; %single-lead strTitle = p.Results.Title; linked_hdl = p.Results.LinkedHandle; strDetail = p.Results.DetailLevel; bPrettyPrint = p.Results.PrettyPrint; bOnlyECG = p.Results.OnlyECG; bFilterECG = p.Results.FilterECG; report_filename = p.Results.ReportFilename; clear p cLinespecs = []; this_path = fileparts(mfilename('fullpath')); cLinespecs = load([ this_path filesep 'clinespecs.mat']); cLinespecsNone = cLinespecs.cLinespecsNone; cLinespecs = cLinespecs.cLinespecs; ECG_w = []; bWrapper_provided = false; if( ischar(ECG) ) if( exist(ECG, 'file') ) ECG_w = ECGwrapper( 'recording_name', ECG); bWrapper_provided = true; if( isempty(heasig) ) heasig = ECG_w.ECG_header; end ECG = []; else error('plot_ecg_strip:FileNotFound', 'File %s not found\n', ECG ) end elseif( isa(ECG, 'ECGwrapper') ) % parse ECGwrapper object ECG_w = ECG; bWrapper_provided = true; if( isempty(heasig) ) heasig = ECG_w.ECG_header; end ECG = []; end QRS_locations_wrapper = []; if( isnumeric(QRS_locations) && ~isempty(QRS_locations) ) QRS_locations_names = {'user_provided'}; QRS_locations = {QRS_locations}; elseif( iscell(QRS_locations) && ~all(cellfun(@(a)(isempty(a)), QRS_locations)) ) cant_QRS_loc_provided = length(QRS_locations); QRS_locations_names = arrayfun(@(a)(sprintf('user_provided %d',a)), colvec(1:cant_QRS_loc_provided), 'UniformOutput', false); elseif( isa(QRS_locations, 'ECGwrapper') ) QRS_locations_wrapper = QRS_locations; QRS_locations_names = {}; QRS_locations = {}; elseif( bWrapper_provided ) QRS_locations_wrapper = ECG_w; QRS_locations_names = {}; QRS_locations = {}; end if( ~isempty(QRS_locations_wrapper) ) % parse ECGwrapper object % get the annotations from the wrapper. task_names = {'QRS_corrector' 'QRS_detection'; 'PPG_ABP_corrector' 'PPG_ABP_detector'}; for kk = 1:size(task_names,1) cached_filenames = QRS_locations_wrapper.GetCahchedFileName( task_names(kk,:) ); if( ~isempty(cached_filenames) ) aux_annotations = load(cached_filenames{1}); fnames = fieldnames(aux_annotations); aux_idx = find(cell2mat( cellfun(@(a)(~isempty(strfind(a, 'corrected_'))), fnames, 'UniformOutput', false))); if( isempty(aux_idx) ) % no corrected annotations if( isfield(aux_annotations, 'series_quality') ) [~, aux_idx] = max(aux_annotations.series_quality.ratios); QRS_locations_names = [QRS_locations_names aux_annotations.series_quality.AnnNames{aux_idx,1} ]; QRS_locations = [QRS_locations {aux_annotations.(aux_annotations.series_quality.AnnNames{aux_idx,1}).(aux_annotations.series_quality.AnnNames{aux_idx,2})}]; end else aux_val = length(aux_idx); for kk = 1:aux_val QRS_locations_names = [QRS_locations_names fnames(aux_idx(kk)) ]; QRS_locations = [QRS_locations {aux_annotations.(fnames{aux_idx(kk)}).time}]; end end end end % check for other annotations, like manual aux_annotations = QRS_locations_wrapper.ECG_annotations; if( ~isempty(aux_annotations) && isfield(aux_annotations, 'time') && ~isempty(aux_annotations.time) ) QRS_locations_names = [QRS_locations_names {'included'} ]; QRS_locations = [QRS_locations {aux_annotations.time}]; end end if( isa(annotations, 'ECGwrapper') ) annotations_wrapper = annotations; elseif( bWrapper_provided ) annotations_wrapper = ECG_w; else annotations_wrapper = []; end if( ~isempty(annotations_wrapper) ) % get the ECG delineation from the wrapper. annotations_wrapper.ECGtaskHandle = 'ECG_delineation'; cached_filenames = annotations_wrapper.GetCahchedFileName(); if( isempty(cached_filenames) ) if( ~isempty(annotations) ) cprintf('[1,0.5,0]', 'Delineation not found for this wrapper object.\n'); end annotations = []; else annotations = load(cached_filenames{1}); if( isfield(annotations, 'wavedet') ) if( isfield(annotations.wavedet, 'multilead') ) global_annotations = annotations.wavedet.multilead; end aux_fields = fieldnames(annotations.wavedet); [~, aux_idx ] = intersect( aux_fields, cellstr(strtrim(heasig.desc)) ); aux_struct = []; for ii = rowvec(aux_idx) aux_struct = [aux_struct annotations.wavedet.(aux_fields{ii}) ]; end annotations = aux_struct; end end end if( isa(global_annotations, 'ECGwrapper') ) global_annotations_wrapper = global_annotations; elseif( bWrapper_provided ) global_annotations_wrapper = ECG_w; else global_annotations_wrapper = []; end if( ~isempty(global_annotations_wrapper) ) % get the ECG delineation from the wrapper. global_annotations_wrapper.ECGtaskHandle = 'ECG_delineation'; cached_filenames = global_annotations_wrapper.GetCahchedFileName(); if( isempty(cached_filenames) ) if( ~isempty(global_annotations) ) cprintf('[1,0.5,0]', 'Multilead/global Delineation not found for this wrapper object.\n'); end global_annotations = []; else global_annotations = load(cached_filenames{1}); if( isfield(global_annotations, 'wavedet') ) if( isfield(global_annotations.wavedet, 'multilead') ) global_annotations = global_annotations.wavedet.multilead; else cprintf('[1,0.5,0]', 'Multilead/global Delineation not found for this wrapper object.\n'); global_annotations = []; end else cprintf('[1,0.5,0]', 'Multilead/global Delineation not found for this wrapper object.\n'); global_annotations = []; end end end if( isa(hb_labels, 'ECGwrapper') ) hb_labels_wrapper = hb_labels; elseif( bWrapper_provided ) hb_labels_wrapper = ECG_w; else if( isempty(hb_labels) ) hb_labels_idx = []; else if( all(isfield(hb_labels, cAnnotationsFieldNamesRequired )) ) hb_labels_idx = renumlab(hb_labels.anntyp, char(cBeatLabels) ); else hb_labels = []; hb_labels_idx = []; cprintf('[1,0.5,0]', disp_option_enumeration('Missing fields within structure:', cAnnotationsFieldNamesRequired ) ); end end hb_labels_wrapper = []; end if( ~isempty(hb_labels_wrapper) ) % get the heartbeat classification from the wrapper. hb_labels_wrapper.ECGtaskHandle = 'ECG_heartbeat_classifier'; cached_filenames = hb_labels_wrapper.GetCahchedFileName(); if( isempty(cached_filenames) ) if( ~isempty(hb_labels) ) cprintf('[1,0.5,0]', 'Heartbeat classification not found for this wrapper object.\n\n'); end hb_labels = []; hb_labels_idx = []; else hb_aux = load(cached_filenames{1}); QRS_locations_names = [QRS_locations_names {'hb_classifier'} ]; QRS_locations = [QRS_locations {hb_aux.time}]; hb_labels_idx = renumlab(hb_aux.anntyp, char(cBeatLabels) ); % % match heartbeat classification with global_annotations.qrs time % % reference. % % aux_time = sort(union(hb_aux.time, global_annotations.qrs)); % aux_labels.time = aux_time; % aux_val2 = nan(size(aux_time)); % aux_labels.anntyp = repmat('U',size(aux_time)); % [~,~, aux_idx2] = intersect(hb_aux.time, aux_time); % aux_labels.anntyp(aux_idx2) = hb_aux.anntyp; % hb_labels = aux_labels; % % [~,~, aux_idx2] = intersect(global_annotations.qrs, aux_time); % % for fn = rowvec(fieldnames(global_annotations)) % aux_val3 = aux_val2; % aux_val3(aux_idx2) = global_annotations.(fn{1}); % aux_anns.(fn{1}) = aux_val3; % end % aux_anns.qrs = aux_time; % % hb_labels_idx = renumlab(hb_labels.anntyp, char(cBeatLabels) ); end end if( (isempty(QRS_locations) || (iscell(QRS_locations) && all(cellfun(@(a)(isempty(a)), QRS_locations)))) && isempty(annotations) && isempty(global_annotations)) %no annotations at all QRS_start_idx = []; cant_qrs = []; if( isempty(start_time) ) start_time = 0; end if( isempty(end_time) ) end_time = realmax; end else % there are annotations if( isempty(start_time) && isempty(end_time) ) if( isempty(QRS_start_idx) ) QRS_start_idx = 1; end if( isempty(cant_qrs) ) cant_qrs = 10; end else if( isempty(start_time) ) if( isempty(end_time) ) start_time = 0; else start_time = end_time - 10; end end if( isempty(end_time) ) end_time = start_time + 10; end end end if( isempty(heasig) ) % warning('plot_ecg_strip:UnknownSamplingRate', 'Assuming sampling rate of 1000 Hz.') cprintf('[1,0.5,0]', 'Assuming sampling rate of 1000 Hz.\n') heasig.freq = 1000; end if( isempty(fig_hdl) ) fig_hdl = figure; maximize(fig_hdl) else % preserve visible property, specially for report generation, when it % is not important to show the figure. visible_prev = get(fig_hdl, 'Visible'); clf('reset') set(fig_hdl, 'Visible', visible_prev); end set(fig_hdl, 'ToolBar','none'); axes_hdl = gca; if( ~isempty(linked_hdl) ) if( isprop(linked_hdl, 'UserData') ) ud = get(linked_hdl, 'UserData'); if( isfield(ud, 'linked_hdl') && fig_hdl ~= linked_hdl ) % set the recyprocal link ud.linked_hdl = fig_hdl; set(linked_hdl, 'UserData', ud); clear ud else % not a proper link linked_hdl = []; end else % not a proper link linked_hdl = []; end end if( isfield(heasig, 'recname') ) set(fig_hdl, 'Name', heasig.recname); else set(fig_hdl, 'Name', 'Unknown ECG recording'); end % maximize(fig_hdl); if( isempty(ECG) ) % in case not all signals are ECG. if( bOnlyECG ) [ECG_signals_idx, heasig] = get_ECG_idx_from_header(heasig); cant_leads = length(ECG_signals_idx); else ECG_signals_idx = get_ECG_idx_from_header(heasig); end cant_samp = heasig.nsamp; if(cant_samp == 0) cant_samp = realmax; end % only for big recordings, can it be readed all together ? aux_MaxIOread = (MaxIOread * 1024^2) / heasig.nsig / 2; if( cant_samp > aux_MaxIOread && isempty(start_time) && isempty(end_time) ) cant_samp = aux_MaxIOread; cprintf('[1,0.5,0]','Recording too big, reading only %d samples. Use "Start_time" and "End_time" arguments to select other parts.\n', cant_samp); end cant_leads = heasig.nsig; else [cant_samp cant_leads ] = size(ECG); % assume more data than channels if( cant_leads > cant_samp ) ECG = ECG'; [cant_samp cant_leads] = size(ECG); end if( bOnlyECG ) % retain only ECG signals [ECG_signals_idx, heasig] = get_ECG_signals_idx_from_header(heasig); cant_leads = length(ECG_signals_idx); ECG = ECG(:,ECG_signals_idx); else cant_leads = heasig.nsig; ECG_signals_idx = 1:cant_leads; end end ECG_signals_idx = rowvec(ECG_signals_idx); if( ~isfield( heasig, 'nsig' ) ) heasig.nsig = cant_leads; end if( ~isfield( heasig, 'nsamp' ) ) heasig.nsamp = cant_samp; end if( ~isempty(global_annotations) ) QRS_locations_names = [QRS_locations_names {'global'} ]; QRS_locations = [QRS_locations {global_annotations.qrs}]; end if( isempty(QRS_locations) ) QRS_locations_names = {[]}; QRS_locations = {[]}; end cant_QRS_locations = cellfun(@(a)(length(a)), QRS_locations); if( ~isfield( heasig, 'adczero' ) ) % warning('plot_ecg_strip:UnknownADCzero', 'Assuming 0 ADC zero.') cprintf('[1,0.5,0]','Assuming 0 ADC zero.\n') heasig.adczero = zeros(cant_leads,1); end if( ~isfield( heasig, 'gain' ) ) % warning('plot_ecg_strip:UnknownADCgain', 'Assuming gain 1 uV/samp.') cprintf('[1,0.5,0]','Assuming gain 1 uV/samp.\n') heasig.gain = repmat(1, cant_leads,1); end % voltages to microvolts if( isfield( heasig, 'units' ) ) volt_idx = find(any(cell2mat(cellfun(@(b)(cell2mat(cellfun(@(a)(~isempty(strfind(a, b))), rowvec(upper(cellstr(heasig.units))), 'UniformOutput', false))), colvec(cUnitsVoltages), 'UniformOutput', false)),1)); % volt_idx = intersect( ECG_signals_idx, volt_idx); for ii = volt_idx switch( upper(strtrim(heasig.units(ii,:))) ) case {'NV', 'NANOVOLTS' , 'NANOVOLTIOS' } heasig.gain(ii) = heasig.gain(ii) * 1e3; case {'UV', 'MICROVOLTS' , 'MICROVOLTIOS' } heasig.gain(ii) = heasig.gain(ii) * 1; case {'MV', 'MILIVOLTS' , 'MILIVOLTIOS' } heasig.gain(ii) = heasig.gain(ii) / 1e3; case {'V', 'VOLTS' , 'VOLTIOS' } heasig.gain(ii) = heasig.gain(ii) / 1e6; otherwise cprintf('[1,0.5,0]', 'Unknown units, assuming uV.\n') end end if( length(volt_idx) ~= cant_leads ) end else % warning('plot_ecg_strip:UnknownADCunits', 'Assuming uV.') cprintf('[1,0.5,0]', 'Unknown units, assuming uV.\n') volt_idx = 1:cant_leads; heasig.units = repmat('uV', cant_leads,1); end aux_val = cellstr(heasig.units); aux_val(volt_idx) = repmat({'uV'}, length(volt_idx),1); heasig.units = char(aux_val); if( ~isfield( heasig, 'btime' ) ) heasig.btime = '00:00:00'; end % if( ~isfield( heasig, 'bdate' ) ) % heasig.bdate = '01/01/0001'; % end if(sum(heasig.btime == ':') == 2 ) formatIn = 'HH:MM:SS'; elseif(sum(heasig.btime == ':') == 3 ) formatIn = 'HH:MM:SS:FFF'; else formatIn = []; cprintf('[1,0.5,0]', 'Unknown base time format %s.\n', heasig.btime); end if( isempty(formatIn) ) base_time = 1; else base_time = (datenum( heasig.btime , formatIn) - datenum( '00:00:00' , 'HH:MM:SS')) * 60 * 60 * 24 * heasig.freq; % in samples end if( isempty(QRS_start_idx) ) aux_idx = max(1,round(start_time * heasig.freq)):min(cant_samp, round(end_time * heasig.freq)); if( length(aux_idx) < (min_cant_samp_seconds*heasig.freq) ) error('plot_ecg_strip:TimeFewSamples', 'Time range should be higher than %3.2f seconds', min_cant_samp_seconds ) end if(isempty(aux_idx)) error('plot_ecg_strip:TimeOutOfBounds', 'Time should be between 0 and %u (%s) seconds', round(cant_samp / heasig.freq), Seconds2HMS(cant_samp / heasig.freq) ) end else if( all(cellfun(@(a)(isempty(a)), QRS_locations)) ) % error('plot_ecg_strip:NoQRSlocations', 'We could not found any QRS complex locations to plot' ) QRS_start_idx = []; QRS_end_idx = []; aux_idx = 1:cant_samp; else QRS_start_idx = max(1, QRS_start_idx); QRS_end_idx = min(max(cant_QRS_locations), QRS_start_idx+cant_qrs); aux_idx = max(1, min(cellfun( @(a)( protected_index(a,QRS_start_idx)),QRS_locations))) : min(cant_samp, max(cellfun( @(a)(protected_index(a,QRS_end_idx)),QRS_locations) ) ); end end this_Xmargin = min(1*heasig.freq, round((aux_idx(end) - aux_idx(1))*0.1)); aux_idx = max(1,aux_idx(1)-this_Xmargin):min(cant_samp, aux_idx(end)+this_Xmargin); aux_idx_downsample = [ max(1,aux_idx(1)-1*heasig.freq) min(cant_samp, aux_idx(end)+1*heasig.freq) ]; kLinesAnns = 1; kBackColourAnns = 2; ann_graph_mode = kBackColourAnns; % Visualization Constants: % Time to start showing detail marks closeDetailSampSize = 10 * heasig.freq; mediumDetailSampSize = 30 * heasig.freq; farDetailSampSize = 60 * heasig.freq; kNoDetail = 0; kCloseDetailSL = 1; kMediumDetailSL = 2; kCloseDetailML = 3; kMediumDetailML = 4; kCloseDetailAll = 5; kMediumDetailAll = 6; switch(strDetail) case 'none' eDetailLevel = kNoDetail; case 'single-lead' eDetailLevel = kCloseDetailSL; case 'multilead' eDetailLevel = kCloseDetailML; otherwise eDetailLevel = kCloseDetailAll; end % Also decimate the signal for report generation. Avoid vectoized reports % too heavy prev_units = get(fig_hdl, 'Units'); set(fig_hdl, 'Units', 'centimeters'); % Downsample version: For efficient marks visualization and printing only paper_size = get(fig_hdl, 'Position'); nsamp_target = paper_size(3) * target_res; set(fig_hdl, 'Units', prev_units); down_factor = max(1, ceil( (aux_idx(end)-aux_idx(1)+1) / nsamp_target)); if( down_factor > 1 ) fir_coeffs = design_downsample_filter(down_factor); aux_idx_downsample_start = max(1, round((aux_idx(1) - aux_idx_downsample(1) + 1)/down_factor)); aux_idx_downsample_end = length( aux_idx(1):down_factor:aux_idx(end) ) + aux_idx_downsample_start - 1; if( isempty(ECG) ) ECGd = resample(double(ECG_w.read_signal(aux_idx_downsample(1), aux_idx_downsample(end))), 1, down_factor, fir_coeffs); ECGd = ECGd(aux_idx_downsample_start:aux_idx_downsample_end,:); ECG = ECG_w.read_signal(aux_idx(1), aux_idx(end)); [cant_samp, cant_leads] = size(ECG); %transform to real units ECG = bsxfun( @rdivide, bsxfun( @minus, double(ECG), rowvec(double(heasig.adczero)) ), rowvec(double(heasig.gain)) ) ; ECGd = bsxfun( @rdivide, bsxfun( @minus, ECGd, rowvec(double(heasig.adczero)) ), rowvec(double(heasig.gain)) ) ; else ECGd = resample( ECG(aux_idx_downsample(1):aux_idx_downsample(end),:), 1, down_factor, fir_coeffs ); ECGd = ECGd(aux_idx_downsample_start:aux_idx_downsample_end); ECGd = bsxfun( @rdivide, bsxfun( @minus, double(ECGd), rowvec(double(heasig.adczero)) ), rowvec(double(heasig.gain)) ) ; %transform to real units ECG = bsxfun( @rdivide, bsxfun( @minus, double(ECG(aux_idx,:)), rowvec(double(heasig.adczero)) ), rowvec(double(heasig.gain)) ) ; end else % no resampling needed. if( isempty(ECG) ) ECG = ECG_w.read_signal(aux_idx(1), aux_idx(end)); ECGd = ECG; [cant_samp, cant_leads] = size(ECG); %transform to real units ECG = bsxfun( @rdivide, bsxfun( @minus, double(ECG), rowvec(double(heasig.adczero)) ), rowvec(double(heasig.gain)) ) ; ECGd = bsxfun( @rdivide, bsxfun( @minus, double(ECGd), rowvec(double(heasig.adczero)) ), rowvec(double(heasig.gain)) ) ; else %transform to real units ECG = bsxfun( @rdivide, bsxfun( @minus, double(ECG(aux_idx,:)), rowvec(double(heasig.adczero)) ), rowvec(double(heasig.gain)) ) ; ECGd = ECG; cant_samp = length(aux_idx); end end if(bFilterECG) if( isempty(ECG_signals_idx) ) warning('plot_ecg_strip:NotFilter', 'No filter was applied since no ECG leads found.') else ECG(:,ECG_signals_idx) = BaselineWanderRemovalMedian( ECG(:,ECG_signals_idx), heasig.freq); end % filtro = bandpass_filter_design( heasig.freq ); % ECG(:,ECG_signals_idx) = filter(filtro, flipud() ); % ECG(:,ECG_signals_idx) = filter(filtro, flipud(ECG(:,ECG_signals_idx)) ); end % ECG ranges if( all(cellfun(@(a)(isempty(a)), QRS_locations)) ) % if( isempty(global_annotations) ) this_qrs_ploted = []; % else % this_qrs_ploted = find(global_annotations(1).qrs >= (aux_idx(1) + this_Xmargin) & global_annotations(1).qrs <= (aux_idx(end) - this_Xmargin) ); % QRS_locations = {global_annotations(1).qrs}; % end this_qrs_ploted = {this_qrs_ploted}; else this_qrs_ploted = cellfun( @(a)(find(a >= aux_idx(1) & a <= aux_idx(end) )), QRS_locations, 'UniformOutput', false ); if( ~isempty(QRS_start_idx) && ~isempty(cant_qrs) ) this_qrs_ploted = cellfun( @(a,b)(intersect(a, QRS_start_idx:min(b, QRS_start_idx+cant_qrs))), this_qrs_ploted, num2cell(cant_QRS_locations), 'UniformOutput', false ); end end [ecg_range, ecg_min, ecg_max, ecg_median] = CalcECG_range(ECG); if( isempty(gains) ) gains = ones( cant_leads, 1); bAux = ecg_range ~= 0; gains(bAux) = max(ecg_range(bAux))./ecg_range(bAux); else % by the moment one gain for all if(length(gains) == 1 ) gains = repmat( gains, cant_leads, 1); elseif(length(gains) ~= cant_leads ) error('plot_ecg_strip:BadLeadGain', ['Lead gain must be a single numeric value or a ' num2str(cant_leads) ' x 1 vector'] ) end end % ensure good visibility if( isempty(lead_offset) ) if( cant_leads > 1 ) lead_offset = 1.1*( (ecg_min(1:end-1) - ecg_median(1:end-1)) .* gains(1:end-1) - ( ecg_max(2:end) - ecg_median(2:end) ) .* gains(2:end) ); offsets = ecg_median .* gains + abs([0; cumsum(lead_offset)]) ; else lead_offset = 0; offsets = 0; end else if(length(lead_offset) == 1 ) lead_offset = repmat( lead_offset, cant_leads, 1); elseif(length(lead_offset) ~= cant_leads ) error('plot_ecg_strip:BadLeadOffset', ['Lead offset must be a single numeric value or a ' num2str(cant_leads) ' x 1 vector'] ) end end % signal margins, relative to the total height and width plot_left_margin_width = 0.03; plot_rigth_margin_width = 0.035; plot_top_margin_width = 0.1; plot_bottom_margin_width = 0.1; [plotYrange, plotYmin, plotYmax] = CalcPlotYlimits(ecg_min, ecg_max, gains, offsets); plotXmin = aux_idx(1); plotXmax = aux_idx(end); plotXrange = plotXmax - plotXmin; plotXmin = plotXmin - plot_left_margin_width * plotXrange; plotXmax = plotXmax + plot_rigth_margin_width * plotXrange; % Set the colormap ColorOrder = my_colormap( 12 ); ColorOrder = repmat(ColorOrder, ceil(heasig.nsig/size(ColorOrder,1)), 1 ); set(axes_hdl, 'ColorOrder', ColorOrder); hold(axes_hdl, 'on') %plot ECG scaled and translated ECG_hdl = plot(axes_hdl, aux_idx, bsxfun( @minus, bsxfun( @times, ECG, rowvec(gains)), rowvec(offsets) ), 'LineWidth', 1.3 ); % ylabel(['ECG (' heasig.units(1,:) ')' ]); % xlabel(['Time (s) relative to start of segment' ]); set(axes_hdl, 'Box', 'off' ); set(axes_hdl, 'Xtick', [] ); set(axes_hdl, 'Ytick', [] ); set(axes_hdl, 'Xcolor', [1 1 1] ); set(axes_hdl, 'Ycolor', [1 1 1] ); % set(axes_hdl, 'Visible', 'off' ); % set(axes_hdl, 'Visible', 'off' ); % set(axes_hdl, 'Visible', 'off' ); set(axes_hdl, 'Position', [ 0.005 0.01 0.99 0.98 ] ); % legend(ECG_hdl, cellstr(heasig.desc) ); % user data initialized user_data.linked_hdl = linked_hdl; % global variables report_format = 'pdf'; report_format_idx = find(strcmpi(report_format,cKnownReportFormats),1); lead_selected_idx = 1:cant_leads; bPaperModeOn = false; major_tick_values_time = round([0.5 1 2 5 10 30 60]*heasig.freq); % seconds major_tick_values_time = unique([major_tick_values_time major_tick_values_time*60 ]); major_tick_values_voltage = [ [1 2 5 ] [1 2 5 ] * 10^1 [1 2 5 ] * 10^1 [1 2 5 ] * 10^2 [1 2 5] * 10^3 [1 2 5] * 10^4 [1 2 5] * 10^5 [1 2 5] * 10^6 ]; % seconds paperModeHdl = {}; topLevelHdl = {}; ECGd_hdl = []; bPlotScales = false; PrevStateWindowButtonMotionFcn = []; UserChnageViewHdls = {}; %store user data. start_sample = aux_idx(1); end_sample = aux_idx(end); % qrs_ploted = cellfun( @(a,b)(a(b)), QRS_locations, this_qrs_ploted, 'UniformOutput', false ); qrs_ploted = QRS_locations; qrs_ploted_names = QRS_locations_names; prev_Xrange = plotXmax - plotXmin; prev_Yrange = plotYmax - plotYmin; original_plot_lims = [ plotXmin plotXmax plotYmin plotYmax ]; plotYtimeAxis = []; plotYindexesAxis = []; PwaveHdls = {}; TwaveHdls = {}; QRScplxHdls = {}; PwaveGlblHdls = cell(heasig.nsig,1); TwaveGlblHdls = cell(heasig.nsig,1); QRScplxGlblHdls = cell(heasig.nsig,1); QRSfpHdls = {}; QRSfpFarHdls = {}; titleExtent = nan; timeAxisTop = nan; startSignalX = nan; endSignalX = nan; xTextOffset = nan; yTextOffset = nan; YlabelLeftPosition = nan; scroll_mode = 'zoom'; gain_offset_mode = 'gain'; % 1V maximum min_gain = 1e-10 / plotYrange; % 1 nV maximum max_gain = 1e15 / plotYrange; my_timer = timer('TimerFcn',@timer_fcn, 'StopFcn', @timer_stop_fcn , 'StartDelay', 10); %% variables and flags from Dragzoom fNoZoom = false; hAxes = axes_hdl; % get info about all axes and create axes info struct mAxesInfo = GetAxesInfo(); fIsSelectedCurrentAxes = true; % Drag Options mDragKeysX = 'normal'; % 'normal', 'reverse' mDragKeysY = 'normal'; % 'normal', 'reverse' mDragShiftStep = 3; % step dragging on keys mDragShiftStepInc = 1; % increase speed dragging on keys mDragStartX = []; mDragStartY = []; mDragShiftStepInc = []; % Zoom Options mZoomScroll = 'normal'; % 'normal', 'reverse' mZoomMinPow = 0; % min zoom percent 10 ^ mZoomMinPow mZoomMaxPow = 5; % max zoom perzent 10 ^ mZoomMaxPow mZoomNum = 51; % count steps of log zoom grid mZoomExtendNum = 301; % count steps of log grid zoom extend for 2D mZoomKeysNum = 181; % count steps of log grid zoom for keys for 2D % Rubber Band Options mRbEdgeColor = 'k'; % rubber band edge color mRbFaceColor = 'none'; % rubber band face color mRbFaceAlpha = 1; % rubber band face alpha (transparency) mRubberBand = []; % Magnifier Options mMgSize = 100; % default size of magnifier (pixels) mMgMinSize = 50; % min size of magnifier mMgMaxSize = 200; % max size of magnifier mMgZoom = 2; % default zoom on magnifier mMgMinZoom = 1; % min zoom on magnifier mMgMaxZoom = 100; % max zoom on magnifier mMgLinesWidth = 1; % lines width on magnifier mMgShadow = 0.95; % shadow area without magnifier mMgSizeStep = 15; % step change in the magnifier size mMgZoomStep = 1.2; % step change in the magnifier zoom mMagnifier = []; mMgDirection = []; mDefaultZoomGrid = []; mDefaultZoomSteps = []; mZoomGrid = []; mZoomSteps = []; mZoomIndexX = []; mZoomIndexY = []; mZoom3DStartX = []; mZoom3DStartY = []; mZoom3DBindX = []; mZoom3DBindY = []; mDefaultXLim = original_plot_lims(1:2); mDefaultYLim = original_plot_lims(3:4); mPointerCross = []; % flags % flags bgColor = [251 248 230]/255; fIsSelectedCurrentAxes = true; fIsDragAllowed = false; fIsRubberBandOn = false; fIsPointerCross = false; fIsAxesGrid = false; fIsEnableZoomX = false; fIsEnableZoomY = false; fIsMagnifierOn = false; fIsEnableControl = true; fIsMouseOnLegend = false; SetDefaultZoomGrid(); mDragSaveShiftStep = mDragShiftStep; %% single lead annotations if( isempty(annotations) ) annotations = []; this_annotation = []; else for ii = 1:length(annotations) this_annotation = annotations(ii); aux_struct = []; if( isempty(this_qrs_ploted) ) this_qrs_ploted = find(this_annotation.qrs >= (aux_idx(1) + this_Xmargin) & this_annotation.qrs <= (aux_idx(end) - this_Xmargin) ); if( ~isempty(this_qrs_ploted) ) for field_name = cAnnotationFieldNames aux_val = this_annotation.(field_name{1}); if( isempty(aux_val) ) aux_struct.(field_name{1}) = nan(length(this_qrs_ploted),1); else aux_struct.(field_name{1}) = aux_val(this_qrs_ploted); end end annotations(ii) = aux_struct; end end end end %% multilead annotations if( isempty(global_annotations) ) global_annotations = []; else if( isempty(this_qrs_ploted) ) this_qrs_ploted = find(global_annotations.qrs >= (aux_idx(1) + this_Xmargin) & global_annotations.qrs <= (aux_idx(end) - this_Xmargin) ); if( isempty(this_qrs_ploted) ) global_annotations = []; this_qrs_ploted = []; else for field_name = cAnnotationFieldNames if( isfield(global_annotations, field_name{1} ) ) aux_val = global_annotations.(field_name{1}); aux_struct.(field_name{1}) = aux_val(this_qrs_ploted); end end global_annotations = aux_struct; this_qrs_ploted = {global_annotations.qrs}; end end end set(axes_hdl, 'Xlim', [plotXmin plotXmax]); if( plotYmin < plotYmax ) set(axes_hdl, 'Ylim', [plotYmin plotYmax]); end % this_qrs_ploted = cellfun( @(a)( find(a >= plotXmin & a <= plotXmax) ), qrs_ploted, 'UniformOutput', false ); if( isfield(heasig, 'recname') ) recname = heasig.recname; else recname = 'no_name'; end if( isempty(strTitle)) strTitle = [ 'Recording ' recname ' - ' Seconds2HMS( (aux_idx(1) + base_time ) / heasig.freq) ' : ' Seconds2HMS( (aux_idx(end) + base_time ) / heasig.freq) ]; else strTitle = strTitle; end update_axis_plot_ecg_strip( this_qrs_ploted ); uistack(ECG_hdl, 'top'); hold(axes_hdl, 'off'); if(bPrettyPrint) PaperModeOn() end % user data stored in figure. set(fig_hdl, 'UserData', user_data); % %Zoom and Pan handles % zoom_hdl = zoom(fig_hdl); % zoom reset; % set(zoom_hdl, 'ActionPostCallback', @(a,b)( UserChangeView(a,b, 'zoom')) ); % % pan_hdl = pan(fig_hdl); % set(pan_hdl, 'ActionPostCallback', @(a,b)( UserChangeView(a,b, 'pan')) ); % % set(fig_hdl,'WindowScrollWheelFcn',@ScrollWheel); % % set(fig_hdl,'WindowKeyPressFcn',@KeyPress); % addlistener(fig_hdl,'WindowKeyPressEvent', @KeyPress); set(fig_hdl,'CloseRequestFcn',@my_closefcn) set(fig_hdl, ... 'WindowButtonDownFcn', {@WindowButtonDownCallback2D}, ... 'WindowButtonUpFcn', {@WindowButtonUpCallback2D}, ... 'WindowScrollWheelFcn', {@WindowScrollWheelFcn2D}, ... 'WindowKeyPressFcn', {@WindowKeyPressCallback2D}, ... 'CloseRequestFcn', {@my_closefcn}, ... 'WindowKeyReleaseFcn', {@WindowKeyReleaseCallback2D}); if(~bPrettyPrint) disp_help() end if( nargout > 1 ) returned_handles = ECG_hdl; else clear returned_handles end %========================================================================== %-------------------------------------------------------------------------- function update_axis_plot_ecg_strip( qrs2plot ) % Some useful constans xTextOffset = 0.005*plotXrange; yTextOffset = 0.01*plotYrange; if( bPaperModeOn ) startSignalX = (plotXmin + xTextOffset); endSignalX = (plotXmax - xTextOffset); width_legend = 0.07*plotXrange; height_legend = 0.03*plotYrange; left_legend = plotXmax - width_legend - 4*xTextOffset; bottom_legend = plotYmax - height_legend - 3*yTextOffset; titleYposition = plotYmax - 4*yTextOffset; else startSignalX = (plotXmin + 5*xTextOffset); endSignalX = (plotXmax - 3*xTextOffset); width_legend = 0.07*plotXrange; height_legend = 0.03*plotYrange; left_legend = plotXmax - width_legend - 2*xTextOffset; bottom_legend = plotYmax - height_legend - 1*yTextOffset; titleYposition = plotYmax - 2*yTextOffset; end timeAxisTop = plotYmin + (0.06 * plotYrange); plotYtimeAxis = plotYmin + (0.04 * plotYrange); %% initialization topLevelHdl = []; %% Lead reference: % Plot leads label or order number close to the signals. % Check overlapps among signal labels. % text label of each axis if( bPaperModeOn ) YlabelLeftPosition = startSignalX + 6*xTextOffset; else YlabelLeftPosition = startSignalX - 3*xTextOffset; end if( isfield(heasig, 'desc') ) if( bPaperModeOn ) str_aux = cellstr(strtrim(heasig.desc)); else str_aux = strcat(cellstr(strtrim(heasig.desc)), repmat({' ('},heasig.nsig,1), cellstr(heasig.units), repmat({')'},heasig.nsig,1) ); end else str_aux = cellstr(num2str(colvec(1:cant_leads))); end % signal labels if( bPaperModeOn ) aux_hdl = cellfun( @(a,b,ii)( text( YlabelLeftPosition, b, a, 'FontSize', 8, 'HorizontalAlignment', 'left', 'Interpreter', 'none', 'BackgroundColor', [1 1 1], 'EdgeColor', ColorOrder(ii,:), 'Margin', 3 ) ), str_aux, num2cell(-offsets + ecg_max .* gains),num2cell((1:heasig.nsig)'), 'UniformOutput', false); else % white patch to cover the background UserChnageViewHdls = [UserChnageViewHdls; {patch('Faces', [1 2 3 4], 'Vertices', [[0; 0; 3*xTextOffset; 3*xTextOffset ] + plotXmin [plotYmin; plotYmax - plotYmin; plotYmax - plotYmin;plotYmin ] ], 'FaceColor', [1 1 1], 'EdgeColor', [1 1 1] )} ]; aux_hdl = cellfun( @(a,b,ii)( text( YlabelLeftPosition, -b + (ecg_min(ii) + ecg_range(ii)/2) * gains(ii), a, 'FontSize', 8, 'HorizontalAlignment', 'center', 'Rotation', 90, 'Interpreter', 'none', 'BackgroundColor', [1 1 1], 'EdgeColor', ColorOrder(ii,:), 'Margin', 3 ) ), str_aux, num2cell(offsets),num2cell((1:heasig.nsig)'), 'UniformOutput', false); end UserChnageViewHdls = [UserChnageViewHdls; colvec(aux_hdl)]; % r2014 change handle to objects. aux_hdll = aux_hdl{1}; for iii = 2:length(aux_hdl) aux_hdll(iii) = aux_hdl{iii}; end aux_hdl = aux_hdll; if( length(aux_hdl) > 1 ) aux_extent = cell2mat(get(aux_hdl, 'Extent')); else aux_extent = get(aux_hdl, 'Extent'); end if( size(aux_extent,1) > 1 && sum(aux_extent(:,4)) > plotYrange ) % not enough room for all descriptions -> overlap % fix first and last, and share the rest of space aux_val = cell2mat(get(aux_hdl, 'Position')); set(aux_hdl(1), 'Position', [aux_val(1,1) aux_val(1,2) - ( aux_extent(1,2) - (plotYmax - aux_extent(1,4))) ] ) set(aux_hdl(end), 'Position', [aux_val(end,1) aux_val(end,2) - ( aux_extent(end,2) - (plotYmin + aux_extent(end,4))) ] ) if( size(aux_extent,1) > 2 ) aux_val = cell2mat(get(aux_hdl, 'Position')); if( length(aux_hdl) > 1 ) aux_extent = cell2mat(get(aux_hdl, 'Extent')); else aux_extent = get(aux_hdl, 'Extent'); end aux_space = aux_extent(1,2) - (aux_extent(end,2)+aux_extent(end,4)); each_weight = aux_extent(2:end-1,4); each_weight = each_weight ./ sum(each_weight); each_space = aux_space .* each_weight; arrayfun( @(ii,a)( set(aux_hdl(ii), 'Position', [aux_val(ii,1) aux_val(ii,2) - ( aux_extent(ii,2) - (aux_extent(1,2) - a) ) ]) ), 1+(1:length(each_space))' , cumsum(each_space) ) end end % save this handles to top them later topLevelHdl = [topLevelHdl; colvec(UserChnageViewHdls(end-cant_leads+1:end))]; if( ~bPaperModeOn ) % vertical axes for each signal %check overlapp if( length(offsets) > 1 ) aux_solap = [ (-offsets(1:end-1) - colvec(ecg_range(1:end-1))/2) (-offsets(2:end) + colvec(ecg_range(2:end))/2) ]; aux_solap = aux_solap(:,1) < aux_solap(:,2); aux_solap = [ aux_solap(1); aux_solap]; aux_jitter = aux_solap .* colvec(linspace(0,2,cant_leads) * xTextOffset); else aux_jitter = 0; end %build vertical axis aux_Xaxis = [ -xTextOffset 0 0 -xTextOffset] + startSignalX; aux_Yaxis = [ ecg_max ecg_max ecg_min ecg_min ]'; UserChnageViewHdls = [UserChnageViewHdls; colvec(arrayfun(@(a,b)(plot(axes_hdl, a, b, 'LineWidth', 0.25 )), bsxfun(@plus, repmat(colvec(aux_Xaxis),1,cant_leads), rowvec(aux_jitter)), bsxfun( @minus, bsxfun( @times, aux_Yaxis, rowvec(gains) ), rowvec(offsets)), 'UniformOutput', false )) ]; end % user_data.UserChnageViewHdls = [user_data.UserChnageViewHdls; text( sample_reference + xTextOffset, plotYmin + (0.02 * plotYrange), 'Index #' , 'FontSize', 8)]; %% Time reference: % Start/End of the excerpt. % QRS location/indexes if were provided if( plotXrange < mediumDetailSampSize ) sample_reference = ceil( (plotXmin + min( heasig.freq, plot_left_margin_width*plotXrange )) * 1/heasig.freq) * heasig.freq; bAux = any( colvec(cellfun( @(a)(isempty(a)), qrs2plot ) )); if( bAux ) if( sample_reference < plotXmin || sample_reference > plotXmin + plot_left_margin_width*plotXrange ) sample_reference = ceil(plotXmin + min( heasig.freq, plot_left_margin_width*plotXrange)); end else aux_val = min( cellfun( @(a,b)(a(b(1))), qrs_ploted, qrs2plot ) ); if( sample_reference >= aux_val ) sample_reference = ceil(plotXmin + min( heasig.freq, plot_left_margin_width*plotXrange)); end end sample_last_reference = floor( (plotXmax - min( heasig.freq, plot_rigth_margin_width*plotXrange)) * 1/heasig.freq) * heasig.freq ; bAux = any( colvec(cellfun( @(a)(isempty(a)), qrs2plot ) )); if( bAux ) if( sample_last_reference > plotXmax || sample_last_reference < plotXmax - min( heasig.freq,plot_rigth_margin_width*plotXrange) ) sample_last_reference = floor(plotXmax - min( heasig.freq, plot_rigth_margin_width*plotXrange)); end else aux_val = max( cellfun( @(a,b)(a(b(end))), qrs_ploted, qrs2plot ) ); if( sample_last_reference <= aux_val ) sample_last_reference = floor(plotXmax - min( heasig.freq,plot_rigth_margin_width*plotXrange)); end end if( ~bPaperModeOn ) %obsolete: try to handle visibility with uistack % % plot a white patch as background % UserChnageViewHdls = [UserChnageViewHdls; patch('Faces', [1 2 3 4], 'Vertices', [[0; plotXrange; plotXrange; 0 ] + plotXmin [0; 0; timeAxisTop; timeAxisTop ] + plotYmin ], 'FaceColor', [1 1 1], 'EdgeColor', [1 1 1] ) ]; aux_hdl = text( sample_reference - xTextOffset, plotYmin + (0.02 * plotYrange), ... 'Index #' , ... 'FontSize', 8, ... 'HorizontalAlignment', 'right' ... ); aux_extent = get(aux_hdl, 'Extent'); if(aux_extent(1) < plotXmin ) aux_position = get(aux_hdl, 'Position'); set(aux_hdl, 'Position', [ plotXmin+aux_extent(3) aux_position(2:3)]); end UserChnageViewHdls = [UserChnageViewHdls; {aux_hdl}]; % time reference % start - end of ECG excerpt aux_hdl = text( sample_reference - xTextOffset, plotYtimeAxis, Seconds2HMS( (sample_reference + base_time ) / heasig.freq, 3), 'FontName', 'Arial', 'FontSize', 8, 'HorizontalAlignment', 'right'); aux_extent = get(aux_hdl, 'Extent'); if(aux_extent(1) < plotXmin ) aux_position = get(aux_hdl, 'Position'); set(aux_hdl, 'Position', [ plotXmin+aux_extent(3) aux_position(2:3)]); end UserChnageViewHdls = [UserChnageViewHdls; {aux_hdl}]; aux_hdl = text( sample_last_reference + xTextOffset, plotYtimeAxis, Seconds2HMS((sample_last_reference + base_time ) / heasig.freq, 3), 'FontName', 'Arial', 'FontSize', 8); aux_extent = get(aux_hdl, 'Extent'); if( aux_extent(1)+aux_extent(3) > plotXmax ) aux_position = get(aux_hdl, 'Position'); set(aux_hdl, 'Position', [ plotXmax - aux_extent(3) aux_position(2:3)]); end UserChnageViewHdls = [UserChnageViewHdls; {aux_hdl}]; aux_seq = cellfun( @(a,b)(limit_sequence(a(b),[sample_reference sample_last_reference])), qrs_ploted, qrs2plot, 'UniformOutput', false ); % plot indexes plotYindexesAxis = plotYmin + (0.02 * plotYrange); cHorAllignment = {'Left' 'Right' 'Center' }; laux_seq = length(aux_seq); aux_hallign = repmat(cHorAllignment, 1, ceil(laux_seq/3) ); aux_hallign = aux_hallign(1:laux_seq); linespec_color = rowvec(cellfun( @(a)(a{4}), cLinespecs(1:length(aux_seq)), 'UniformOutput', false )); aux_vall = cellfun( @(a,b,c,e,f,g)( colvec(arrayfun( @(d)(text( a(b(d)), plotYindexesAxis + e, num2str(b(d)) , 'FontSize', 8, 'HorizontalAlignment', f, 'Color', g)), c, 'UniformOutput', false)) ), qrs_ploted, qrs2plot, aux_seq, num2cell(0.02 * plotYrange * linspace(-0.1, 0.1, length(qrs_ploted))), aux_hallign, linespec_color, 'UniformOutput', false ); % r2014 change handle to objects. laux_seq = aux_vall{1}; for iii = 2:length(aux_vall) laux_seq = [laux_seq; colvec(aux_vall{iii})]; end UserChnageViewHdls = [ ... UserChnageViewHdls; laux_seq ]; % plot times relative to the reference aux_vall = colvec(cellfun( @(a,b,c,e,f,g)( colvec(arrayfun( @(d)(text( a(b(d)), plotYtimeAxis + e, Seconds2HMS( (a(b(d)) - sample_reference + 1)/heasig.freq, 3 ) , 'FontSize', 8, 'HorizontalAlignment', f, 'Color', g )), c, 'UniformOutput', false)) ), qrs_ploted, qrs2plot, aux_seq, num2cell(0.02 * plotYrange * linspace(-0.1, 0.1, length(qrs_ploted))), aux_hallign, linespec_color, 'UniformOutput', false )); % r2014 change handle to objects. laux_seq = aux_vall{1}; for iii = 2:length(aux_vall) laux_seq = [laux_seq; colvec(aux_vall{iii})]; end UserChnageViewHdls = [ ... UserChnageViewHdls; laux_seq ]; % black tips at the beginning/end UserChnageViewHdls = [UserChnageViewHdls; colvec(arrayfun(@(a,b)(plot(axes_hdl, a, b , 'k-', 'LineWidth', 0.25 )), [sample_reference sample_last_reference; sample_reference sample_last_reference], [ repmat(plotYmin + (0.01 * plotYrange), 1, 2); repmat(plotYmin + (0.06 * plotYrange), 1,2) ], 'UniformOutput', false)) ]; end else % for far zoom views sample_reference = ceil( (plotXmin + plot_left_margin_width*plotXrange ) * 1/heasig.freq) * heasig.freq; sample_last_reference = floor( (plotXmax - plot_rigth_margin_width*plotXrange) * 1/heasig.freq) * heasig.freq ; if( ~bPaperModeOn ) aux_seq = linspace( sample_reference, sample_last_reference, 6); aux_seq = round( aux_seq/heasig.freq ) * heasig.freq ; plotYtimeAxis = plotYmin + (0.02 * plotYrange); UserChnageViewHdls = [ UserChnageViewHdls; {text( aux_seq(1) - xTextOffset, plotYtimeAxis, Seconds2HMS( (aux_seq(1) + base_time )/heasig.freq ) , 'FontSize', 8, 'HorizontalAlignment', 'right') } ]; for jj = rowvec(aux_seq(2:end-1)) UserChnageViewHdls = [ UserChnageViewHdls; {text( jj, plotYtimeAxis, Seconds2HMS( jj/heasig.freq ) , 'FontSize', 8, 'HorizontalAlignment', 'center')}]; end UserChnageViewHdls = [ UserChnageViewHdls; {text( aux_seq(end) + xTextOffset, plotYtimeAxis, Seconds2HMS( (aux_seq(end) + base_time )/heasig.freq ) , 'FontSize', 8, 'HorizontalAlignment', 'left')}]; % black tips at the beginning/end UserChnageViewHdls = [UserChnageViewHdls; colvec(arrayfun(@(a,b)(plot(axes_hdl, a, b, 'k-', 'LineWidth', 0.25 )), [sample_reference sample_last_reference; sample_reference sample_last_reference], [ repmat(plotYmin + (0.01 * plotYrange), 1, 2); repmat(plotYmin + (0.06 * plotYrange), 1,2) ], 'UniformOutput', false )) ]; end end bWaveLegendPlotted = false; bHBclassLegendPlotted = false; %% multilead or global annotations if( isempty(global_annotations) ) aux_seq = cellfun( @(a)({1:length(a)}), qrs2plot ); % for far zoom views bAux = eDetailLevel ~= kNoDetail && ( ( eDetailLevel == kMediumDetailML || eDetailLevel == kMediumDetailAll ) && ( plotXrange > mediumDetailSampSize && plotXrange < farDetailSampSize ) ); if( isempty(QRSfpFarHdls) ) if( bAux ) QRSfpFarHdls = cellfun( @(a,b,c,d)( plot(axes_hdl, rowvec(a(b(c))), repmat(titleYposition - (d* 0.1 * plotYrange), 1,length(c)) )), qrs_ploted, qrs2plot, aux_seq, num2cell(linspace(0.9, 1.1, length(aux_seq))), 'UniformOutput', false ); cellfun( @(a,b)(set_a_linespec(a, b)), QRSfpFarHdls, cLinespecsNone(1:length(QRSfpFarHdls)) ); end else if( bAux ) str_aux = 'on'; else str_aux = 'off'; end cellfun(@(a)(set(a, 'Visible', str_aux )), QRSfpFarHdls); end % for closer zoom views bAux = eDetailLevel ~= kNoDetail && ( (eDetailLevel == kCloseDetailML || eDetailLevel == kCloseDetailAll ) && plotXrange < mediumDetailSampSize); if( isempty(QRSfpHdls) ) bAux2 = any( colvec(cellfun( @(a,b,c)(~isempty(a(b(c)))), qrs_ploted, qrs2plot, aux_seq ) )); if( bAux && bAux2 ) QRSfpHdls = colvec(cellfun( @(a,b,c,d)( plot(axes_hdl, repmat(rowvec(a(b(c))), 2,1), [ repmat(plotYmin + d*(0.06 * plotYrange), 1,length(c)); repmat(titleYposition - d*(0.1 * plotYrange), 1,length(c)) ] ) ), qrs_ploted, qrs2plot, aux_seq, num2cell(linspace(0.9, 1.1, length(aux_seq))), 'UniformOutput', false)); cellfun( @(a,b)(set_a_linespec(a, b)), QRSfpHdls, cLinespecs(1:length(QRSfpHdls)) ); cellfun(@(a)(set(a, 'LineWidth', 0.25)), QRSfpHdls); end else if( bAux ) str_aux = 'on'; else str_aux = 'off'; end cellfun(@(a)(set(a, 'Visible', str_aux )), QRSfpHdls); end else bAux = eDetailLevel ~= kNoDetail && ( (eDetailLevel == kCloseDetailML || eDetailLevel == kCloseDetailAll ) && plotXrange <= closeDetailSampSize ); if( bAux ) bWaveLegendPlotted = true; % Waves frame legend_hdl = {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])}; legend_hdl = [legend_hdl; {text( left_legend + width_legend/4, bottom_legend + height_legend/2, 'P', 'FontSize', 8, 'HorizontalAlignment', 'center', 'BackgroundColor', color_Pwave_global)}]; legend_hdl = [legend_hdl; {text( left_legend + width_legend/2, bottom_legend + height_legend/2, 'QRS', 'FontSize', 8, 'HorizontalAlignment', 'center', 'BackgroundColor', color_QRScomplex_global )}]; legend_hdl = [legend_hdl; {text( left_legend + width_legend*3/4, bottom_legend + height_legend/2, 'T', 'FontSize', 8, 'HorizontalAlignment', 'center', 'BackgroundColor', color_Twave_global )}]; UserChnageViewHdls = [UserChnageViewHdls; legend_hdl]; end aux_seq = cellfun( @(a)({1:length(a)}), qrs2plot ); % when annotations are provided % P wave bAux = eDetailLevel ~= kNoDetail && ( ( eDetailLevel == kCloseDetailML || eDetailLevel == kCloseDetailAll ) && plotXrange <= closeDetailSampSize ); if( isempty(PwaveHdls) ) if( bAux ) % PwaveHdls = [ PwaveHdls; PlotGlobalWaveMarks({'Pon' 'P' 'Poff'}, [ plotYmin + (0.1*plotYrange) plotYmax - (0.05 * plotYrange) ], color_Pwave_global )]; for jj = ECG_signals_idx PwaveHdls = [ PwaveHdls; PlotWaveMarks(global_annotations, {'Pon' 'P' 'Poff'}, jj, 0.5*yTextOffset, PwaveColor*0.95 ) ]; end end else if( bAux ) str_aux = 'on'; else str_aux = 'off'; end cellfun( @(a)(set(a, 'Visible', str_aux )), PwaveHdls); end % QRS complex % for far zoom views bAux = eDetailLevel ~= kNoDetail && ( ( eDetailLevel == kMediumDetailML || eDetailLevel == kMediumDetailAll ) && plotXrange > mediumDetailSampSize && plotXrange < farDetailSampSize ); if( isempty(QRSfpFarHdls) ) if( bAux ) QRSfpFarHdls = colvec(cellfun( @(a,b,c)( plot(axes_hdl, rowvec(a(b(c))), repmat(titleYposition - (0.1 * plotYrange), 1,length(c)) )), qrs_ploted, qrs2plot, aux_seq, 'UniformOutput', false )); % set_rand_linespec(QRSfpFarHdls, 'v', 'none', [], 6 ); cellfun( @(a,b)(set_a_linespec(a, b)), QRSfpFarHdls, cLinespecsNone(1:length(QRSfpHdls)) ); end else if( bAux ) str_aux = 'on'; else str_aux = 'off'; end cellfun(@(a)(set(a, 'Visible', str_aux )), QRSfpFarHdls); end bAux = eDetailLevel ~= kNoDetail && ( ( eDetailLevel == kCloseDetailML || eDetailLevel == kCloseDetailAll ) && plotXrange < mediumDetailSampSize); if( isempty(QRSfpHdls) ) bAux2 = any( colvec(cellfun( @(a,b,c)(~isempty(a(b(c)))), qrs_ploted, qrs2plot, aux_seq )) ); if( bAux && bAux2 ) QRSfpHdls = colvec(cellfun( @(a,b,c)( plot(axes_hdl, repmat(rowvec(a(b(c))), 2,1), [ repmat(plotYmin + (0.06 * plotYrange), 1,length(c)); repmat(titleYposition - (0.1 * plotYrange), 1,length(c)) ] ) ), qrs_ploted, qrs2plot, aux_seq , 'UniformOutput', false)); % aux_val = cellfun( @(a)(set_rand_linespec(a(1), '^', ':', [], 5 )), QRSfpHdls, 'UniformOutput', false); cellfun( @(a,b)(set_a_linespec(a, b)), QRSfpHdls, cLinespecs(1:length(QRSfpHdls)) ); cellfun( @(a)(set(a, 'LineWidth', 0.25)), QRSfpHdls); hb_classifier_idx = find(strcmpi(qrs_ploted_names, 'hb_classifier')); if( ~isempty(hb_classifier_idx) ) aux_hb_classifier_val = colvec(qrs_ploted{hb_classifier_idx}); aux_seq_classifier = aux_seq{hb_classifier_idx}; qrs2plot_classifier = qrs2plot{hb_classifier_idx}; QRSfpHdls = [ QRSfpHdls; ... arrayfun( @(b)( ... text( aux_hb_classifier_val(b), titleYposition - (0.08 * plotYrange), cBeatLabels{hb_labels_idx(b)}, ... 'FontSize', 8, ... 'VerticalAlignment', 'middle', ... 'HorizontalAlignment', 'left', ... 'Interpreter', 'none', ... 'Color', 1-cBeatLabelsColorCode{hb_labels_idx(b)}, ... 'BackgroundColor', cBeatLabelsColorCode{hb_labels_idx(b)}, ... 'EdgeColor', cBeatLabelsColorCode{hb_labels_idx(b)} ) ... ), qrs2plot_classifier(aux_seq_classifier), 'UniformOutput', false ) ]; end end else if( bAux ) str_aux = 'on'; else str_aux = 'off'; end cellfun(@(a)(set(a, 'Visible', str_aux )), QRSfpHdls); end bAux = eDetailLevel ~= kNoDetail && ( ( eDetailLevel == kCloseDetailML || eDetailLevel == kCloseDetailAll ) && plotXrange <= closeDetailSampSize ); if( isempty(QRScplxHdls) ) if( bAux ) % QRScplxHdls = [ QRScplxHdls; PlotGlobalWaveMarks({'QRSon' 'qrs' 'QRSoff'}, [ plotYmin + (0.08*plotYrange) titleYposition - (0.1*plotYrange) ], color_QRScomplex_global)]; for jj = ECG_signals_idx QRScplxHdls = [ QRScplxHdls; PlotWaveMarks(global_annotations, {'QRSon' 'qrs' 'QRSoff'}, jj, 2*yTextOffset, QRScplxColor*0.95 ) ]; end end else if( bAux ) str_aux = 'on'; else str_aux = 'off'; end cellfun(@(a)(set(a, 'Visible', str_aux )), QRScplxHdls); end % T wave bAux = eDetailLevel ~= kNoDetail && ( ( eDetailLevel == kCloseDetailML || eDetailLevel == kCloseDetailAll ) && plotXrange <= closeDetailSampSize ); if( isempty(TwaveHdls) ) if( bAux ) % TwaveHdls = [ TwaveHdls; PlotGlobalWaveMarks({'Ton' 'T' 'Toff'}, [ plotYmin + (0.09*plotYrange) plotYmax - (0.07*plotYrange) ], color_Twave_global)]; for jj = ECG_signals_idx TwaveHdls = [ TwaveHdls; PlotWaveMarks(this_annotation, {'Ton' 'T' 'Toff'}, jj, yTextOffset, TwaveColor*0.95 ) ]; end end else if( bAux ) str_aux = 'on'; else str_aux = 'off'; end cellfun(@(a)(set(a, 'Visible', str_aux )), TwaveHdls); end end bAux = eDetailLevel ~= kNoDetail && ( (eDetailLevel == kCloseDetailML || eDetailLevel == kCloseDetailAll ) && plotXrange <= mediumDetailSampSize ); % QRS class labels if( bAux && ~isempty(hb_labels_idx) ) if(bWaveLegendPlotted) left_hb_legend = left_legend - 1.3*width_legend; else left_hb_legend = left_legend - 0.05*width_legend; end width_hb_legend = 1.1*width_legend; height_hb_legend = 1.1*height_legend; bottom_hb_legend = bottom_legend - 0.05*height_legend; bHBclassLegendPlotted = true; % Heartbeats class frame legend_hdl = {patch([left_hb_legend left_hb_legend [left_hb_legend left_hb_legend]+width_hb_legend left_hb_legend ], [bottom_hb_legend [bottom_hb_legend bottom_hb_legend]+height_hb_legend bottom_hb_legend bottom_hb_legend], [1 1 1], 'EdgeColor', [0 0 0])}; lcBeatLabels = length(cBeatLabels); for jj = 1:lcBeatLabels legend_hdl = [legend_hdl; {text( left_hb_legend + jj*width_hb_legend/(lcBeatLabels+1), bottom_hb_legend + height_hb_legend/2, cBeatLabels{jj}, 'FontSize', 8, 'HorizontalAlignment', 'center', 'BackgroundColor', QRScplxColor, 'EdgeColor', cBeatLabelsColorCode{jj} )} ]; end UserChnageViewHdls = [UserChnageViewHdls; legend_hdl]; end % bAux = eDetailLevel ~= kNoDetail && ( (eDetailLevel == kCloseDetailML || eDetailLevel == kCloseDetailAll ) && plotXrange <= closeDetailSampSize ); bAux = eDetailLevel ~= kNoDetail && ( (eDetailLevel == kCloseDetailML || eDetailLevel == kCloseDetailAll ) ); % QRS annotations labels bAux2 = any( colvec(cellfun( @(a,b,c)(~isempty(a(b(c)))), qrs_ploted, qrs2plot, aux_seq )) ); if( bAux && bAux2 && ~isempty(qrs_ploted_names) ) cant_qrs_names = length(qrs_ploted_names); if(bWaveLegendPlotted) left_qrs_names_legend = left_legend; width_qrs_names_legend = width_legend; height_qrs_names_legend = height_legend * cant_qrs_names; bottom_qrs_names_legend = bottom_legend - 0.5*height_legend - height_qrs_names_legend; elseif(bHBclassLegendPlotted) left_qrs_names_legend = left_hb_legend; width_qrs_names_legend = width_hb_legend; height_qrs_names_legend = height_hb_legend * cant_qrs_names; bottom_qrs_names_legend = bottom_hb_legend - 0.5*height_hb_legend - height_qrs_names_legend; else left_qrs_names_legend = left_legend ; width_qrs_names_legend = width_legend; height_qrs_names_legend = height_legend * cant_qrs_names; bottom_qrs_names_legend = bottom_legend + height_legend - height_qrs_names_legend; end % QRS names frame legend_hdl = {patch([left_qrs_names_legend left_qrs_names_legend [left_qrs_names_legend left_qrs_names_legend]+width_qrs_names_legend left_qrs_names_legend ], [bottom_qrs_names_legend [bottom_qrs_names_legend bottom_qrs_names_legend]+height_qrs_names_legend bottom_qrs_names_legend bottom_qrs_names_legend], [1 1 1], 'EdgeColor', [0 0 0])}; for jj = 1:cant_qrs_names aux_ls = cLinespecs{jj}; legend_hdl = [legend_hdl; {text( left_qrs_names_legend + 0.1*width_qrs_names_legend, bottom_qrs_names_legend + height_qrs_names_legend * (jj)/(cant_qrs_names+1), adjust_string( qrs_ploted_names{jj}, 15 ), ... 'FontSize', 8, ... 'VerticalAlignment', 'middle', ... 'HorizontalAlignment', 'left', ... 'Interpreter', 'none', ... 'Color', 1-aux_ls{4}, ... 'BackgroundColor', aux_ls{4}, ... 'EdgeColor', aux_ls{5} )} ... ]; end UserChnageViewHdls = [UserChnageViewHdls; legend_hdl]; end %% single-lead annotations if( ~isempty(annotations) ) if( eDetailLevel ~= kNoDetail && ( (eDetailLevel == kCloseDetailSL || eDetailLevel == kCloseDetailAll ) && plotXrange <= closeDetailSampSize ) ) bWaveLegendPlotted = true; % frame legend_hdl = {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])}; legend_hdl = [legend_hdl; {text( left_legend + width_legend/4, bottom_legend + height_legend/2, 'P', 'FontSize', 8, 'HorizontalAlignment', 'center', 'BackgroundColor', PwaveColor)}]; legend_hdl = [legend_hdl; {text( left_legend + width_legend/2, bottom_legend + height_legend/2, 'QRS', 'FontSize', 8, 'HorizontalAlignment', 'center', 'BackgroundColor', QRScplxColor )}]; legend_hdl = [legend_hdl; {text( left_legend + width_legend*3/4, bottom_legend + height_legend/2, 'T', 'FontSize', 8, 'HorizontalAlignment', 'center', 'BackgroundColor', TwaveColor )}]; UserChnageViewHdls = [UserChnageViewHdls; legend_hdl]; end for jj = 1:length(ECG_signals_idx) this_annotation = annotations(jj); % P wave bAux = ( eDetailLevel ~= kNoDetail && ( ( eDetailLevel == kCloseDetailSL || eDetailLevel == kCloseDetailAll ) && plotXrange <= closeDetailSampSize ) ); if( isempty(PwaveGlblHdls{jj}) ) if( bAux ) % PwaveGlblHdls{jj} = [ PwaveGlblHdls{jj}; PlotWaveMarks(this_annotation, {'Pon' 'P' 'Poff'}, jj, 0.5*yTextOffset, ColorOrder(jj,:) )]; PwaveGlblHdls{jj} = [ PwaveGlblHdls{jj}; PlotWaveMarks(this_annotation, {'Pon' 'P' 'Poff'}, ECG_signals_idx(jj), 0.5*yTextOffset, PwaveColor )]; end else if( bAux ) str_aux = 'on'; else str_aux = 'off'; end cellfun(@(a)(set(a, 'Visible', str_aux )), PwaveGlblHdls{jj}); end % QRS complex bAux = eDetailLevel ~= kNoDetail && ( ( eDetailLevel == kCloseDetailSL || eDetailLevel == kCloseDetailAll ) && plotXrange <= closeDetailSampSize); if( isempty(QRScplxGlblHdls{jj}) ) if( bAux ) QRScplxGlblHdls{jj} = [ QRScplxGlblHdls{jj}; PlotWaveMarks(this_annotation, {'QRSon' 'qrs' 'QRSoff'}, ECG_signals_idx(jj), 2*yTextOffset, QRScplxColor)]; end else if( bAux ) str_aux = 'on'; else str_aux = 'off'; end cellfun(@(a)(set(a, 'Visible', str_aux )), QRScplxGlblHdls{jj}); end % T wave bAux = eDetailLevel ~= kNoDetail && ( ( eDetailLevel == kCloseDetailSL || eDetailLevel == kCloseDetailAll ) && plotXrange <= closeDetailSampSize ); if( isempty(TwaveGlblHdls{jj}) ) if( bAux ) % TwaveGlblHdls{jj} = [ TwaveGlblHdls{jj}; PlotWaveMarks(this_annotation, {'Ton' 'T' 'Toff'}, jj, yTextOffset, ColorOrder(jj,:) )]; TwaveGlblHdls{jj} = [ TwaveGlblHdls{jj}; PlotWaveMarks(this_annotation, {'Ton' 'T' 'Toff'}, ECG_signals_idx(jj), yTextOffset, TwaveColor )]; end else if( bAux ) str_aux = 'on'; else str_aux = 'off'; end cellfun(@(a)(set(a, 'Visible', str_aux )), TwaveGlblHdls{jj}); end end end %% Time/Voltage scales % ECG voltage if( bPlotScales ) PlotECGscale(limits); % time rect_scale_height = 4*yTextOffset; rect_scale_width = 0.06 * plotXrange; rect_scale_X = plotXmax - (0.07 * plotXrange); rect_scale_Y = plotYmin + (0.07 * plotYrange); UserChnageViewHdls = [UserChnageViewHdls; {rectangle('Position',[rect_scale_X, rect_scale_Y, rect_scale_width, rect_scale_height], 'FaceColor', [1 1 1])}]; XscaleSize = floor( 0.03 * plotXrange * 1/heasig.freq) * heasig.freq ; if( XscaleSize == 0 ) % multiple of mult milliseconds mult = 800; XscaleSize_ms = 0; while( XscaleSize_ms == 0 && mult > 1) XscaleSize_ms = floor(floor( 0.03 * plotXrange * 1e3 * 1/heasig.freq) / mult) * mult; mult = round(mult / 2); end XscaleSize = XscaleSize_ms * heasig.freq / 1e3; UserChnageViewHdls = [ UserChnageViewHdls; {text( rect_scale_X + round(rect_scale_width/2), rect_scale_Y + yTextOffset, [ num2str(XscaleSize_ms) ' ms' ], 'FontSize', 8, 'HorizontalAlignment', 'center')}]; else % multiple of mult seconds mult = 60; XscaleSize_s = 0; while( XscaleSize_s == 0) XscaleSize_s = floor(floor( 0.03 * plotXrange * 1/heasig.freq) / mult) * mult; mult = round(mult / 2); end XscaleSize = XscaleSize_s * heasig.freq; UserChnageViewHdls = [ UserChnageViewHdls; {text( rect_scale_X + round(rect_scale_width/2), rect_scale_Y + yTextOffset, [ num2str(XscaleSize_s) ' s' ], 'FontSize', 8, 'HorizontalAlignment', 'center')}]; end UserChnageViewHdls = [UserChnageViewHdls; {plot(axes_hdl, [ -(XscaleSize/2) -(XscaleSize/2) XscaleSize/2 XscaleSize/2] + rect_scale_X + round(rect_scale_width/2) , [ -yTextOffset 0 0 -yTextOffset] + rect_scale_Y + 3*yTextOffset, 'k-', 'LineWidth', 0.25 )}]; end %% Title if( bPaperModeOn ) UserChnageViewHdls = [UserChnageViewHdls; ... { text( plotXmin + 0.5 * plotXrange, titleYposition, ... strTitle, ... 'BackGroundColor', [0.99 0.92 0.8], ... 'EdgeColor', [1 0 0], ... 'LineWidth', 1.2, ... 'Interpreter', 'none', ... 'FontSize', 9, ... 'HorizontalAlignment', 'center' )}; ]; else UserChnageViewHdls = [UserChnageViewHdls; ... { text( plotXmin + 0.5 * plotXrange, titleYposition, ... strTitle, ... 'BackGroundColor', [0.702 0.78 1], ... 'EdgeColor', [0.078 0.169 0.549], ... 'LineWidth', 1.2, ... 'Interpreter', 'none', ... 'FontSize', 9, ... 'HorizontalAlignment', 'center' )}; ]; end titleExtent = get(UserChnageViewHdls{end}, 'Extent'); topLevelHdl = [topLevelHdl; UserChnageViewHdls(end)]; end %-------------------------------------------------------------------------- %========================================================================== function aux_seq = limit_sequence(qrs2plot, limits) aux_QRS2plot = 15; aux_idx = find( qrs2plot > limits(1) & qrs2plot < limits(2) ); if( isempty(aux_idx) ) aux_seq = []; else if( aux_QRS2plot < length(aux_idx) ) aux_seq = unique(round(linspace( 1, length(aux_idx), aux_QRS2plot))); aux_seq = aux_idx(aux_seq); else aux_seq = aux_idx; end end end %========================================================================== %-------------------------------------------------------------------------- %========================================================================== function PlotECGscale(limits) plotXmin = limits(1); plotXmax = limits(2); plotYmin = limits(3); plotYmax = limits(4); plotXrange = plotXmax - plotXmin; plotYrange = plotYmax - plotYmin; xTextOffset = 0.005*plotXrange; rect_scale_height = 0.13 * plotYrange; rect_scale_width = 4*xTextOffset; rect_scale_X = plotXmax - 6*xTextOffset; rect_scale_Y = plotYmin + (0.13 * plotYrange); UserChnageViewHdls = [UserChnageViewHdls; {rectangle('Position',[rect_scale_X, rect_scale_Y, rect_scale_width, rect_scale_height], 'FaceColor', [1 1 1])}]; YscaleSize = rect_scale_height / gains(volt_idx(1)); if( YscaleSize < 1e-3 ) % picovolts k = 1e-6; str_aux = 'p'; elseif( YscaleSize < 1 && YscaleSize >= 1e-3 ) % nanovolts k = 1e-3; str_aux = 'n'; elseif( YscaleSize >= 1 && YscaleSize <= 1e3) % microvolts k = 1; str_aux = '{\mu}'; elseif( YscaleSize < 1e6 && YscaleSize >= 1e3 ) % milivolts k = 1e3; str_aux = 'm'; elseif( YscaleSize >= 1e6 ) % volts k = 1e6; str_aux = ''; end mult = 800; YscaleSize = 0; while( YscaleSize == 0 && mult >= 1) YscaleSize = floor(floor( rect_scale_height / lead_gain / k ) / mult ) * mult; mult = round(mult / 2); end YscaleSize_uv = YscaleSize * k * lead_gain; UserChnageViewHdls = [ UserChnageViewHdls; {text( plotXmax - 5*xTextOffset, rect_scale_Y + rect_scale_height/2, [ num2str(YscaleSize) ' ' str_aux 'V' ], 'FontSize', 8, 'HorizontalAlignment', 'center', 'Rotation', 90)}]; UserChnageViewHdls = [UserChnageViewHdls; {plot(axes_hdl, [ -xTextOffset 0 0 -xTextOffset] + (plotXmax - 3*xTextOffset) , [ -YscaleSize_uv/2 -YscaleSize_uv/2 YscaleSize_uv/2 YscaleSize_uv/2 ] + rect_scale_Y + rect_scale_height/2, 'k-', 'LineWidth', 0.25 )}]; end %-------------------------------------------------------------------------- %========================================================================== function UserChangeView(obj, event_obj, id_caller) if( nargin < 3 ) id_caller = ''; end hold(axes_hdl, 'on') xlimits = get(axes_hdl,'Xlim'); ylimits = get(axes_hdl,'Ylim'); plotYmax = ylimits(2); plotYmin = ylimits(1); plotXmax = xlimits(2); plotXmin = xlimits(1); if( strcmpi(id_caller, 'pan') ) if( plotXmin < original_plot_lims(1) ) plotXmin = original_plot_lims(1); plotXmax = original_plot_lims(1) + prev_Xrange; end if( plotXmax > original_plot_lims(2) ) plotXmax = original_plot_lims(2); plotXmin = original_plot_lims(2) - prev_Xrange; end aux_lim = (original_plot_lims(3) * max(gains)) - max(offsets); if( plotYmin < aux_lim ) plotYmin = aux_lim; plotYmax = aux_lim + prev_Yrange; end aux_lim = (original_plot_lims(4) * min(gains)) - min(offsets); if( plotYmax > aux_lim ) plotYmax = aux_lim; plotYmin = aux_lim - prev_Yrange; end elseif( strcmpi(id_caller, 'zoom') ) % plotYrange = diff(ylimits); % additional space for x axis % plotYmin = plotYmin - 0.04*plotYrange; plotYmax = min(plotYmax, original_plot_lims(4)); plotYmin = max(plotYmin, original_plot_lims(3)); % plotYmin = min(plotYmin, user_data.original_plot_lims(4) - 0.2*plotYrange); % plotYmax = max(plotYmax, user_data.original_plot_lims(3) + 0.2*plotYrange); end plotYrange = plotYmax - plotYmin; plotXrange = plotXmax - plotXmin; prev_Yrange = plotYrange; prev_Xrange = plotXrange; if( plotYmin < plotYmax ) set(axes_hdl, 'Ylim', [plotYmin plotYmax]); end aux_qrs_ploted = cellfun( @(a)( find(a >= plotXmin & a <= plotXmax) ), qrs_ploted, 'UniformOutput', false ); % additional space for x axis % Xmargin = plotXrange * 0.1; % plotXmin = max(plotXmin - Xmargin, original_plot_lims(1)); % plotXmax = min(plotXmax + Xmargin, original_plot_lims(2)); % plotXrange = plotXmax - plotXmin; set(axes_hdl, 'Xlim', [ plotXmin plotXmax]); cellfun(@(a)( CheckAndDeleteHdl(a) ), UserChnageViewHdls); UserChnageViewHdls = []; % if( ~isempty(UserChnageViewHdls) ) % if( ishandle(UserChnageViewHdls) ) % delete(UserChnageViewHdls); % end % UserChnageViewHdls = []; % end update_axis_plot_ecg_strip( aux_qrs_ploted ); if( ishandle(ECG_hdl) ) uistack(ECG_hdl, 'top'); end if( ishandle(ECGd_hdl) ) uistack(ECGd_hdl, 'top'); end hold(axes_hdl, 'off') % set(obj, 'UserData', user_data); if( ~strcmpi(event_obj, 'JustUpdate') ) %save focus aux_fig = gcf; % update linked plots if any for jj = rowvec(linked_hdl) figure(jj) axes_hdl = get(jj,'CurrentAxes'); set(axes_hdl, 'Xlim', xlimits); UserChangeView(jj, 'JustUpdate', id_caller) end %restore focus figure(aux_fig) end end %-------------------------------------------------------------------------- %========================================================================== function [ecg_range ecg_min ecg_max ecg_median] = CalcECG_range(ECG) ecg_prctiles = cell2mat(arrayfun( @(a)(prctile( randsample(ECG(:,a), max( 100, round(cant_samp * cant_leads / 100) ) ), [ 2.5 50 97.5 ] )), (1:cant_leads)', 'UniformOutput', false) )'; ecg_range = ecg_prctiles(3,:) - ecg_prctiles(1,:); ecg_max = colvec(ecg_prctiles(3,:) + 0.7 * ecg_range); ecg_min = colvec(ecg_prctiles(1,:) - 0.4 * ecg_range); ecg_median = colvec(ecg_prctiles(2,:)); ecg_range = ecg_max - ecg_min; end %-------------------------------------------------------------------------- %========================================================================== function [plotYrange plotYmin plotYmax] = CalcPlotYlimits(ecg_min, ecg_max, lead_gain, lead_offset) % plotYmax = max(ecg_max*(lead_gain) - lead_offset); % plotYmin = min(ecg_min*(lead_gain) - lead_offset); plotYmax = max(colvec(ecg_max).*lead_gain - colvec(lead_offset) ) ; plotYmin = min(colvec(ecg_min).*lead_gain - colvec(lead_offset) ); plotYrange = plotYmax - plotYmin; % additional space for x axis if( plotYrange == 0 ) plotYmax = plotYmin + 0.1; plotYmin = plotYmin - 0.1; plotYrange = plotYmax - plotYmin; end plotYmin = plotYmin - plot_bottom_margin_width*plotYrange; plotYmax = plotYmax + plot_top_margin_width*plotYrange; end %-------------------------------------------------------------------------- %========================================================================== function WindowButtonDownCallback2D(src, evnt) %#ok %WindowButtonDownCallback2D if fIsMouseOnLegend, return; end clickType = get(src, 'SelectionType'); switch clickType case 'normal' DragMouseBegin(); mMgDirection = 'plus'; case 'open' if fIsMagnifierOn MagnifierSizeChange(mMgDirection); else ResetAxesToOrigView(); end case 'alt' RubberBandBegin(); mMgDirection = 'minus'; case 'extend' if fIsMagnifierOn MagnifierReset(); else position_mouse = get(axes_hdl, 'CurrentPoint'); prev_lead_idx = lead_selected_idx; lead_selected_idx = max(1,heasig.nsig) * abs(ecg_max(1) * gains(1) - position_mouse(1,2)) / (ecg_max(1) * gains(1) - (-offsets(cant_leads) + ecg_min(cant_leads)*gains(cant_leads)) ); % update_title_efimero( num2str(lead_selected_idx), 5 ); lead_selected_idx = min(cant_leads, max(1, 1+floor( lead_selected_idx ) ) ); if( length(prev_lead_idx) == length(lead_selected_idx) ) % disable lead selection lead_selected_idx = 1:cant_leads; update_title_efimero( 'All leads', 5 ); else update_title_efimero( heasig.desc(lead_selected_idx,:), 5 ); end %just measure on graph fNoZoom = true; RubberBandBegin(); % ZoomMouseExtendBegin(); end end end %-------------------------------------------------------------------------- %========================================================================== function WindowButtonUpCallback2D(src, evnt) %#ok %WindowButtonUpCallback2D DragMouseEnd(); % ZoomMouseExtendEnd(); RubberBandEnd(); fNoZoom = false; end %-------------------------------------------------------------------------- %========================================================================== function WindowButtonMotionCallback2D(src, evnt) %#ok %WindowButtonMotionCallback2D if ~(fIsMagnifierOn || fIsDragAllowed || fIsRubberBandOn) % set current axes under cursor SelectAxesUnderCursor(); end if fIsEnableControl DragMouse(); RubberBandUpdate(); MagnifierUpdate(); end % ZoomMouseExtend(); PointerCrossUpdate(); end %-------------------------------------------------------------------------- %========================================================================== function WindowScrollWheelFcn2D(src, evnt) %#ok %WindowScrollWheelFcn2D if fIsMouseOnLegend, return; end % Update Zoom Info % because it can be changed function 'zoom' UpdateCurrentZoomAxes(); switch mZoomScroll case 'normal' directions = {'minus', 'plus'}; case 'reverse' directions = {'plus', 'minus'}; end verScrollCount = evnt.VerticalScrollCount; if (verScrollCount > 0) direction = directions{1}; elseif (verScrollCount < 0) direction = directions{2}; else return; end if( strcmpi(scroll_mode, 'gain/offset') ) changeGainOffset(verScrollCount); set(src, 'UserData', user_data); elseif( strcmpi(scroll_mode, 'zoom') ) ZoomMouse(direction); PointerCrossUpdate(); MagnifierZoomChange(direction); end UserChangeView( fig_hdl, [], 'zoom'); end %-------------------------------------------------------------------------- function changeGainOffset(verScrollCount) prev_offset = lead_offset; prev_gain = gains; bLeadMask = false(heasig.nsig,1); bLeadMask(lead_selected_idx) = true; if( strcmp(gain_offset_mode, 'offset' ) ) % offset k_offset = verScrollCount * ecg_range .* gains * 0.05; this_lead_offset = max( 0, lead_offset + k_offset); if( all(prev_offset == this_lead_offset) ) return else lead_offset = this_lead_offset ; end % retain all not selected lead_offset(~bLeadMask(2:end)) = prev_offset(~bLeadMask(2:end)); offsets = abs(cumsum(lead_offset)); else % gain this_lead_gain = 1.5^(-verScrollCount) * gains; bAux = this_lead_gain < min_gain | this_lead_gain > max_gain; gains = this_lead_gain; gains(bAux) = prev_gain(bAux); % retain all not selected gains(~bLeadMask) = prev_gain(~bLeadMask); update_title_efimero( num2str(rowvec(gains)), 5 ); end [plotYrange plotYmin plotYmax] = CalcPlotYlimits(ecg_min, ecg_max, gains, offsets); cla(axes_hdl); set(axes_hdl, 'ColorOrder', ColorOrder); hold(axes_hdl, 'on') %plot ECG ECG_hdl = plot(axes_hdl, start_sample:end_sample, bsxfun( @minus, bsxfun( @times, ECG, rowvec(gains)), rowvec(offsets) ), 'LineWidth', 1.3 ); set(axes_hdl, 'Box', 'off' ); set(axes_hdl, 'Xtick', [] ); set(axes_hdl, 'Ytick', [] ); set(axes_hdl, 'Xcolor', [1 1 1] ); set(axes_hdl, 'Ycolor', [1 1 1] ); ylim([plotYmin plotYmax]); hold(axes_hdl, 'off') PwaveHdls = []; TwaveHdls = []; QRScplxHdls = []; PwaveGlblHdls = cell(heasig.nsig,1); TwaveGlblHdls = cell(heasig.nsig,1); QRScplxGlblHdls = cell(heasig.nsig,1); QRSfpHdls = []; QRSfpFarHdls = []; if( strcmp(gain_offset_mode, 'offset' ) ) update_title_efimero( num2str(rowvec(offsets)), 5 ); else update_title_efimero( num2str(rowvec(gains)), 5 ); end end %========================================================================== function WindowKeyPressCallback2D(src, evnt) %#ok %WindowKeyPressCallback2D modifier = evnt.Modifier; switch evnt.Key case '0' ResetAxesToOrigView(); case {'equal', 'add'} ZoomKeys('plus'); case {'hyphen', 'subtract'} ZoomKeys('minus'); case 'a' switch( ann_graph_mode ) case kBackColourAnns ann_graph_mode = kLinesAnns; update_title_efimero( 'Annotations line mode', 5 ); case kLinesAnns ann_graph_mode = kBackColourAnns; update_title_efimero( 'Annotations colour mode', 5 ); otherwise ann_graph_mode = kBackColourAnns; update_title_efimero( 'Annotations colour mode', 5 ); end cellfun( @(a)(delete(a)), [PwaveGlblHdls; TwaveGlblHdls; QRScplxGlblHdls]); PwaveGlblHdls = cell(heasig.nsig,1); TwaveGlblHdls = cell(heasig.nsig,1); QRScplxGlblHdls = cell(heasig.nsig,1); UserChangeView( fig_hdl, [], 'pan'); case 'h' disp_help(); case 'c' SetPointerCrossKeys(); case 'd' switch( eDetailLevel ) case kNoDetail eDetailLevel = kCloseDetailSL; update_title_efimero( 'Close SL', 5 ); case kCloseDetailSL eDetailLevel = kMediumDetailSL; update_title_efimero( 'Medium SL', 5 ); case kMediumDetailSL eDetailLevel = kCloseDetailML; update_title_efimero( 'Close ML', 5 ); case kCloseDetailML eDetailLevel = kMediumDetailML; update_title_efimero( 'Medium ML', 5 ); case kMediumDetailML eDetailLevel = kCloseDetailAll; update_title_efimero( 'Close All', 5 ); case kCloseDetailAll eDetailLevel = kMediumDetailAll; update_title_efimero( 'Medium All', 5 ); case kMediumDetailAll eDetailLevel = kNoDetail; update_title_efimero( 'No detail', 5 ); otherwise eDetailLevel = kNoDetail; update_title_efimero( 'No detail', 5 ); end UserChangeView( fig_hdl, [], 'pan'); case 'p' if( bPaperModeOn ) PaperModeOff(); else PaperModeOn(); end case 'g' scroll_mode = 'gain/offset'; gain_offset_mode = 'gain'; % update_title_efimero( 'Gain', inf ); case 'o' scroll_mode = 'gain/offset'; gain_offset_mode = 'offset'; % update_title_efimero( 'Offset', inf ); case 'x' fIsEnableZoomX = ~fIsEnableZoomX; if( fIsEnableZoomX ) fIsEnableZoomY = false; update_title_efimero( 'X mode', 10 ); else update_title_efimero( 'XY mode', 10 ); end case 'y' fIsEnableZoomY = ~fIsEnableZoomY; if( fIsEnableZoomY ) fIsEnableZoomX = false; update_title_efimero( 'Y mode', 10 ); else update_title_efimero( 'XY mode', 10 ); end case 'm' if fIsEnableControl MagnifierOn(); update_title_efimero( 'Magnifier', 5 ); end case 'r' report_format_idx = report_format_idx + 1; if(report_format_idx > length(cKnownReportFormats) || report_format_idx < 1 ) report_format_idx = 1; end report_format = cKnownReportFormats{report_format_idx}; update_title_efimero( report_format, 5 ); case 's' SaveReport(); end end %-------------------------------------------------------------------------- function SaveReport() if( isempty(report_filename) ) if( isempty(ECG_w) ) report_path = [pwd filesep]; else report_path = fileparts(ECG_w.recording_name); report_path = [report_path filesep]; end if( isfield(heasig, 'recname') ) report_filename = [report_path heasig.recname '.' report_format]; else report_filename = [report_path 'ECG_strip_capture_' datestr(now, 'dd_mm_yy-HH_MM_SS' ) '.' report_format]; end else report_path = fileparts(report_filename); report_path = [report_path filesep]; end if( exist(report_path, 'dir') ) init_ghostscript(); export_fig(report_filename, '-nocrop', ['-' report_format], fig_hdl); update_title_efimero( ['Exported to ' report_filename], 5 ); else cprintf('[1,0.5,0]', 'Could not create report file: folder %s does not exist\n', report_path ); end end %========================================================================== function PaperModeOff() bPaperModeOn = false; bAux = cellfun(@(a)(ishandle(a)), paperModeHdl ); if( any(bAux) ) cellfun(@(a)(delete(a)), paperModeHdl(bAux) ); paperModeHdl = {}; end if( ishandle(ECGd_hdl) ) delete(ECGd_hdl); end set(ECG_hdl, 'Visible', 'on') UserChangeView( fig_hdl, [], 'zoom'); end %-------------------------------------------------------------------------- %========================================================================== function PaperModeOn() % definitions top_frame = plotYmax - 2* yTextOffset ; bottom_frame = plotYmin + 2* yTextOffset ; left_frame = startSignalX; right_frame = endSignalX; % some updates in the view bPaperModeOn = true; set(ECG_hdl, 'Visible', 'off') hold(axes_hdl, 'on'); ECGd_hdl = plot(axes_hdl, start_sample:down_factor:end_sample, bsxfun( @minus, bsxfun( @times, ECGd, rowvec(gains)), rowvec(offsets) ), 'LineWidth', 1.3 ); hold(axes_hdl, 'off'); UserChangeView( fig_hdl, [], 'zoom'); % time grid [~, major_tick_idx] = sort( abs((plotXrange./major_tick_values_time) - 10)); major_tick = major_tick_values_time(major_tick_idx(1)); this_start = ceil(max(0,plotXmin)/major_tick)*major_tick; this_end = floor(min(heasig.nsamp,plotXmax)/major_tick)*major_tick; major_tick_x = this_start:major_tick:this_end; minor_tick = major_tick / 5; this_start = ceil(max(1,plotXmin)/minor_tick)*minor_tick; this_end = floor(min(heasig.nsamp,plotXmax)/minor_tick)*minor_tick; minor_tick_x = this_start:minor_tick:this_end; minor_tick_x = setdiff(minor_tick_x, major_tick_x); % voltage grid [~, major_tick_idx] = sort( abs(( (plotYrange / gains(1)) ./major_tick_values_voltage) - 10)); major_tick = major_tick_values_voltage(major_tick_idx(1)); [voltage_major_tick, voltage_major_tick_preffix]= microVoltsTransformer(major_tick); major_tick_voltage = bottom_frame:major_tick * gains(1):top_frame; minor_tick = major_tick * gains(1) / 5; minor_tick_voltage = bottom_frame:minor_tick:top_frame; minor_tick_voltage = setdiff(minor_tick_voltage, major_tick_voltage); bAux = ishandle(paperModeHdl); if( any(bAux) ) delete(paperModeHdl(bAux)); paperModeHdl = {}; end hold(axes_hdl, 'on'); % background % left paperModeHdl = [paperModeHdl; ... {patch('Faces', [1 2 3 4], ... 'Vertices', [ [plotXmin; plotXmin; left_frame; left_frame ] [plotYmin; plotYmax; plotYmax; plotYmin ] ], ... 'FaceColor', [1 1 1], 'EdgeColor', [1 1 1] )} ]; % right paperModeHdl = [paperModeHdl; ... {patch('Faces', [1 2 3 4], ... 'Vertices', [ [right_frame; right_frame; plotXmax; plotXmax ] [plotYmin; plotYmax; plotYmax; plotYmin ] ], ... 'FaceColor', [1 1 1], 'EdgeColor', [1 1 1] )} ]; % top paperModeHdl = [paperModeHdl; ... {patch('Faces', [1 2 3 4], ... 'Vertices', [ [plotXmin; plotXmax; plotXmax; plotXmin ] [plotYmax; plotYmax; top_frame; top_frame ] ], ... 'FaceColor', [1 1 1], 'EdgeColor', [1 1 1] )} ]; % bottom paperModeHdl = [paperModeHdl; ... {patch('Faces', [1 2 3 4], ... 'Vertices', [ [plotXmin; plotXmax; plotXmax; plotXmin ] [bottom_frame; bottom_frame; plotYmin; plotYmin ] ], ... 'FaceColor', [1 1 1], 'EdgeColor', [1 1 1] )} ]; % grid % time aux_grid_idx = length(paperModeHdl) + 1; paperModeHdl = [paperModeHdl; colvec(arrayfun(@(a)(plot(axes_hdl, repmat(a,2,1), [bottom_frame; top_frame], '-', 'Color', [1 0.6 0.6], 'LineWidth', 0.5 )), major_tick_x, 'UniformOutput', false)) ]; paperModeHdl = [paperModeHdl; colvec(arrayfun(@(a)(plot(axes_hdl, repmat(a,2,1), [bottom_frame; top_frame], ':', 'Color', [1 0.6 0.6], 'LineWidth', 0.3 )), minor_tick_x, 'UniformOutput', false))]; %voltage paperModeHdl = [paperModeHdl; colvec(arrayfun(@(a)(plot(axes_hdl, [left_frame; right_frame], repmat(a,2,1), 'Color', [1 0.6 0.6], 'LineWidth', 0.5 )), major_tick_voltage, 'UniformOutput', false)) ]; paperModeHdl = [paperModeHdl; colvec(arrayfun(@(a)(plot(axes_hdl, [left_frame; right_frame], repmat(a,2,1), ':', 'Color', [1 0.6 0.6], 'LineWidth', 0.3 )), minor_tick_voltage, 'UniformOutput', false)) ]; aux_grid_idx = aux_grid_idx:length(paperModeHdl); % Arrow function caughts an error situation that is preferred to % avoid. db_status = dbstatus(); bRestoreErrorStatus = false; if( length(db_status) > 1 && strcmpi(db_status(end).cond, 'caught error') ) dbclear if caught error bRestoreErrorStatus = true; end % voltage scale if( length(major_tick_voltage) > 2 ) Vscale_y = mean(major_tick_voltage([2,3])); Vscale_x = right_frame - 2*xTextOffset; Vscale_arrow_x = right_frame - 1*xTextOffset; paperModeHdl = [paperModeHdl; {text( Vscale_x, Vscale_y , sprintf([ '%d ' voltage_major_tick_preffix 'V' ], voltage_major_tick), 'FontSize', 8, 'HorizontalAlignment', 'center', 'Rotation', 90, 'BackGroundColor', [1 1 1], 'EdgeColor', [1 1 1] )}]; paperModeHdl = [paperModeHdl; {arrow( [Vscale_arrow_x; major_tick_voltage(2)], [Vscale_arrow_x; major_tick_voltage(3)], 2, 1, [0 0 0], axes_hdl )}]; end % restore error status if(bRestoreErrorStatus) dbstop if caught error end % time reference if( (plotXrange/heasig.freq) > 20 ) precision = 0; elseif( (plotXrange/heasig.freq) > 10 ) precision = 1; elseif( (plotXrange/heasig.freq) > 5 ) precision = 2; else precision = 3; end paperModeHdl = [ ... paperModeHdl; ... colvec(arrayfun( @(a)(text( a, bottom_frame + yTextOffset, Seconds2HMS( (a + base_time )/heasig.freq, precision ) , 'FontSize', 8, 'HorizontalAlignment', 'center', 'BackgroundColor', [1 1 1])), major_tick_x, 'UniformOutput', false)) ... ]; % frame aux_frame_idx = length(paperModeHdl) + 1; paperModeHdl = [paperModeHdl; {plot(axes_hdl, [left_frame left_frame right_frame right_frame left_frame ], [bottom_frame top_frame top_frame bottom_frame bottom_frame], 'Color', [1 0.6 0.6], 'LineWidth', 2.5 )}]; aux_frame_idx = aux_frame_idx:length(paperModeHdl); hold(axes_hdl, 'off'); cellfun(@(a)(uistack(a,'bottom')), paperModeHdl(aux_grid_idx)); cellfun(@(a)(uistack(a,'top')), [paperModeHdl(aux_frame_idx); topLevelHdl]); end %-------------------------------------------------------------------------- %========================================================================== function WindowKeyReleaseCallback2D(src, evnt) %#ok %WindowKeyReleaseCallback2D switch evnt.Key case {'leftarrow', 'rightarrow', 'uparrow', 'downarrow'} mDragShiftStep = mDragSaveShiftStep; case 'o' scroll_mode = 'zoom'; % update_title_efimero( 'Zoom', 5 ); case 'g' scroll_mode = 'zoom'; % update_title_efimero( 'Zoom', 5 ); case 'm' MagnifierOff(); end end %-------------------------------------------------------------------------- %========================================================================== function DragMouseBegin() %DragMouseBegin begin draging if (~fIsDragAllowed && ~fIsMagnifierOn) if( bPaperModeOn ) PaperModeOff(); end [cx, cy] = GetCursorCoordOnWindow(); mDragStartX = cx; mDragStartY = cy; fIsDragAllowed = true; PrevStateWindowButtonMotionFcn = get(fig_hdl, 'WindowButtonMotionFcn'); set(fig_hdl, 'WindowButtonMotionFcn', @WindowButtonMotionCallback2D); end end %-------------------------------------------------------------------------- %========================================================================== function DragMouseEnd() %DragMouseEnd end draging if fIsDragAllowed fIsDragAllowed = false; set(fig_hdl, 'WindowButtonMotionFcn', PrevStateWindowButtonMotionFcn); UserChangeView( fig_hdl, [], 'pan'); end end %-------------------------------------------------------------------------- %========================================================================== function DragMouse() %DragMouse if fIsDragAllowed [cx, cy] = GetCursorCoordOnWindow(); pdx = mDragStartX - cx; pdy = mDragStartY - cy; mDragStartX = cx; mDragStartY = cy; DragAxes(pdx, pdy); end end %-------------------------------------------------------------------------- %========================================================================== function DragKeys(direction) %DragKeys dx = mDragShiftStep; dy = mDragShiftStep; % Increment of speed when you hold the button mDragShiftStep = mDragShiftStep + mDragShiftStepInc; directionsX = {'right', 'left'}; directionsY = {'down', 'up'}; switch mDragKeysX case 'normal' case 'reverse' directionsX = fliplr(directionsX); end switch mDragKeysY case 'normal' case 'reverse' directionsY = fliplr(directionsY); end switch direction case directionsX{1} DragAxes(-dx, 0); case directionsX{2} DragAxes(dx, 0); case directionsY{1} DragAxes(0, dy); case directionsY{2} DragAxes(0, -dy); end PointerCrossUpdate(); UserChangeView( fig_hdl, [], 'pan'); end %-------------------------------------------------------------------------- %========================================================================== function DragAxes(pdx, pdy) %DragAxes [xLim, yLim] = GetAxesLimits(); pos = GetObjPos(axes_hdl, 'Pixels'); pbar = get(axes_hdl, 'PlotBoxAspectRatio'); %NOTE: MATLAB Bug? % Fixed problem with AspectRatio and Position of Axes % MATLAB Function PAN is not correct works with rectangular images! % Here it is correctly. imAspectRatioX = pbar(2) / pbar(1); if (imAspectRatioX ~= 1) posAspectRatioX = pos(3) / pos(4); arFactorX = imAspectRatioX * posAspectRatioX; if (arFactorX < 1) arFactorX = 1; end else arFactorX = 1; end imAspectRatioY = pbar(1) / pbar(2); if (imAspectRatioY ~= 1) posAspectRatioY = pos(4) / pos(3); arFactorY = imAspectRatioY * posAspectRatioY; if (arFactorY < 1) arFactorY = 1; end else arFactorY = 1; end if fIsEnableZoomX % For log plots, transform to linear scale if strcmp(get(axes_hdl, 'xscale'), 'log') xLim = log10(xLim); xLim = FixInfLogLimits('x', xLim); isXLog = true; else isXLog = false; end dx = pdx * range(xLim) / (pos(3) / arFactorX); xLim = xLim + dx; % For log plots, untransform limits if isXLog xLim = 10.^(xLim); end end if fIsEnableZoomY if strcmp(get(axes_hdl, 'yscale'), 'log') yLim = log10(yLim); yLim = FixInfLogLimits('y', yLim); isYLog = true; else isYLog = false; end dy = pdy * range(yLim) / (pos(4) / arFactorY); yLim = yLim + dy; if isYLog yLim = 10.^(yLim); end end SetAxesLimits(xLim, yLim); end %-------------------------------------------------------------------------- %========================================================================== function ZoomMouse(direction) %ZoomMouse zooming axes with mouse if (IsZoomMouseAllowed && ~fIsMagnifierOn) [acx, acy] = GetCursorCoordOnAxes(); ZoomAxes(direction, acx, acy) end end %-------------------------------------------------------------------------- %========================================================================== function ZoomKeys(direction) %ZoomKeys zooming axes with keyboard if( bPaperModeOn ) PaperModeOff(); end UpdateCurrentZoomAxes(); [mZoomGrid, mZoomSteps] = ZoomLogGrid(mZoomMinPow, mZoomMaxPow, mZoomKeysNum); UpdateCurrentZoomAxes(); [acx, acy] = GetCursorCoordOnAxes(); ZoomAxes(direction, acx, acy) PointerCrossUpdate(); SetDefaultZoomGrid(); end %-------------------------------------------------------------------------- %========================================================================== function ZoomAxes(direction, cx, cy) %ZoomAxes Zoom axes in 2D and image modes [xLim, yLim] = GetAxesLimits(); if (fIsEnableZoomX || (~fIsEnableZoomX && ~fIsEnableZoomY ) ) mZoomIndexX = ChangeZoomIndex(direction, mZoomIndexX); zoomPct = GetZoomPercent(mZoomIndexX); xLim = RecalcZoomAxesLimits('x', xLim, mDefaultXLim, cx, zoomPct); end if (fIsEnableZoomY || (~fIsEnableZoomX && ~fIsEnableZoomY ) ) mZoomIndexY = ChangeZoomIndex(direction, mZoomIndexY); zoomPct = GetZoomPercent(mZoomIndexY); yLim = RecalcZoomAxesLimits('y', yLim, mDefaultYLim, cy, zoomPct); end SetAxesLimits(xLim, yLim); end %-------------------------------------------------------------------------- %========================================================================== function zoomPct = GetZoomPercent(zoomIndex, zoomGrid) %GetZoomPercent get zoom percent if (nargin < 2) zoomGrid = mZoomGrid; end zoomPct = zoomGrid(zoomIndex); end %-------------------------------------------------------------------------- %========================================================================== function zoomIndex = ChangeZoomIndex(direction, zoomIndex, zoomSteps) %ChangeZoomIndex if (nargin < 3) zoomSteps = mZoomSteps; end switch direction case 'plus' if (zoomIndex < zoomSteps) zoomIndex = zoomIndex + 1; end case 'minus' if (zoomIndex > 1) zoomIndex = zoomIndex - 1; end end end %-------------------------------------------------------------------------- %========================================================================== function axLim = RecalcZoomAxesLimits(ax, axLim, axLimDflt, zcCrd, zoomPct) %RecalcZoomAxesLimits recalc axes limits if strcmp(get(axes_hdl, [ax, 'scale']), 'log') axLim = log10(axLim); axLim = FixInfLogLimits(ax, axLim); axLimDflt = log10(axLimDflt); zcCrd = log10(zcCrd); isLog = true; else isLog = false; end if (zcCrd < axLim(1)), zcCrd = axLim(1); end if (zcCrd > axLim(2)), zcCrd = axLim(2); end rf = range(axLim); ra = range([axLim(1), zcCrd]); rb = range([zcCrd, axLim(2)]); cfa = ra / rf; cfb = rb / rf; newRange = range(axLimDflt) * 100 / zoomPct; dRange = newRange - rf; axLim(1) = axLim(1) - dRange * cfa; axLim(2) = axLim(2) + dRange * cfb; if isLog axLim = 10.^axLim; end end %-------------------------------------------------------------------------- %========================================================================== function UpdateCurrentZoomAxes() %UpdateCurrentZoomAxes [xLim, yLim] = GetAxesLimits(); [curentZoomX, curentZoomY] = GetCurrentZoomAxesPercent(xLim, yLim); if (curentZoomX ~= GetZoomPercent(mZoomIndexX)) [nu, mZoomIndexX] = min(abs(mZoomGrid - curentZoomX)); %#ok ([~, ...]) end if (curentZoomY ~= GetZoomPercent(mZoomIndexY)) [nu, mZoomIndexY] = min(abs(mZoomGrid - curentZoomY)); %#ok ([~, ...]) end end %-------------------------------------------------------------------------- %========================================================================== function [curentZoomX, curentZoomY] = GetCurrentZoomAxesPercent(xLim, yLim) %GetCurrentZoomAxesPercent if strcmp(get(axes_hdl, 'xscale'), 'log') xLim = log10(xLim); defaultXLim = log10(mDefaultXLim); else defaultXLim = mDefaultXLim; end if strcmp(get(axes_hdl, 'yscale'), 'log') yLim = log10(yLim); defaultYLim = log10(mDefaultYLim); else defaultYLim = mDefaultYLim; end curentZoomX = range(defaultXLim) * 100 / range(xLim); curentZoomY = range(defaultYLim) * 100 / range(yLim); end %-------------------------------------------------------------------------- %========================================================================== function SetDefaultZoomGrid() %SetDefaultZoomGrid set default zoom grid [mDefaultZoomGrid, mDefaultZoomSteps] = ... ZoomLogGrid(mZoomMinPow, mZoomMaxPow, mZoomNum); mZoomGrid = mDefaultZoomGrid; mZoomSteps = mDefaultZoomSteps; mZoomIndexX = find(mZoomGrid == 100); mZoomIndexY = mZoomIndexX; mZoom3DIndex = mZoomIndexX; end %-------------------------------------------------------------------------- %========================================================================== function PointerCrossOn() %PointerCrossOn if ~fIsPointerCross SetPointer('fullcrosshair'); % text objects h = []; for jj = 1:cant_leads h = [ h text('Parent', axes_hdl, 'BackgroundColor', bgColor, 'Color', ColorOrder(jj,:), 'EdgeColor', ColorOrder(jj,:)) ]; end h = [ h text('Parent', axes_hdl, 'BackgroundColor', bgColor, 'EdgeColor', [0 0 0] ) ]; % create pointer cross struct mPointerCross = struct(... 'htext', h ... ); PointerCrossSetup(); fIsPointerCross = true; PointerCrossUpdate(); end end %-------------------------------------------------------------------------- %========================================================================== function PointerCrossOff() %PointerCrossOff if fIsPointerCross delete(mPointerCross.htext); SetPointer('arrow'); fIsPointerCross = false; set(fig_hdl, 'WindowButtonMotionFcn', []); mPointerCross = []; end end %-------------------------------------------------------------------------- %========================================================================== function PointerCrossSetup() %PointerCrossSetup set(fig_hdl, 'WindowButtonMotionFcn', @WindowButtonMotionCallback2D); end %-------------------------------------------------------------------------- %========================================================================== function PointerCrossUpdate() %PointerCrossUpdate if fIsPointerCross [this_xlim, this_ylim] = GetAxesLimits(); [acx, acy] = GetCursorCoordOnAxes(); acx = min( cant_samp, max(1, round(acx) - start_sample + 1 )); % each lead extents = nan(cant_leads,4); for jj = 1:cant_leads if( any(jj == volt_idx) ) [aux_val, str_unit_prefix]= microVoltsTransformer(ECG( acx, jj)); set(mPointerCross.htext(jj), 'String', sprintf(['%3.0f ' str_unit_prefix 'V'], aux_val ) ); else set(mPointerCross.htext(jj), 'String', sprintf('%3.2f %s', ECG( acx, jj), heasig.units(jj,:) ) ); end extents(jj,:) = get(mPointerCross.htext(jj), 'Extent'); end % time if( (diff(this_xlim)/heasig.freq) > 20 ) precision = 0; else precision = 3; end set(mPointerCross.htext(cant_leads+1), 'String', Seconds2HMS( (acx + start_sample - 1 + base_time )/heasig.freq, precision)); % each lead for jj = 1:cant_leads set(mPointerCross.htext(jj), 'Position', [ this_xlim(2) - extents(jj,3), -offsets(jj) ] ); end % time extents = get(mPointerCross.htext(cant_leads+1), 'Extent'); set(mPointerCross.htext(cant_leads+1), 'Position', [acx + start_sample - 1 + 0.5*extents(3) this_ylim(1)] ); uistack(mPointerCross.htext,'top'); end end %-------------------------------------------------------------------------- %========================================================================== function RubberBandBegin() %RubberBandBegin if (~fIsRubberBandOn && ~fIsMagnifierOn) [acx, acy] = GetCursorCoordOnAxes(); % create rubber band struct mRubberBand = struct(... 'obj', [patch('Parent', axes_hdl), patch('Parent', axes_hdl)], ... 'txt_start_hdl', text('String', Seconds2HMS((acx+ base_time )/heasig.freq, 3), 'Parent', axes_hdl, 'BackgroundColor', bgColor, 'EdgeColor', [0 0 0] ), ... 'txt_duration_hdl', text('String', Seconds2HMS(0, 0), 'Parent', axes_hdl, 'BackgroundColor', bgColor, 'EdgeColor', [0 0 0] ), ... 'txt_amp_hdl', text('String', '0', 'Parent', axes_hdl, 'BackgroundColor', bgColor, 'EdgeColor', [0 0 0] ), ... 'txt_end_hdl', text('String', Seconds2HMS((acx+ base_time)/heasig.freq, 3), 'Parent', axes_hdl, 'BackgroundColor', bgColor, 'EdgeColor', [0 0 0] ), ... 'x1', acx, ... 'y1', acy, ... 'x2', acx, ... 'y2', acy); extents = get(mRubberBand.txt_start_hdl, 'Extent'); set(mRubberBand.txt_start_hdl, 'Position', [acx - extents(3) acy - extents(4)] ); extents = get(mRubberBand.txt_end_hdl, 'Extent'); set(mRubberBand.txt_end_hdl, 'Position', [acx acy - extents(4)] ); extents = get(mRubberBand.txt_duration_hdl, 'Extent'); set(mRubberBand.txt_duration_hdl, 'Position', [acx + 0.5 * extents(3) acy - extents(4)] ); set(mRubberBand.txt_amp_hdl, 'Position', [acx acy ] ); hAxes2d = GetHandlesAxes2D(); if ~isempty(hAxes2d) set(hAxes2d, ... 'XLimMode', 'manual', ... 'YLimMode', 'manual'); end RubberBandSetPos(); RubberBandSetup(); fIsRubberBandOn = true; PrevStateWindowButtonMotionFcn = get(fig_hdl, 'WindowButtonMotionFcn'); set(fig_hdl, 'WindowButtonMotionFcn', @WindowButtonMotionCallback2D); end end %-------------------------------------------------------------------------- %========================================================================== function RubberBandEnd() %RubberBandEnd if fIsRubberBandOn fIsRubberBandOn = false; set(fig_hdl, 'WindowButtonMotionFcn', PrevStateWindowButtonMotionFcn); delete(mRubberBand.obj); delete(mRubberBand.txt_start_hdl); delete(mRubberBand.txt_end_hdl); delete(mRubberBand.txt_duration_hdl); delete(mRubberBand.txt_amp_hdl); if(~fNoZoom) RubberBandZoomAxes(); end PointerCrossUpdate(); mRubberBand = []; if(~fNoZoom) UserChangeView(fig_hdl, [], 'zoom'); end end end %-------------------------------------------------------------------------- %========================================================================== function RubberBandUpdate() %RubberBandUpdate if fIsRubberBandOn [acx, acy] = GetCursorCoordOnAxes(); if( fIsEnableZoomX ) mRubberBand.x2 = acx; mRubberBand.y2 = mRubberBand.y1; elseif( fIsEnableZoomY ) mRubberBand.y2 = acy; mRubberBand.x2 = mRubberBand.x1; else mRubberBand.x2 = acx; mRubberBand.y2 = acy; end RubberBandSetPos(); this_start = min(mRubberBand.x1, mRubberBand.x2); this_end = max(mRubberBand.x1, mRubberBand.x2); this_dur = (this_end - this_start); aux_dur = (this_dur/heasig.freq); if( aux_dur < 1 ) time_precision = 3; this_dur_str = sprintf( '%3.0f ms', aux_dur*1e3); else if( aux_dur < 2 ) time_precision = 3; elseif( aux_dur < 5 ) time_precision = 2; elseif( aux_dur < 10 ) time_precision = 1; else time_precision = 0; end this_dur_str = Seconds2HMS( aux_dur, time_precision); end upper_part = max( mRubberBand.y1, mRubberBand.y2); lower_part = min( mRubberBand.y1, mRubberBand.y2); this_amp = (upper_part - lower_part)/gains(1); if(this_amp > 999) %milli str_unit_prefix = 'm'; this_amp = this_amp / 1e3; elseif(this_amp > 999999) str_unit_prefix = ''; this_amp = this_amp / 1e6; else %micro volts per default str_unit_prefix = '\\mu'; end % check decimal precision now if(this_amp < 9) amp_decs = '2'; elseif(this_amp < 99) amp_decs = '1'; else amp_decs = '0'; end % time set(mRubberBand.txt_start_hdl, 'String', Seconds2HMS( (this_start+ base_time)/heasig.freq, time_precision) ); extents = get(mRubberBand.txt_start_hdl, 'Extent'); set(mRubberBand.txt_start_hdl, 'Position', [this_start - extents(3) lower_part - extents(4)] ); set(mRubberBand.txt_end_hdl, 'String', Seconds2HMS( (this_end+ base_time)/heasig.freq, time_precision) ); extents = get(mRubberBand.txt_end_hdl, 'Extent'); set(mRubberBand.txt_end_hdl, 'Position', [this_end lower_part - extents(4)] ); set(mRubberBand.txt_duration_hdl, 'String', this_dur_str ); extents = get(mRubberBand.txt_duration_hdl, 'Extent'); set(mRubberBand.txt_duration_hdl, 'Position', [ this_start + this_dur/2 upper_part + extents(4)] ); % amplitude set(mRubberBand.txt_amp_hdl, 'String', sprintf(['%3.' amp_decs 'f ' str_unit_prefix 'V'], this_amp ) ); extents = get(mRubberBand.txt_amp_hdl, 'Extent'); set(mRubberBand.txt_amp_hdl, 'Position', [ this_end + 0.3*extents(3) lower_part + (upper_part - lower_part)/2 ] ); end end %-------------------------------------------------------------------------- %========================================================================== function RubberBandSetPos() %RubberBandSetPos set position of rubber band x1 = mRubberBand.x1; y1 = mRubberBand.y1; x2 = mRubberBand.x2; y2 = mRubberBand.y2; set(mRubberBand.obj, ... 'XData', [x1 x2 x2 x1], ... 'YData', [y1 y1 y2 y2]); end %-------------------------------------------------------------------------- %========================================================================== function RubberBandSetup() %RubberBandSetup set(mRubberBand.obj(1), ... 'EdgeColor', 'w', ... 'FaceColor', 'none', ... 'LineWidth', 1.5, ... 'LineStyle', '-'); set(mRubberBand.obj(2), ... 'EdgeColor', mRbEdgeColor, ... 'FaceColor', mRbFaceColor, ... 'FaceAlpha', mRbFaceAlpha, ... 'LineWidth', 0.5, ... 'LineStyle', '-'); end %-------------------------------------------------------------------------- %========================================================================== function RubberBandZoomAxes() %RubberBandZoomAxes apply zoom from rubber band if( fIsEnableZoomY ) xLim = get(axes_hdl, 'Xlim'); else xLim = sort([mRubberBand.x1, mRubberBand.x2]); end if( fIsEnableZoomX ) yLim = get(axes_hdl, 'Ylim'); else yLim = sort([mRubberBand.y1, mRubberBand.y2]); end if (range(xLim) == 0 || range(yLim) == 0) return; end [zoomPctX, zoomPctY] = GetCurrentZoomAxesPercent(xLim, yLim); cx = mean(xLim); cy = mean(yLim); xLim = RecalcZoomAxesLimits('x', xLim, mDefaultXLim, cx, zoomPctX); yLim = RecalcZoomAxesLimits('y', yLim, mDefaultYLim, cy, zoomPctY); SetAxesLimits(xLim, yLim); end %-------------------------------------------------------------------------- %========================================================================== function MagnifierOn() %MagnifierCreate if ~fIsMagnifierOn if( bPaperModeOn ) PaperModeOff(); end if fIsPointerCross isPointerCross = true; PointerCrossOff(); else isPointerCross = false; end mMgDirection = 'plus'; % create magnifier struct mMagnifier = struct(... 'obj', copyobj(axes_hdl, fig_hdl), ... 'frame_obj', [], ... 'size', mMgSize, ... 'zoom', mMgZoom); fIsMagnifierOn = true; set(fig_hdl, 'WindowButtonMotionFcn', @WindowButtonMotionCallback2D); MagnifierSetup(); MagnifierUpdate(); if isPointerCross PointerCrossOn(); end end end %-------------------------------------------------------------------------- %========================================================================== function MagnifierOff() %MagnifierOff if fIsMagnifierOn fIsMagnifierOn = false; set(fig_hdl, 'WindowButtonMotionFcn', PrevStateWindowButtonMotionFcn); set(axes_hdl, 'Color', get(mMagnifier.obj, 'Color')); delete(mMagnifier.obj); mMagnifier = []; end end %-------------------------------------------------------------------------- %========================================================================== function MagnifierUpdate() %MagnifierUpdate if fIsMagnifierOn % see original idea of magnify by Rick Hindman -- 7/29/04 % http://www.mathworks.com/matlabcentral/fileexchange/5961 [acx, acy] = GetCursorCoordOnAxes(); [wcx, wcy] = GetCursorCoordOnWindow('pixels'); [xLim, yLim] = GetAxesLimits(); if strcmp(get(axes_hdl, 'xscale'), 'log') xLim = log10(xLim); xLim = FixInfLogLimits('x', xLim); acx = log10(acx); isXLog = true; else isXLog = false; end if strcmp(get(axes_hdl, 'yscale'), 'log') yLim = log10(yLim); yLim = FixInfLogLimits('y', yLim); acy = log10(acy); isYLog = true; else isYLog = false; end figPos = GetObjPos(fig_hdl, 'pixels'); axPos = GetObjPos(axes_hdl, 'normalized'); % always square magnifier pbar = get(axes_hdl, 'PlotBoxAspectRatio'); af = pbar(1) / pbar(2); if (af == 1 && (pbar(1) == 1 && pbar(2) == 1)) af = figPos(3) / figPos(4); end mgSizePix = round(mMagnifier.size); mgZoom = mMagnifier.zoom; mgSize = mgSizePix / figPos(3); % normalized size mgPos(3) = mgSize * 2; mgPos(4) = mgPos(3) * af; mg3 = round(mgPos(3) * figPos(3)); mg4 = round(mgPos(4) * figPos(4)); if (mg4 < mg3) mgSize = (mgSizePix * (mg3 / mg4)) / figPos(3); end mgPos(3) = mgSize * 2; mgPos(4) = mgPos(3) * af; mgPos(1) = wcx / figPos(3) - mgSize; mgPos(2) = wcy / figPos(4) - mgSize * af; mgXLim = acx + (1 / mgZoom) * (mgPos(3) / axPos(3)) * diff(xLim) * [-0.5 0.5]; mgYLim = acy + (1 / mgZoom) * (mgPos(4) / axPos(4)) * diff(yLim) * [-0.5 0.5]; SetObjPos(mMagnifier.obj, mgPos, 'normalized'); if isXLog mgXLim = 10.^mgXLim; end if isYLog mgYLim = 10.^mgYLim; end set(mMagnifier.obj, ... 'XLim', mgXLim, ... 'YLim', mgYLim); MagnifierBorderUpdate(); end end %-------------------------------------------------------------------------- %========================================================================== function MagnifierSetup() %MagnifierSetup set(mMagnifier.obj, ... 'Box', 'on', ... 'XMinorTick', 'on', ... 'YMinorTick', 'on'); title(mMagnifier.obj, ''); xlabel(mMagnifier.obj, ''); ylabel(mMagnifier.obj, ''); hLines = findobj(mMagnifier.obj, 'Type', 'line'); if ~isempty(hLines) if (mMgLinesWidth ~= 1) set(hLines, 'LineWidth', mMgLinesWidth); end end set(axes_hdl, 'Color', get(axes_hdl, 'Color')*mMgShadow); end %-------------------------------------------------------------------------- %========================================================================== function MagnifierBorderUpdate() %MagnifierBorderUpdate end %-------------------------------------------------------------------------- %========================================================================== function MagnifierSizeChange(direction) %MagnifierSizeChange if fIsMagnifierOn switch direction case 'plus' if (mMagnifier.size < mMgMaxSize) mMagnifier.size = mMagnifier.size + mMgSizeStep; end case 'minus' if (mMagnifier.size > mMgMinSize) mMagnifier.size = mMagnifier.size - mMgSizeStep; end end MagnifierUpdate(); end end %-------------------------------------------------------------------------- %========================================================================== function MagnifierZoomChange(direction) %MagnifierZoomChange if fIsMagnifierOn switch direction case 'plus' if (mMagnifier.zoom < mMgMaxZoom) mMagnifier.zoom = mMagnifier.zoom * mMgZoomStep; end case 'minus' if (mMagnifier.zoom > mMgMinZoom) mMagnifier.zoom = mMagnifier.zoom / mMgZoomStep; end end MagnifierUpdate(); end end %-------------------------------------------------------------------------- %========================================================================== function MagnifierReset() %MagnifierReset if fIsMagnifierOn mMagnifier.size = mMgSize; mMagnifier.zoom = mMgZoom; MagnifierUpdate(); end end %-------------------------------------------------------------------------- %========================================================================== function ResetAxesToOrigView() %ResetAxesToOrigView reset axes to original limits if( bPaperModeOn ) PaperModeOff(); end SetAxesLimits(mDefaultXLim, mDefaultYLim); PointerCrossUpdate(); mZoomIndexX = find(mZoomGrid == 100); mZoomIndexY = mZoomIndexX; UserChangeView( fig_hdl, [], 'zoom') end %-------------------------------------------------------------------------- %========================================================================== function [x, y, z] = GetCursorCoordOnAxes() %GetCursorCoordOnAxImg crd = get(axes_hdl, 'CurrentPoint'); x = crd(2,1); y = crd(2,2); z = crd(2,3); end %-------------------------------------------------------------------------- %========================================================================== function [x, y] = GetCursorCoordOnWindow(units) %GetCursorCoordOnWindow if (nargin < 1), units = 'pixels'; end dfltUnits = get(fig_hdl, 'Units'); set(fig_hdl, 'Units', units); crd = get(fig_hdl, 'CurrentPoint'); x = crd(1); y = crd(2); set(fig_hdl, 'Units', dfltUnits); end %-------------------------------------------------------------------------- %========================================================================== function pos = GetObjPos(h, units) %GetObjPos get object position if (nargin < 2), units = get(h, 'Units'); end dfltUnits = get(h, 'Units'); set(h, 'Units', units); pos = get(h, 'Position'); set(h, 'Units', dfltUnits); end %-------------------------------------------------------------------------- %========================================================================== function SetObjPos(h, pos, units) %SetObjPos set object position if (nargin < 3), units = get(h, 'Units'); end dfltUnits = get(h, 'Units'); set(h, 'Units', units); set(h, 'Position', pos); set(h, 'Units', dfltUnits); end %-------------------------------------------------------------------------- %========================================================================== function [xLim, yLim] = GetAxesLimits() %GetAxesLimits xLim = get(axes_hdl, 'XLim'); yLim = get(axes_hdl, 'YLim'); end %-------------------------------------------------------------------------- %========================================================================== function SetAxesLimits(xLim, yLim) %SetAxesLimits set(axes_hdl, 'XLim', xLim); set(axes_hdl, 'YLim', yLim); end %-------------------------------------------------------------------------- %========================================================================== function SetPointerCrossKeys() %SetPointerCrossKeys set pointer fullcross if( bPaperModeOn ) PaperModeOff(); end if fIsPointerCross PointerCrossOff(); else PointerCrossOn(); end UserData = get(fig_hdl, 'UserData'); UserData.tools.pointercross = mPointerCross; set(fig_hdl, 'UserData', UserData); end %-------------------------------------------------------------------------- %========================================================================== function SetPointer(pointerType) %SetPointer set pointer symbol set(fig_hdl, 'Pointer', pointerType); end %-------------------------------------------------------------------------- %========================================================================== function SetAxesGridKeys() %SetAxesGridKeys on/off axes grid if fIsAxesGrid action = 'off'; fIsAxesGrid = false; else action = 'on'; fIsAxesGrid = true; end set(axes_hdl, 'XGrid', action, 'YGrid', action, 'ZGrid', action); if fIsMagnifierOn set(mMagnifier.obj, 'XGrid', action, 'YGrid', action); end end %-------------------------------------------------------------------------- %========================================================================== function [zg, st] = ZoomLogGrid(a, b, n) %ZoomLogGrid log zoom grid zg = unique(round(logspace(a, b, n))); zg(zg<100) = []; % begin zoom == 100% st = length(zg); if isempty(find(zg == 100, 1)) error('dragzoom:badZoomGridOptions', 'Options for zoom grid is bad.') end end %-------------------------------------------------------------------------- %========================================================================== function tf = IsZoomMouseAllowed() %IsZoomMouseAllowed [wcx, wcy] = GetCursorCoordOnWindow(); figPos = get(fig_hdl, 'Position'); if (wcx >= 1 && wcx <= figPos(3) && wcy >= 1 && wcy <= figPos(4)) tf = true; else tf = false; end end %-------------------------------------------------------------------------- %========================================================================== function SelectAxesUnderCursor() %SelectAxesUnderCursor select axes under cursor as current axi = GetAxesIndexUnderCursor(); if (axi > 0) fIsEnableControl = true; if ~mAxesInfo(axi).iscurrent caxi = GetCurrentAxesIndex(); if isempty(caxi) DeleteInvalidAxesInfo(); axi = GetAxesIndexUnderCursor(); isCax2d = mAxesInfo(axi).is2d; else isCax2d = mAxesInfo(caxi).is2d; end SetCurrentAxes(axi); % for fix "legend" axes capture if mAxesInfo(axi).islegend; fIsMouseOnLegend = true; else fIsMouseOnLegend = false; end % check callbacks if (isCax2d ~= mAxesInfo(axi).is2d) % if dimension of axes has changed SetCallbacks(); if fIsPointerCross % disable pointer cross PointerCrossOff() end else if fIsPointerCross % reset pointer cross PointerCrossOff() SetPointerCrossKeys() end end end else fIsEnableControl = false; end end %-------------------------------------------------------------------------- %========================================================================== function SetCurrentAxes(axi) %SetCurrentAxes set current axes and work mode axes_hdl = mAxesInfo(axi).handle; set(fig_hdl, 'CurrentAxes', axes_hdl); for i = 1:numel(mAxesInfo) mAxesInfo(i).iscurrent = false; end mAxesInfo(axi).iscurrent = true; mDefaultAxPos = mAxesInfo(axi).position; mDefaultXLim = mAxesInfo(axi).xlim; mDefaultYLim = mAxesInfo(axi).ylim; % save info to work correctly after saving figures UserData = get(fig_hdl, 'UserData'); UserData.axesinfo = mAxesInfo; set(fig_hdl, 'UserData', UserData); end %-------------------------------------------------------------------------- %========================================================================== function axi = GetCurrentAxesIndex() %GetCurrentAxesIndex axi = []; for i = 1:numel(mAxesInfo) if (ishandle(mAxesInfo(i).handle) && mAxesInfo(i).iscurrent) axi = i; return; end end end %-------------------------------------------------------------------------- %========================================================================== function axi = GetAxesIndexUnderCursor() %FindAxesUnderCursor find current axes under cursor axi = GetCurrentAxesIndex(); if ~fIsSelectedCurrentAxes caxi = GetCurrentAxesIndex(); if ~IsInBoundsAxes(mAxesInfo(caxi).handle) axi = 0; end return; end for i = 1:numel(mAxesInfo) if (ishandle(mAxesInfo(i).handle) && IsInBoundsAxes(mAxesInfo(i).handle)) axi = i; return; else axi = 0; % without axes end end end %-------------------------------------------------------------------------- %========================================================================== function hAxes2d = GetHandlesAxes2D() %GetHandlesAxes2D Get handles of 2-D axes isAxes2d = arrayfun(@(x) x.is2d && ~x.islegend, mAxesInfo); hAxes2d = hAxes(isAxes2d); if ~isempty(hAxes2d) % Set current axes on first position hAxes2d(eq(hAxes2d, axes_hdl)) = 0; hAxes2d = sort(hAxes2d); hAxes2d(eq(hAxes2d, 0)) = axes_hdl; end end %-------------------------------------------------------------------------- %========================================================================== function AxesInfo = GetAxesInfo() %GetAxesInfo make and get axes info struct countAxes = length(hAxes); AxesInfo = struct(... 'handle', cell(1, countAxes), ... 'iscurrent', cell(1, countAxes), ... 'is2d', cell(1, countAxes), ... 'isimage', cell(1, countAxes), ... 'isvisible', cell(1, countAxes), ... 'isvis3d', cell(1, countAxes), ... 'islegend', cell(1, countAxes), ... 'position', cell(1, countAxes), ... 'normposition', cell(1, countAxes), ... 'xlim', cell(1, countAxes), ... 'ylim', cell(1, countAxes), ... 'camtarget', cell(1, countAxes), ... 'camposition', cell(1, countAxes)); for i = 1:countAxes h = hAxes(i); AxesInfo(i).handle = h; AxesInfo(i).iscurrent = IsCurrentAxes(h); AxesInfo(i).is2d = IsAxes2D(h); AxesInfo(i).isimage = IsImageOnAxes(h); AxesInfo(i).isvisible = strcmpi(get(h, 'Visible'), 'on'); AxesInfo(i).isvis3d = IsAxesVis3D(h); AxesInfo(i).islegend = IsLegendAxes(h); AxesInfo(i).position = GetObjPos(h, 'pixels'); AxesInfo(i).normposition = GetObjPos(h, 'normalized'); AxesInfo(i).xlim = get(h, 'XLim'); AxesInfo(i).ylim = get(h, 'YLim'); AxesInfo(i).camtarget = get(h, 'CameraTarget'); AxesInfo(i).camposition = get(h, 'CameraPosition'); end end %-------------------------------------------------------------------------- %========================================================================== function tf = IsImageOnAxes(ax) %IsImageOnAxes if (nargin < 1), ax = axes_hdl; end h = findobj(ax, 'Type', 'Image'); if isempty(h) tf = false; else tf = true; end end %-------------------------------------------------------------------------- %========================================================================== function tf = IsAxes2D(ax) %IsAxes2D if (nargin < 1), ax = axes_hdl; end tf = is2D(ax); % (!!!) internal undocumented function end %-------------------------------------------------------------------------- %========================================================================== function tf = IsLegendAxes(ax) %IsLegendAxes tf = strcmp(get(ax, 'Tag'), 'legend'); end %-------------------------------------------------------------------------- %========================================================================== function targetInBounds = IsInBoundsAxes(ax) %InBoundsAxes Check if the user clicked within the bounds of the axes. If not, do nothing targetInBounds = true; tol = 3e-16; cp = get(ax, 'CurrentPoint'); XLims = get(ax, 'XLim'); if ((cp(1,1) - min(XLims)) < -tol || (cp(1,1) - max(XLims)) > tol) && ... ((cp(2,1) - min(XLims)) < -tol || (cp(2,1) - max(XLims)) > tol) targetInBounds = false; end YLims = get(ax, 'YLim'); if ((cp(1,2) - min(YLims)) < -tol || (cp(1,2) - max(YLims)) > tol) && ... ((cp(2,2) - min(YLims)) < -tol || (cp(2,2) - max(YLims)) > tol) targetInBounds = false; end ZLims = get(ax, 'ZLim'); if ((cp(1,3) - min(ZLims)) < -tol || (cp(1,3) - max(ZLims)) > tol) && ... ((cp(2,3) - min(ZLims)) < -tol || (cp(2,3) - max(ZLims)) > tol) targetInBounds = false; end end %-------------------------------------------------------------------------- %========================================================================== function tf = IsCurrentAxes(ax) %IsCurrentAxes hcAx = get(fig_hdl, 'CurrentAxes'); tf = eq(ax, hcAx); end %-------------------------------------------------------------------------- %========================================================================== function tf = IsAxesVis3D(ax) %IsAxesVis3D visProp = { get(ax, 'PlotBoxAspectRatioMode') get(ax, 'DataAspectRatioMode') get(ax, 'CameraViewAngleMode') }; tf = all(strcmpi(visProp, 'manual')); end %-------------------------------------------------------------------------- %========================================================================== function maximize(fig) units=get(fig,'units'); set(fig,'units','normalized','outerposition',[0 0.03 1 0.97]); set(fig,'units',units); end %-------------------------------------------------------------------------- %========================================================================== function b=protected_index(a,idx) if(isempty(a)) b = nan; else b = a(idx); end end %-------------------------------------------------------------------------- %========================================================================== function this_hdl = PlotWaveMarks( this_annotation, field_names, lead, vertTextOffset, this_color) this_hdl = {}; if( isfield(this_annotation, field_names{1} ) ) aux_on = colvec(this_annotation.(field_names{1})); aux_on( aux_on < 1 | aux_on > heasig.nsamp) = nan; else aux_on = []; end if( isfield(this_annotation, field_names{2} ) ) aux_peak = colvec(this_annotation.(field_names{2})); aux_peak( aux_peak < 1 | aux_peak > heasig.nsamp) = nan; else aux_peak = []; end if( isfield(this_annotation, field_names{3} ) ) aux_off = colvec(this_annotation.(field_names{3})); aux_off( aux_off < 1 | aux_off > heasig.nsamp) = nan; else aux_off = []; end if( ann_graph_mode == kLinesAnns ) % wave start bOn = ~isnan(aux_on) & aux_on >= start_sample & aux_on <= end_sample; aux_on_idx = find(bOn); this_hdl = [ this_hdl; colvec(arrayfun(@(a,b)(plot(axes_hdl, a, b, 'Color' , this_color, 'LineStyle', ':', 'Marker', '<' , 'MarkerSize', 2, 'LineWidth', 0.25) ), repmat(rowvec(aux_on(aux_on_idx)), 2, 1 ), bsxfun( @plus, repmat([-vertTextOffset; vertTextOffset], 1, length(aux_on_idx)), rowvec( (ECG(aux_on(aux_on_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead) ) ), 'UniformOutput', false ) ), ]; % wave end bOff = ~isnan(aux_off) & aux_off >= start_sample & aux_off <= end_sample; aux_off_idx = find(bOff); this_hdl = [ this_hdl; colvec(arrayfun(@(a,b)(plot(axes_hdl, a, b, 'Color' , this_color, 'LineStyle', ':', 'Marker', '>', 'MarkerSize', 2, 'LineWidth', 0.25 )), repmat(rowvec(aux_off(aux_off_idx)), 2, 1 ), bsxfun( @plus, repmat([-vertTextOffset; vertTextOffset], 1, length(aux_off_idx)), rowvec((ECG(aux_off(aux_off_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead) ) ), 'UniformOutput', false)) ]; % wave peak bPeak = ~isnan(aux_peak) & aux_peak >= start_sample & aux_peak <= end_sample; aux_peak_idx = find(bPeak); this_hdl = [ this_hdl; colvec(arrayfun(@(a,b)(plot(axes_hdl, a, b, 'Color' , this_color, 'LineStyle', ':', 'Marker', '^', 'MarkerSize', 2, 'LineWidth', 0.25 )), repmat(rowvec(aux_peak(aux_peak_idx)), 2, 1 ), bsxfun( @plus, repmat([-vertTextOffset; vertTextOffset]*1.3, 1, length(aux_peak_idx)), rowvec( (ECG(aux_peak(aux_peak_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead) ) ), 'UniformOutput', false)) ]; this_hdl = [ this_hdl; arrayfun( @(a)(text(a + 0.5*xTextOffset, (ECG(a - start_sample + 1, lead) * gains(lead)) - offsets(lead) + sign(ECG(a - start_sample + 1, lead)) * yTextOffset, field_names{2}, 'FontSize', 8, 'Color', this_color ) ), aux_peak(aux_peak_idx), 'UniformOutput', false ) ]; % wave conection between start-end aux_complete_idx = find(bOn & bOff); this_hdl = [ this_hdl; plot(axes_hdl, repmat([rowvec(aux_on(aux_complete_idx));rowvec(aux_off(aux_complete_idx))], 1, 2 ) , ... [ [-vertTextOffset + rowvec( (ECG(aux_on(aux_complete_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead) ) ; -vertTextOffset + rowvec((ECG(aux_off(aux_complete_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead))] [ vertTextOffset + rowvec( (ECG(aux_on(aux_complete_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead) ); vertTextOffset + rowvec((ECG(aux_off(aux_complete_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead)) ] ], 'Color' , this_color, 'LineStyle', ':', 'Marker', 'none', 'LineWidth', 0.25 )]; elseif( ann_graph_mode == kBackColourAnns ) % wave start bOn = ~isnan(aux_on) & aux_on >= start_sample & aux_on <= end_sample; % wave end bOff = ~isnan(aux_off) & aux_off >= start_sample & aux_off <= end_sample; % wave peak bPeak = ~isnan(aux_peak) & aux_peak >= start_sample & aux_peak <= end_sample; aux_peak_idx = find(bPeak); this_hdl = [ this_hdl; colvec(arrayfun(@(a,b)(plot(axes_hdl, a, b, 'Color' , this_color, 'LineStyle', ':', 'Marker', '^', 'MarkerSize', 2, 'LineWidth', 0.25 )), repmat(rowvec(aux_peak(aux_peak_idx)), 2, 1 ), bsxfun( @plus, repmat([-vertTextOffset; vertTextOffset]*1.3, 1, length(aux_peak_idx)), rowvec( (ECG(aux_peak(aux_peak_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead) ) ), 'UniformOutput', false)) ]; % this_hdl = [ this_hdl; arrayfun( @(a)(text(a + 0.5*xTextOffset, (ECG(a - start_sample + 1, lead) * gains(lead)) - offsets(lead) + sign(ECG(a - start_sample + 1, lead)) * yTextOffset, field_names{2}, 'FontSize', 8, 'Color', this_color ) ), aux_peak(aux_peak_idx) ) ]; % wave conection between start-end bOnOff = bOn & bOff; aux_complete_idx = find(bOnOff); aux_complete_idx2 = aux_complete_idx; aux_complete_idxx = arrayfun( @(a)( max(1, aux_on(a)):min(heasig.nsamp,aux_off(a)) ),aux_complete_idx, 'UniformOutput', false); % on-peak aux_complete_idx = find( ~bOnOff & bOn & bPeak); aux_complete_idx2 = [aux_complete_idx2;colvec(aux_complete_idx)]; aux_complete_idxx = [ aux_complete_idxx; arrayfun( @(a)( aux_on(a):aux_peak(a) ),aux_complete_idx, 'UniformOutput', false) ]; % peak-off aux_complete_idx = find(~bOnOff & bPeak & bOff); aux_complete_idx2 = [aux_complete_idx2;colvec(aux_complete_idx)]; aux_complete_idxx = [ aux_complete_idxx; arrayfun( @(a)( aux_peak(a):min(heasig.nsamp,aux_off(a)) ),aux_complete_idx, 'UniformOutput', false) ]; if( ~isempty(aux_complete_idxx) ) aux_offset = (start_sample - 1); %patch around the signal % this_hdl = [ this_hdl; cellfun( @(a)( patch( [a fliplr(a) ], [ (ECG(a-aux_offset, lead)* gains(lead) )- offsets(lead) + 0.5*yTextOffset ; flipud((ECG(a-aux_offset, lead)* gains(lead) )- offsets(lead)) - 0.5*yTextOffset ]', this_color, 'EdgeColor', 'none')), aux_complete_idxx) ]; %box around the wave max_vals = cellfun( @(a)( max(ECG(a-aux_offset, lead)) ), aux_complete_idxx, 'UniformOutput', false); min_vals = cellfun( @(a)( min(ECG(a-aux_offset, lead)) ), aux_complete_idxx, 'UniformOutput', false); this_edge_color = repmat({0.8*this_color}, length(aux_complete_idxx), 1 ); this_hdl = [ this_hdl; cellfun( @(a,b,c,d)( patch( [a(1) a(1) a(end) a(end) ], ( [ c b b c ] * gains(lead) )- offsets(lead), this_color, 'EdgeColor', d)), aux_complete_idxx, max_vals, min_vals, this_edge_color, 'UniformOutput', false) ]; end end % uistack(this_hdl, 'bottom'); end %-------------------------------------------------------------------------- %========================================================================== function this_hdl = PlotGlobalWaveMarks( field_names, limits, this_color) this_hdl = []; if( isfield(global_annotations, field_names{1} ) ) aux_on = global_annotations.(field_names{1}); else aux_on = []; end if( isfield(global_annotations, field_names{2} ) ) aux_peak = global_annotations.(field_names{2}); else aux_peak = []; end if( isfield(global_annotations, field_names{3} ) ) aux_off = global_annotations.(field_names{3}); else aux_off = []; end if( ann_graph_mode == kLinesAnns ) % wave start bOn = ~isnan(aux_on) & aux_on >= start_sample & aux_on <= end_sample; aux_on_idx = find(bOn); % this_hdl = [ this_hdl; plot(axes_hdl, repmat(rowvec(aux_on(aux_on_idx)), 2, 1 ), bsxfun( @plus, repmat([-vertTextOffset; vertTextOffset], 1, length(aux_on_idx)), rowvec( (ECG(aux_on(aux_on_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead) ) ), 'Color' , this_color, 'LineStyle', ':', 'Marker', '<' , 'MarkerSize', 2, 'LineWidth', 0.25)]; this_hdl = [ this_hdl; plot(axes_hdl, repmat(rowvec(aux_on(aux_on_idx)), 2, 1 ), repmat(colvec(limits), 1, length(aux_on_idx)), 'Color' , this_color, 'LineStyle', ':', 'Marker', '<' , 'MarkerSize', 4, 'LineWidth', 0.25)]; % wave end bOff = ~isnan(aux_off) & aux_off >= start_sample & aux_off <= end_sample; aux_off_idx = find(bOff); % this_hdl = [ this_hdl; plot(axes_hdl, repmat(rowvec(aux_off(aux_off_idx)), 2, 1 ), bsxfun( @plus, repmat([-vertTextOffset; vertTextOffset], 1, length(aux_off_idx)), rowvec((ECG(aux_off(aux_off_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead) ) ), 'Color' , this_color, 'LineStyle', ':', 'Marker', '>', 'MarkerSize', 2, 'LineWidth', 0.25 )]; this_hdl = [ this_hdl; plot(axes_hdl, repmat(rowvec(aux_off(aux_off_idx)), 2, 1 ), repmat(colvec(limits), 1, length(aux_off_idx)), 'Color' , this_color, 'LineStyle', ':', 'Marker', '>', 'MarkerSize', 4, 'LineWidth', 0.25 )]; % wave peak bPeak = ~isnan(aux_peak) & aux_peak >= start_sample & aux_peak <= end_sample; aux_peak_idx = find(bPeak); % this_hdl = [ this_hdl; plot(axes_hdl, repmat(rowvec(aux_peak(aux_peak_idx)), 2, 1 ), bsxfun( @plus, repmat([-vertTextOffset; vertTextOffset]*1.3, 1, length(aux_peak_idx)), rowvec( (ECG(aux_peak(aux_peak_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead) ) ), 'Color' , this_color, 'LineStyle', ':', 'Marker', '^', 'MarkerSize', 2, 'LineWidth', 0.25 )]; this_hdl = [ this_hdl; plot(axes_hdl, repmat(rowvec(aux_peak(aux_peak_idx)), 2, 1 ), repmat(colvec(limits) + [-0.02; 0.02] * diff(limits), 1, length(aux_peak_idx)), 'Color' , this_color, 'LineStyle', ':', 'Marker', '^', 'MarkerSize', 4, 'LineWidth', 0.25 )]; % this_hdl = [ this_hdl; arrayfun( @(a)(text(a + 0.5*xTextOffset, (ECG(a - start_sample + 1, lead) * gains(lead)) - offsets(lead) + sign(ECG(a - start_sample + 1, lead)) * yTextOffset, field_names{2}, 'FontSize', 8, 'Color', this_color ) ), aux_peak(aux_peak_idx) ) ]; % wave conection between start-end aux_complete_idx = find(bOn & bOff); % this_hdl = [ this_hdl; plot(axes_hdl, repmat([rowvec(aux_on(aux_complete_idx));rowvec(aux_off(aux_complete_idx))], 1, 2 ) , ... % [ [-vertTextOffset + rowvec( (ECG(aux_on(aux_complete_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead) ) ; -vertTextOffset + rowvec((ECG(aux_off(aux_complete_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead))] [ vertTextOffset + rowvec( (ECG(aux_on(aux_complete_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead) ); vertTextOffset + rowvec((ECG(aux_off(aux_complete_idx) - start_sample + 1, lead) * gains(lead)) - offsets(lead)) ] ], 'Color' , this_color, 'LineStyle', ':', 'Marker', 'none', 'LineWidth', 0.25 )]; this_hdl = [ this_hdl; plot(axes_hdl, repmat([rowvec(aux_on(aux_complete_idx));rowvec(aux_off(aux_complete_idx))], 1, 2 ) , ... [ repmat(limits(1), 2, length(aux_complete_idx)) repmat(limits(2),2,length(aux_complete_idx)) ], 'Color' , this_color, 'LineStyle', ':', 'Marker', 'none', 'MarkerSize', 4, 'LineWidth', 0.25 )]; % aux_on_idx = find(~isnan(aux_on)); % aux_off_idx = find(~isnan(aux_off)); % aux_peak_idx = find(~isnan(aux_peak)); % aux_complete_idx = find(~isnan(aux_on) & ~isnan(aux_off)); elseif( ann_graph_mode == kBackColourAnns ) % wave start bOn = ~isnan(aux_on) & aux_on >= start_sample & aux_on <= end_sample; % wave end bOff = ~isnan(aux_off) & aux_off >= start_sample & aux_off <= end_sample; % wave peak bPeak = ~isnan(aux_peak) & aux_peak >= start_sample & aux_peak <= end_sample; aux_peak_idx = find(bPeak); this_hdl = [ this_hdl; plot(axes_hdl, repmat(rowvec(aux_peak(aux_peak_idx)), 2, 1 ), repmat(colvec(limits) + [-0.02; 0.02] * diff(limits), 1, length(aux_peak_idx)), 'Color' , this_color, 'LineStyle', ':', 'Marker', '^', 'MarkerSize', 4, 'LineWidth', 0.25 )]; % wave conection between start-end bOnOff = bOn & bOff; aux_complete_idx = find(bOnOff); % normal sampled version aux_complete_idxx = arrayfun( @(a)( max(1, aux_on(a)):min(heasig.nsamp,aux_off(a)) ),aux_complete_idx, 'UniformOutput', false); % on-peak aux_complete_idx = find( ~bOnOff & bOn & bPeak); aux_complete_idxx = [ aux_complete_idxx; arrayfun( @(a)( aux_on(a):aux_peak(a) ),aux_complete_idx, 'UniformOutput', false) ]; % peak-off aux_complete_idx = find(~bOnOff & bPeak & bOff); aux_complete_idxx = [ aux_complete_idxx; arrayfun( @(a)( aux_peak(a):aux_off(a) ),aux_complete_idx, 'UniformOutput', false) ]; aux_offset = (start_sample - 1); %patch around the signal % this_hdl = [ this_hdl; cellfun( @(a)( patch( [a fliplr(a) ], [ (ECG(a-aux_offset, lead)* gains(lead) )- offsets(lead) + 0.5*yTextOffset ; flipud((ECG(a-aux_offset, lead)* gains(lead) )- offsets(lead)) - 0.5*yTextOffset ]', this_color, 'EdgeColor', 'none')), aux_complete_idxx) ]; %box around the wave this_hdl = [ this_hdl; cellfun( @(a,b)( patch( [a(1) a(1) a(end) a(end) ] - aux_offset, [ limits(2) limits(1) limits(1) limits(2) ], this_color, 'EdgeColor', b)), aux_complete_idxx, this_edge_color) ]; end uistack(this_hdl, 'bottom'); end %-------------------------------------------------------------------------- %========================================================================== function [aux_val, str_unit_prefix] = microVoltsTransformer(aux_val) orig_aux_val = aux_val; aux_val = abs(aux_val); if(aux_val > 999) %milli str_unit_prefix = 'm'; aux_val = orig_aux_val / 1e3; elseif(aux_val > 999999) str_unit_prefix = ''; aux_val = orig_aux_val / 1e6; else %micro volts per default str_unit_prefix = '\\mu'; end end %-------------------------------------------------------------------------- function disp_help() title_color = 'blue*'; sub_title_color = 'magenta'; disp_string_framed('*Blue', 'plot_ecg_strip help' ); cprintf( title_color, 'Mouse actions:\n\n'); cprintf( sub_title_color, ' Normal mode:\n' ); fprintf(1, [... ' single-click and holding LB : Activation Drag mode\n' ... ' single-click and holding RB : Activation Rubber Band for region zooming\n' ... ' single-click MB : Activation ''Extend'' Zoom mode\n' ... ' scroll wheel MB : Activation Zoom mode\n' ... ' double-click LB, RB, MB : Reset to Original View\n' ... ] ); cprintf( sub_title_color, ' Magnifier mode:\n'); fprintf(1, [... ' single-click LB : Not Used\n' ... ' single-click RB : Not Used\n' ... ' single-click MB : Reset Magnifier to Original View\n' ... ' scroll MB : Change Magnifier Zoom\n' ... ' double-click LB : Increase Magnifier Size\n' ... ' double-click RB : Decrease Magnifier Size\n' ... ] ); fprintf(1, '\n'); cprintf( title_color, 'Hotkeys in 2D mode:'); fprintf(1, '\n\n'); fprintf(1, [... ' ''h'' : Show help\n' ... ' ''+'' : Zoom plus\n' ... ' ''-'' : Zoom minus\n' ... ' ''d'' : Toggle the detail level of the annotations\n' ... ' ''a'' : Toggle the annotations graph mode\n' ... ' ''0'' : Set default axes (reset to original view)\n' ... ' ''c'' : On/Off pointer in crosshair mode\n' ... ' ''g'' : Change lead gain with scroll\n' ... ' ''o'' : Change lead offset with scroll\n' ... ' ''x'' : Zoom and drag works only for X axis\n' ... ' ''y'' : Zoom and drag works only for Y axis\n' ... ' ''m'' : If pressed and holding, Magnifier mode on\n' ... ' ''p'' : On/Off paper mode\n' ... ' ''r'' : Format of the exported file (PDF/PNG)\n' ... ' ''s'' : Export current view\n' ... ] ); ] ); end 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 update_title_efimero( strTitle, delay ) delete(findobj('Tag', 'title_efimero' )) left_legend = plotXmin + 4*xTextOffset; bottom_legend = plotYmax - 2*yTextOffset; aux_hdl = text( left_legend , bottom_legend , strTitle, 'FontSize', 8, 'HorizontalAlignment', 'left', 'BackgroundColor', 'r' ); set(aux_hdl, 'Tag', 'title_efimero') if( ~isinf(delay) && strcmpi(my_timer.Running, 'off') ) my_timer.StartDelay = delay; start(my_timer) end end function my_closefcn(obj,event_obj) stop(my_timer) delete(my_timer) delete(fig_hdl) end function CheckAndDeleteHdl( this_hdl ) if( ishandle(this_hdl) ) delete(this_hdl); end end end %--------------------------------------------------------------------------