Waveform Database Software Package (WFDB) for MATLAB and Octave 0.10.0

File: <base>/mcode/wfdbRecordViewer.m (52,950 bytes)
function varargout = wfdbRecordViewer(varargin)
% WFDBRECORDVIEWER MATLAB code for wfdbRecordViewer.fig
%      WFDBRECORDVIEWER, by itself, creates a new WFDBRECORDVIEWER or raises the existing
%      singleton*.
%
%      H = WFDBRECORDVIEWER returns the handle to a new WFDBRECORDVIEWER or the handle to
%      the existing singleton*.
%
%      WFDBRECORDVIEWER('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in WFDBRECORDVIEWER.M with the given input arguments.
%
%      WFDBRECORDVIEWER('Property','Value',...) creates a new WFDBRECORDVIEWER or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before wfdbRecordViewer_OpeningFcn gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to wfdbRecordViewer_OpeningFcn via varargin.
%
%      *See GUI Options on GUIDE's Tools menu.  Choose "GUI allows only one
%      instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES

% Edit the above text to modify the response to help wfdbRecordViewer

% Last Modified by GUIDE v2.5 05-Feb-2015 15:38:57

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
    'gui_Singleton',  gui_Singleton, ...
    'gui_OpeningFcn', @wfdbRecordViewer_OpeningFcn, ...
    'gui_OutputFcn',  @wfdbRecordViewer_OutputFcn, ...
    'gui_LayoutFcn',  [] , ...
    'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT


% --- Executes just before wfdbRecordViewer is made visible.
function wfdbRecordViewer_OpeningFcn(hObject, ~, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to wfdbRecordViewer (see VARARGIN)

global current_record records tm signal info tm_step exportFigure physionetAnn fext ann1 ann1Type ann1LabelsDisplaySetting
exportFigure=0;
physionetAnn={};
% Choose default command line output for wfdbRecordViewer
handles.output = hObject;

% Update handles structure
guidata(hObject, handles);
current_tmp=1;
isWorkspace=0;
ButtonName = questdlg('Select Database Location', 'Database Location',...
    'Local Directory','PhysioNet','MATLAB Workspace','Local Directory');
switch ButtonName,
    case 'Local Directory',
        [filename,directoryname] = uigetfile('*.hea;*.edf;*.mat','Select signal header file:');
        cd(directoryname)
        fext=filename(end-3:end);
        tmp=dir(['*' fext]);
        N=length(tmp);
        records=cell(N,1);
        for n=1:N
            fname=tmp(n).name;
            if(strcmp(fext,'.edf'))
                records(n)={fname};
            else
                records(n)={fname(1:end-4)};
            end
            if(strcmp(fname,filename))
                current_tmp=n;
            end
        end
    case 'MATLAB Workspace',
        records={'MALTAB Workspace'};
        [tm,signal,info,ann1,ann1Type]=loadWorkspaceRecord(handles);
        isWorkspace=1;
    case 'PhysioNet',
        %To implement
        %Select Database name
        h = waitbar(0,'Getting List of Databases from PhysioNet. Please wait...');
        dbs=physionetdb;
        D=length(dbs);
        IND=zeros(D,1);
        for n=1:D
            str=char(dbs{n});
            str=regexprep(str,'\n',' ');
            ind=strfind(str,'Description:');
            IND(n)=ind;
            pad=30-ind(1);
            str=regexprep(str,'Description:',blanks(pad));
            dbs{n}=str;
        end
        close(h)
        [db_ind,ok] = listdlg('PromptString','Select a database:',...
            'SelectionMode','single',...
            'ListString',dbs,'ListSize',[550 600]);
        if(~ok)
            return
        end
        h = waitbar(0,'Loading list of records. Please wait...');
        dbname=dbs{db_ind};
        dbname=dbname(1:IND(db_ind));
        dbname=regexprep(dbname,' ','');
        dbname=regexprep(dbname,'\n','');
        dbname=regexprep(dbname,'\t','');
        [tmp,status]=urlread(['http://physionet.org/physiobank/database/' dbname '/RECORDS']);
        if(status==1)
            records=regexp(tmp,'\n','split');
            if(isempty(records{end}(:)))
                records(end)=[];
            end
            for n=1:length(records)
                records{n}=['/' dbname '/' records{n}];
            end
            close(h)
            
            h = waitbar(0,'Loading list of annotations. Please wait...');
            [tmp,status]=urlread(['http://physionet.org/physiobank/database/' dbname '/ANNOTATORS']);
            if(status==1)
                ann=regexp(tmp,'\n','split');
                for n=1:length(ann(:,1))
                    tmpInd=regexp(ann{1},'\s');
                    physionetAnn(end+1)={ann{1}(1:tmpInd-1)};
                end
            end
            close(h)
        else
            errordlg(['Could not connect to PhysioNet server. Exiting!'])
            return
        end
        
end % switch


set(handles.RecorListMenu,'String',records)
current_record=current_tmp;
set(handles.RecorListMenu,'Value',current_record)
if(isWorkspace==0)
    loadRecord(records{current_record},handles);
    loadAnnotationList(records{current_record},handles);
end
set(handles.slider1,'Max',tm(end))
set(handles.slider1,'Min',tm(1))
set(handles.slider1,'SliderStep',[1 1]);
sliderStep=get(handles.slider1,'SliderStep');
tm_step=(tm(end)-tm(1)).*sliderStep(1);

wfdbplot(handles)



function varargout = wfdbRecordViewer_OutputFcn(~,~, handles)
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% Get default command line output from handles structure
varargout{1} = handles.output;

function PreviousButton_Callback(hObject, eventdata, handles)
global current_record records

current_record=current_record - 1;
set(handles.RecorListMenu,'Value',current_record);
Refresh(hObject, eventdata, handles)


function NextButton_Callback(hObject, eventdata, handles)
global current_record records
current_record=current_record + 1;
set(handles.RecorListMenu,'Value',current_record);
Refresh(hObject, eventdata, handles)


% --------------------------------------------------------------------
function FileMenu_Callback(hObject, eventdata, handles)
% hObject    handle to FileMenu (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)


% --------------------------------------------------------------------
function OpenMenuItem_Callback(hObject, eventdata, handles)
file = uigetfile('*.fig');
if ~isequal(file, 0)
    open(file);
end

% --------------------------------------------------------------------
function PrintMenuItem_Callback(hObject, eventdata, handles)
printdlg(handles.figure1)

% --------------------------------------------------------------------
function CloseMenuItem_Callback(hObject, eventdata, handles)
selection = questdlg(['Close ' get(handles.figure1,'Name') '?'],...
    ['Close ' get(handles.figure1,'Name') '...'],...
    'Yes','No','Yes');
if strcmp(selection,'No')
    return;
end

delete(handles.figure1)


% --- Executes on selection change in RecorListMenu.
function RecorListMenu_Callback(hObject, eventdata, handles)

global current_record records
current_record=get(handles.RecorListMenu,'Value');
Refresh(hObject, eventdata, handles)


% --- Executes during object creation, after setting all properties.
function RecorListMenu_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on slider movement.
function slider1_Callback(hObject, eventdata, handles)
wfdbplot(handles)


% --- Executes during object creation, after setting all properties.
function slider1_CreateFcn(hObject, eventdata, handles)
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end

function loadRecord(fname,handles)
global tm signal info analysisSignal analysisTime
h = waitbar(0,'Loading Data. Please wait...');
[signal,Fs,tm]=rdsamp(fname);
signal=single(signal);
info=wfdbdesc(fname);
R=length(info);
analysisSignal=[];
analysisTime=[];
signalDescription=cell(R,1);
for r=R:-1:1
    signalDescription(r)={info(r).Description};
end
set(handles.signalList,'String',signalDescription)
close(h)
return


function loadAnn1(fname,annName)
global ann1 ann1Labels
h = waitbar(0,'Loading Annotations. Please wait...');
if(strcmp(fname,'none'))
    ann1=[];
else
    [ann1,type,subtype,chan,num,comment]=rdann(fname,annName);
end
ann1Labels.type=type;
ann1Labels.subtype=subtype;
ann1Labels.chan=chan;
ann1Labels.num=num;
ann1Labels.comment=comment;
%Load other parameters for the ann1Labels structure
wfdbShowAnn1Labels(0);

close(h)

function loadAnn2(fname,annName)
global ann2 ann2Labels
h = waitbar(0,'Loading Annotations. Please wait...');
if(strcmp(fname,'none'))
    ann1=[];
else
    [ann2,type,subtype,chan,num,comment]=rdann(fname,annName);
    ann2Labels.type=type;
    ann2Labels.subtype=subtype;
    ann2Labels.chan=chan;
    ann2Labels.num=num;
    ann2Labels.comment=comment;
end
close(h)

function loadAnnotationList(fname,handles)
global ann1 ann2 annDiff physionetAnn

ann1=[];
ann2=[];
annDiff=[];
tmp=dir([fname '*']);
annotations={'none'};
if(isempty(physionetAnn))
    exclude={'dat','hea','edf','mat'};
    for i=1:length(tmp)
        name=tmp(i).name;
        st=strfind(name,'.');
        if(~isempty(st))
            tmp_ann=name(st+1:end);
            enter=1;
            for k=1:length(exclude)
                if(strcmp(tmp_ann,exclude{k}))
                    enter=0;
                end
            end
            if(enter)
                annotations(end+1)={tmp_ann};
            end
        end
    end
else
    annotations=[annotations;physionetAnn];
end

set(handles.Ann1Menu,'String',annotations)
set(handles.Ann2Menu,'String',annotations)


function wfdbplot(handles)
global tm signal info tm_step ann1 ann2 annDiff ann1RR analysisSignal
global specEstimation analysisTime analysisUnits analysisYAxis ann1Labels
global exportFigure ann1LabelsDisplaySetting

if(exportFigure)
    figure
    subplot(211)
else
    axes(handles.axes1);
    cla;
end

%Normalize each signal and plot them with an offset
[N,CH]=size(signal);
offset=0.5;

%Get time info
center=get(handles.slider1,'Value');
maxSlide=get(handles.slider1,'Max');
minSlide=get(handles.slider1,'Min');
if(tm_step == ( tm(end)-tm(1) ))
    tm_start=tm(1);
    tm_end=tm(end);
elseif(center==maxSlide)
    tm_end=tm(end);
    tm_start=tm_end - tm_step;
elseif(center==minSlide)
    tm_start=tm(1);
    tm_end=tm_start + tm_step;
else
    tm_start=center - tm_step/2;
    tm_end=center + tm_step/2;
end
[~,ind_start]=min(abs(tm-tm_start));
[~,ind_end]=min(abs(tm-tm_end));

DC=min(signal(ind_start:ind_end,:),[],1);
sig=signal - repmat(DC,[N 1]);
SCALE=max(sig(ind_start:ind_end,:),[],1);
SCALE(SCALE==0)=1;
sig=offset.*sig./repmat(SCALE,[N 1]);
OFFSET=offset.*[1:CH];
sig=sig + repmat(OFFSET,[N 1]);
if(~isempty(ann1))
    showThreshold=str2num(ann1LabelsDisplaySetting.threshold);
end

for ch=1:CH;
    plot(tm(ind_start:ind_end),sig(ind_start:ind_end,ch))
    hold on ; grid on
    if(~isempty(ann1))
        tmp_ann1=ann1((ann1>ind_start) & (ann1<ind_end));
        if(~isempty(tmp_ann1))
            if(length(tmp_ann1)<30)
                msize=8;
            else
                msize=5;
            end
            plot(tm(tmp_ann1),OFFSET(ch),'go','MarkerSize',msize,'MarkerFaceColor','g')
            %Plot labels if selected
            if(length(tmp_ann1)<showThreshold && ~strcmp(ann1LabelsDisplaySetting.showType,'-1'))
                ann_ind=find(((ann1>ind_start) & (ann1<ind_end)) ==1);
                K=length(ann_ind);
                for k=1:K
                    str=ann1Labels.type(k);
                    if(strcmp(ann1LabelsDisplaySetting.showComment,'true'))
                        str=[ str(:) ' ' ann1Labels.comment(k)];
                    end
                    if(strcmp(ann1LabelsDisplaySetting.channelSpecific,'false') || tmpChan(k)==ch)
                        if(strcmp(ann1LabelsDisplaySetting.abnormalOnly,'false'))
                            text(tm(tmp_ann1(k)),OFFSET(ch)+0.15,str)
                        else
                            if(~strcmp(ann1Labels.type(k),'N'))
                                text(tm(tmp_ann1(k)),OFFSET(ch)+0.15,str)
                            end
                        end
                    end
                end
            end
        end
    end
    if(~isempty(ann2))
        tmp_ann2=ann2((ann2>ind_start) & (ann2<ind_end));
        if(~isempty(tmp_ann2))
            if(length(tmp_ann2)<30)
                msize=8;
            else
                msize=5;
            end
            plot(tm(tmp_ann2),OFFSET(ch),'r*','MarkerSize',msize,'MarkerFaceColor','r')
        end
    end
    if(~isempty(info(ch).Description))
        text(tm(ind_start),ch*offset+0.85*offset,info(ch).Description,'FontWeight','bold','FontSize',12)
    end
    
end
set(gca,'YTick',[])
set(gca,'YTickLabel',[])
set(gca,'FontSize',10)
set(gca,'FontWeight','bold')
xlabel('Time (seconds)')
xlim([tm(ind_start) tm(ind_end)])

%Plot annotations in analysis window
if(~isempty(annDiff) & (get(handles.AnnotationMenu,'Value')==2))
    if(exportFigure)
        gcf
        subplot(212)
    else
        axes(handles.AnalysisAxes);
    end
    df=annDiff((ann1>ind_start) & (ann1<ind_end));
    plot(tm(tmp_ann1),df,'k*-')
    text(tm(tmp_ann1(1)),max(df),'Ann Diff','FontWeight','bold','FontSize',12)
    grid on
    ylabel('Diff (seconds)')
    xlim([tm(ind_start) tm(ind_end)])
end

if(~isempty(specEstimation))
    %Plot Spectral estimate in the analysis window
    if(exportFigure)
        gcf
        subplot(212)
    else
        axes(handles.AnalysisAxes);
    end
    if(specEstimation.isCohere==0)
        [Pxx,F]=pwelch(sig(ind_start:ind_end,specEstimation.sigInd),...
            specEstimation.WINDOW,specEstimation.NOVERLAP,...
            specEstimation.NFFT,specEstimation.Fs,'power');
    else
        [Pxx,F]=mscohere(sig(ind_start:ind_end,specEstimation.sigInd),...
            sig(ind_start:ind_end,specEstimation.ind2),specEstimation.WINDOW,...
            specEstimation.NOVERLAP,specEstimation.NFFT,specEstimation.Fs);
    end
    switch specEstimation.scale
        case 'linear'
            plot(F,Pxx,'k')
        case 'semilogx'
            semilogx(F,Pxx,'k')
        case 'semilogy'
            semilogy(F,Pxx,'k')
        case 'loglog'
            loglog(F,Pxx,'k')
    end
    xlabel('Frequency (Hz)')
    ylabel('Power')
    grid on
else
    %Plot custom signal in the analysis window
    if(~isempty(analysisSignal))
        if(exportFigure)
            gcf
            subplot(212)
        else
            axes(handles.AnalysisAxes);
        end
        if(isempty(analysisYAxis))
            %Standard 2D Plot
            plot(analysisTime,analysisSignal,'k')
            grid on;
        else
            if(isfield(analysisYAxis,'isImage') && analysisYAxis.isImage)
                %Plot scaled image
                imagesc(analysisSignal)
            else
                %3D Plot with colormap
                surf(analysisTime,analysisYAxis.values,analysisSignal,'EdgeColor','none');
                axis xy; axis tight; colormap(analysisYAxis.map); view(0,90);
            end
            ylim([analysisYAxis.minY analysisYAxis.maxY])
        end
        xlim([tm(ind_start) tm(ind_end)])
        if(~isempty(analysisUnits))
            ylabel(analysisUnits)
        end
    else
        %Plot RR series in analysis window
        if(~isempty(ann1RR))
            annStr=get(handles.AnnotationMenu,'String');
            valInd=get(handles.AnnotationMenu,'Value');
            if(strcmp(annStr{valInd},'Plot RR Series Ann1'))
                Nann=length(ann1);
                if(exportFigure)
                    gcf
                    subplot(212)
                else
                    axes(handles.AnalysisAxes);
                end
                ind=(ann1(1:end)>ind_start) & (ann1(1:end)<ind_end);
                ind=find(ind==1)+1;
                if(~isempty(ind) && ind(end)> Nann)
                    ind(end)=[];
                end
                tm_ind=ann1(ind);
                del_ind=find(tm_ind>N);
                if(~isempty(del_ind))
                    ind(ann1(ind)==tm_ind(del_ind))=[];
                    tm_ind(del_ind)=[];
                end
                if(~isempty(ind) && ind(end)>length(ann1RR))
                    del_ind=find(ind>length(ann1RR));
                    ind(del_ind)=[];
                    tm_ind(del_ind)=[];
                end
                plot(tm(tm_ind),ann1RR(ind),'k*-')
                text(tm(tm_ind(1)),max(ann1RR(ind)),'RR Series','FontWeight','bold','FontSize',12)
                grid on
                ylabel('Interval (seconds)')
                if(~isnan(ind_start) && ~isnan(ind_end) && ~(ind_start==ind_end))
                    xlim([tm(ind_start) tm(ind_end)])
                end
            end
        end
    end
    exportFigure=0;
end

% --- Executes on selection change in TimeScaleSelection.
function TimeScaleSelection_Callback(hObject, eventdata, handles)
global tm_step tm

TM_SC=[tm(end)-tm(1) 120 60 30 15 10 5 1];
index = get(handles.TimeScaleSelection, 'Value');
%Normalize step to time range
if(TM_SC(index)>TM_SC(1))
    index=1;
end
stp=TM_SC(index)/TM_SC(1);
set(handles.slider1,'SliderStep',[stp stp*10]);
tm_step=TM_SC(1).*stp(1);

axes(handles.axes1);
cla;
wfdbplot(handles)

% --- Executes during object creation, after setting all properties.
function TimeScaleSelection_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on selection change in AmplitudeScale.
function AmplitudeScale_Callback(hObject, eventdata, handles)
wfdbplot(handles)


% --- Executes during object creation, after setting all properties.
function AmplitudeScale_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on selection change in Ann1Menu.
function Ann1Menu_Callback(hObject, eventdata, handles)
global ann1 records current_record

ind = get(handles.Ann1Menu, 'Value');
annStr=get(handles.Ann1Menu, 'String');
loadAnn1(records{current_record},annStr{ind})
wfdbplot(handles)


% --- Executes during object creation, after setting all properties.
function Ann1Menu_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on selection change in Ann2Menu.
function Ann2Menu_Callback(hObject, eventdata, handles)
global ann2 records current_record

ind = get(handles.Ann2Menu, 'Value');
annStr=get(handles.Ann2Menu, 'String');
loadAnn2(records{current_record},annStr{ind})
wfdbplot(handles)


% --- Executes during object creation, after setting all properties.
function Ann2Menu_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


function AnnotationMenu_Callback(hObject, eventdata, handles)

global ann1 ann1Labels ann1RR info ann2 ann2Labels tm specEstimation analysisSignal exportFigure ann1LabelsDisplaySetting
exportFigure=0;

tips=0;
Fs=double(info(1).SamplingFrequency);
annStr=get(handles.AnnotationMenu,'String');
index=get(handles.AnnotationMenu,'Value');
switch(annStr{index})
    case 'Plot Annotation Differences'
        h = waitbar(0,'Comparing Annotations. Please wait...');
        annDiff=[];
        %Compare annotation with ann1menu being the reference
        N=length(ann1);
        if(~isempty(ann2))
            [A1,A2]=meshgrid(ann1,ann2);
            annDiff=min(abs(A1-A2))./Fs;
        end
        close(h)
        specEstimation=[];
        wfdbplot(handles)
        
    case 'Plot RR Series Ann1'
        h = waitbar(0,'Generating RR Series. Please wait...');
        %Compare annotation with ann1menu being the reference
        ann1RR=diff(ann1)./double(info(1).SamplingFrequency);
        close(h)
        specEstimation=[];
        analysisSignal=[];
        wfdbplot(handles)
        
    case 'Add annotations to Ann1'
        %Get closest sample using ginput
        if(tips)
            helpdlg('Left click to add multiple annotations. Hit Enter when done.','Adding Annotations');
        end
        axes(handles.axes1);
        [x,~]= ginput;
        
        %Convert to samples ann to ann1
        x=round(x*Fs);
        x=sort(x);
        N=length(x);
        
        %Get annotation info (which will be the same for multiple
        %annotations
        [annType,annSubtype,annChan,annNum,annComments]=getAnnFields();
        for n=1:N
            %[~,tmp_ind]=min(abs(x(n)-samp));
            ann1(end+1)=x(n);
            ann1Labels(end+1).type=annType;
            ann1Labels(end).subtype=annSubtype;
            ann1Labels(end).chan=annChan;
            ann1Labels(end).num=annNum;
            ann1Labels(end).comment=annComments;
        end
        if(isempty(ann1LabelsDisplaySetting))
            %Define/set display parameters if this is the first annotation
            wfdbShowAnn1Labels(1);
        end
        %Refresh annotation plot
        wfdbplot(handles)
        
    case 'Show Ann1 Labels'
        wfdbShowAnn1Labels(1);
        
    case 'Delete annotations from Ann1'
        
        %Get closest sample using ginput
        if(tips)
            helpdlg('Left click on annotations to remove multiple. Hit Enter when done.','Removing Annotations');
        end
        axes(handles.axes1);
        [x,~]= ginput;
        rmN=length(x);
        rm_ind=zeros(rmN,1);
        for n=1:rmN
            [~,tmp_ind]=min(abs(x(n)-tm(ann1)));
            rm_ind(n)=tmp_ind;
        end
        if~(isempty(rm_ind))
            ann1(rm_ind)=[];
        end
        %Refresh annotation plot
        wfdbplot(handles)
        
    case 'Delete annotations in a range from Ann1'
        
        %Get closest sample using ginput
        if(tips)
            helpdlg('Left click on start and end regions. Hit Enter when done.','Removing Annotations');
        end
        axes(handles.axes1);
        [x,~]= ginput;
        [~,start_ind]=min(abs(x(end-1)-tm(ann1)));
        [~,end_ind]=min(abs(x(end)-tm(ann1)));
        ann1(start_ind:end_ind)=[];
        %Refresh annotation plot
        wfdbplot(handles)
        
    case 'Edit annotations in Ann1'
        %Modify closest sample using ginput
        if(tips)
            helpdlg('Left click on waveform will shift closest annotation to the clicked point. Hit Enter when done.','Adding Annotations');
        end
        axes(handles.axes1);
        [x,~]= ginput;
        editN=length(x);
        edit_ind=zeros(editN,1);
        for n=1:editN
            [~,tmp_ind]=min(abs(x(n)-tm(ann1)));
            edit_ind(n)=tmp_ind;
        end
        if~(isempty(edit_ind))
            ann1(edit_ind)=round(x*Fs);
        end
        %Refresh annotation plot
        wfdbplot(handles)
        
    case 'Add annotations in a range from Ann2 to Ann2'
        
        if(tips)
            helpdlg('Left click on waveform to select start and end of region to add from Ann2 to Ann1. Hit Enter when done.','Adding Annotations');
        end
        axes(handles.axes1);
        [x,~]= ginput;
        ind=[1:length(x)]';
        X=sortrows([ind x(:)],2);
        x=X(:,2);
        ann2Labels=ann2Labels(X(:,1));
        [~,start_ind]=min(abs(x(1)-tm(ann2)));
        [~,end_ind]=min(abs(x(2)-tm(ann2)));
        ann1=sort([ann1;ann2(start_ind:end_ind)]);
        ann1Labels=[ann1Labels;ann2Labels];
        %Refresh annotation plot
        wfdbplot(handles)
        
    case 'Save modified annotations of Ann1'
        global records current_record
        defaultAnn=get(handles.Ann1Menu,'String');
        defaultInd=get(handles.Ann1Menu,'Value');
        defName={[defaultAnn{defaultInd} '_x']};
        newAnn=inputdlg('Enter new annotation name:','Save Annotation',1,defName);
        h=waitbar(0,['Saving annotation file: ' records{current_record} '.' newAnn{1}]);
        wrann(records{current_record},newAnn{1},ann1);
        close(h)
        
    case 'Save Ann1 to matfile'
        defName={'new_label.mat'};
        newAnn=inputdlg('Enter new MAT file name:','Save to mat file: ',1,defName);
        h=waitbar(0,['Saving annotation file: ' newAnn]);
        save(newAnn,'ann1','ann1Labels')
        close(h)
        
    case 'Launch PhysioNet Label Definitions'
        web('http://www.physionet.org/physiobank/annotations.shtml');
        
end


function AnnotationMenu_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


function Refresh(hObject, eventdata, handles)
global records current_record
loadRecord(records{current_record},handles);
loadAnnotationList(records{current_record},handles)
Ann1Menu_Callback(hObject, eventdata, handles)
Ann2Menu_Callback(hObject, eventdata, handles)
%AnalysisMenu_Callback(hObject, eventdata, handles)


% --- Executes on selection change in SignalMenu.
function SignalMenu_Callback(hObject, eventdata, handles)
% hObject    handle to SignalMenu (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: contents = cellstr(get(hObject,'String')) returns SignalMenu contents as cell array
%        contents{get(hObject,'Value')} returns selected item from SignalMenu

global tm signal info analysisSignal analysisTime analysisUnits analysisYAxis specEstimation
global exportFigure
contents = cellstr(get(hObject,'String'));
ind=get(handles.signalList,'Value');
str= contents{get(hObject,'Value')};
exportFigure=0;

if(strcmp(str,'Overwrite with Analyzed Signal'))
    if(length(analysisTime) == length(tm))
        signal(:,ind)=analysisSignal;
    else
        errordlg(['Analysis signal must be same size as original signal to be overwritten.'])
    end
else
    %Get Raw Signal
    analysisTime=tm;
    analysisSignal=signal(:,ind);
    analysisUnits=strsplit(info(ind).Gain,'/');
    if(length(analysisUnits)>1)
        analysisUnits=analysisUnits{2};
    else
        analysisUnits=[];
    end
    Fs=double(info(ind).SamplingFrequency);
    analysisYAxis=[];
    specEstimation=[];
    switch str
        case 'Plot Raw Signal'
            %default to plot after switch
        case 'Apply General Filter'
            [analysisSignal]=wfdbFilter(analysisSignal,Fs);
        case '60/50 Hz Notch Filter'
            [analysisSignal]=wfdbNotch(analysisSignal,Fs);
        case 'Resonator Filter'
            [analysisSignal]=wfdbResonator(analysisSignal,Fs);
        case 'Savitzky-Golay Filter'
            [analysisSignal]=wfdbSgolayfilt(analysisSignal);
        case 'Custom Function'
            try
                [analysisSignal,analysisTime]=wfdbFunction(analysisSignal,analysisTime,Fs);
            catch
                errordlg(lasterr)
            end
        case 'Spectogram Analysis'
            [analysisSignal,analysisTime,analysisYAxis,analysisUnits]=wfdbSpect(analysisSignal,Fs);
        case 'Wavelets Analysis'
            [analysisSignal,analysisYAxis,analysisUnits]=wfdbWavelets(analysisSignal,Fs);
        case 'Spatial PCA'
            [analysisSignal,analysisUnits]=wfdbPCA(signal);
        case 'Harmonic Filter'
            [analysisSignal]=wfdbHarmonicFilter(analysisSignal,Fs);
        case 'Karhunen-Loeve Expansion'
            [analysisSignal,analysisUnits]=wfdbKL(analysisSignal);
        case 'Track Fundamental'
            [analysisSignal,analysisUnits]=wfdbF1Track(analysisSignal,Fs);
        case 'Spectral Estimation'
            specEstimation=wfdbPwelch();
            specEstimation.sigInd=ind;
            specEstimation.Fs=Fs;
        case 'Spectral Coherence'
            specEstimation=wfdbCohere();
            specEstimation.sigInd=ind;
            specEstimation.Fs=Fs;
        case 'Export as Separate Figure'
            exportFigure=1;
        case 'Add Analyzed Signal'
            try
                signal(:,end+1)=analysisSignal;
            catch
                errordlg(lasterr);
            end
            info(end+1).Description='Analyzed Signal';
            signalDescription=cell(R,1);
            for r=R:-1:1
                signalDescription(r)={info(r).Description};
            end
            set(handles.signalList,'String',signalDescription)
        case 'Delete Selected Signal'
            info(ind)=[];
            signal(:,ind)=[];
            signalDescription=cell(R,1);
            for r=R:-1:1
                signalDescription(r)={info(r).Description};
            end
            set(handles.signalList,'String',signalDescription)
            wfdbplot(handles);
    end
end
if(~isempty(analysisSignal) || ~isempty(specEstimation))
    %If analysisSignal is empty, command has been cancelled, else go ahead
    %and plot
    wfdbplot(handles);
end

% --- Executes during object creation, after setting all properties.
function SignalMenu_CreateFcn(hObject, eventdata, handles)
% hObject    handle to SignalMenu (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: popupmenu controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on selection change in signalList.
function signalList_Callback(hObject, eventdata, handles)
% hObject    handle to signalList (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: contents = cellstr(get(hObject,'String')) returns signalList contents as cell array
%        contents{get(hObject,'Value')} returns selected item from signalList


% --- Executes during object creation, after setting all properties.
function signalList_CreateFcn(hObject, eventdata, handles)
% hObject    handle to signalList (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: popupmenu controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


function [annType,annSubtype,annChan,annNum,annComments]=getAnnFields()
%Set Low-pass default values
persistent dlgParam

if(isempty(dlgParam))
    dlgParam.prompt={'Annotation Type:', 'Annotation Subtype:','Annotation Channel:','Annotation Number:'...
        'Annotation Commentes:'};
    dlgParam.annType='N';
    dlgParam.annSubtype='0';
    dlgParam.annChan='1';
    dlgParam.annNum='0';
    dlgParam.annComments='';
    dlgParam.name='Enter Optional Annotation Info';
    dlgParam.numlines=1;
end

answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines,{dlgParam.annType,...
    dlgParam.annSubtype,dlgParam.annChan,dlgParam.annNum,dlgParam.annComments});

dlgParam.annType=answer{1};
dlgParam.annSubtype=answer{2};
dlgParam.annChan=answer{3};
dlgParam.annNum=answer{4};
dlgParam.annComments=answer{5};

annType=answer{1};
annSubtype=num2str(answer{2});
annChan=num2str(answer{3});
annNum=num2str(answer{4});
annComments=answer(5);


function [analysisSignal]=wfdbFilter(analysisSignal,Fs)

%Set Low-pass default values
persistent dlgParam

if(isempty(dlgParam))
    dlgParam.prompt={'Filter Design Function (should return "a" and "b", for use by FILTFILT ):'};
    dlgParam.answer=['Fs= ' num2str(Fs) ';b=fir1(48,[10 40]./(0.5*Fs));a=1;'];
    dlgParam.name='Filter Design Command';
    dlgParam.numlines=1;
end

answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines,{dlgParam.answer});

if(isempty(answer))
    analysisSignal=[];
    return;
end
h = waitbar(0,'Filtering Data. Please wait...');
dlgParam.answer=answer{1};
try
    eval([dlgParam.answer ';']);
    analysisSignal=filtfilt(b,a,double(analysisSignal));
catch
    errordlg(['Unable to filter data! Error: ' lasterr])
end
close(h)

function x=wfdbPwelch()

persistent specEstimation
if(isempty(specEstimation))
    specEstimation.prompt={'Window Size','Samples that Overlap','FFT Size'...
        'Plot scale (linear, semilogx, semilogy, loglog)'};
    specEstimation.WINDOW='[]';
    specEstimation.NOVERLAP='[]';
    specEstimation.NFFT='[]';
    specEstimation.scale='semilogy';
    specEstimation.name='PWLECH Spectral Estimation Parameters';
    specEstimation.numlines=1;
end

answer=inputdlg(specEstimation.prompt,specEstimation.name,specEstimation.numlines,...
    {specEstimation.WINDOW, specEstimation.NOVERLAP,specEstimation.NFFT, specEstimation.scale});
specEstimation.WINDOW = answer{1};
specEstimation.NOVERLAP= answer{2};
specEstimation.NFFT= answer{3};
specEstimation.scale= answer{4};

x.WINDOW =str2num(answer{1});
x.NOVERLAP=str2num(answer{2});
x.NFFT= str2num(answer{3});
x.scale= answer{4};
x.isCohere=0;

function x=wfdbCohere()

persistent specEstimation
if(isempty(specEstimation))
    specEstimation.prompt={'Window Size','Samples that Overlap','FFT Size'...
        'Plot scale (linear, semilogx, semilogy, loglog)','Index of Second Signal:'};
    specEstimation.WINDOW='[]';
    specEstimation.NOVERLAP='[]';
    specEstimation.NFFT='[]';
    specEstimation.scale='linear';
    specEstimation.ind2='1';
    specEstimation.name='PWLECH Spectral Estimation Parameters';
    specEstimation.numlines=1;
end

answer=inputdlg(specEstimation.prompt,specEstimation.name,specEstimation.numlines,...
    {specEstimation.WINDOW, specEstimation.NOVERLAP,specEstimation.NFFT,...
    specEstimation.scale,specEstimation.ind2});
specEstimation.WINDOW = answer{1};
specEstimation.NOVERLAP= answer{2};
specEstimation.NFFT= answer{3};
specEstimation.scale= answer{4};
specEstimation.ind2= answer{5};

x.WINDOW =str2num(answer{1});
x.NOVERLAP=str2num(answer{2});
x.NFFT= str2num(answer{3});
x.scale= answer{4};
x.ind2= str2num(answer{5});
x.isCohere=1;



function [analysisSignal]=wfdbNotch(analysisSignal,Fs)
% References:
% *Rangayyan (2002), "Biomedical Signal Analysis", IEEE Press Series in BME
%
% *Hayes (1999), "Digital Signal Processing", Schaum's Outline
%Set Low-pass default values

persistent dlgParam

if(isempty(dlgParam))
    dlgParam.prompt={'Control Paramter (0 < r < 1 ):','Notch Frequency (Hz):'};
    dlgParam.r='0.995';
    dlgParam.fn='60';
    dlgParam.name='Notch Filter Design';
    dlgParam.numlines=1;
end

answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines,...
    {dlgParam.r, dlgParam.fn});
if(isempty(answer))
    analysisSignal=[];
    return;
end

h = waitbar(0,'Filtering Data. Please wait...');
dlgParam.r = answer{1};   % Control parameter. 0 < r < 1.
dlgParam.fn= answer{2};

r = str2num(dlgParam.r);   % Control parameter. 0 < r < 1.
fn= str2num(dlgParam.fn);

cW = cos(2*pi*fn/Fs);
b=[1 -2*cW 1];
a=[1 -2*r*cW r^2];
try
    eval([answer{1} ';']);
    analysisSignal=filtfilt(b,a,double(analysisSignal));
catch
    errordlg(['Unable to filter data! Error: ' lasterr])
end
close(h)


function [analysisSignal]=wfdbResonator(analysisSignal,Fs)
% References:
% *Rangayyan (2002), "Biomedical Signal Analysis", IEEE Press Series in BME
%
% *Hayes (1999), "Digital Signal Processing", Schaum's Outline
%Set Low-pass default values

persistent dlgParam

if(isempty(dlgParam))
    dlgParam.prompt={'Resonating Frequency (Hz):','Q factor:'};
    dlgParam.fn=num2str(Fs/5);
    dlgParam.K='50';
    dlgParam.name='Resonator Filter Design';
    dlgParam.numlines=1;
end

answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines,...
    {dlgParam.fn,dlgParam.fn});
if(isempty(answer))
    analysisSignal=[];
    return;
end


h = waitbar(0,'Filtering Data. Please wait...');
dlgParam.fn= answer{1};
dlgParam.K= answer{2};

%Similar  to 'Q1' but more accurate
%For details see IEEE SP 2008 (5), pg 113
K=str2num(dlgParam.K);
fn=str2num(dlgParam.fn);
beta=1+K;
f=pi*fn/Fs;
numA=tan(pi/4 - f);
denA=sin(2*f)+cos(2*f)*numA;
A=numA/denA;
b=[1 -2*A A.^2];
a=[ (beta + K*(A^2)) -2*A*(beta+K) ((A^2)*beta + K)];

try
    eval([answer{1} ';']);
    analysisSignal=filtfilt(b,a,double(analysisSignal));
catch
    errordlg(['Unable to filter data! Error: ' lasterr])
end
close(h)


function [analysisSignal]=wfdbHarmonicFilter(analysisSignal,Fs)
% References:
% *Rangayyan (2002), "Biomedical Signal Analysis", IEEE Press Series in BME
%
% *Hayes (1999), "Digital Signal Processing", Schaum's Outline
%Set Low-pass default values

persistent dlgParam

if(isempty(dlgParam))
    dlgParam.prompt={'Fundamental Frequency (Hz). If empty, will be estimated:','Stop Frequency (Hz)','Q factor:'};
    dlgParam.fn='';
    dlgParam.stop=num2str(Fs);
    dlgParam.K='50';
    dlgParam.name='Harmonic Filter Design';
    dlgParam.numlines=1;
end
answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines,...
    {dlgParam.fn,dlgParam.stop,dlgParam.K});
if(isempty(answer))
    analysisSignal=[];
    return;
end


h = waitbar(0,'Filtering Data. Please wait...');
dlgParam.fn= answer{1};
dlgParam.stop=answer{2};
dlgParam.K= answer{3};

if(isempty(dlgParam.fn))
    %Estimate fundamental from spectrum
    N=length(analysisSignal);
    [Pxx,F] = pwelch(analysisSignal,N,0,[],Fs);
    [~,minInd]=min(abs(F-1));% Kill all frequencies below 1 Hz
    L=length(Pxx);
    [acor] = xcorr(Pxx-mean(Pxx));
    acor(1:L-1)=[];
    %Find where first peak stops
    ind=find(sign(diff(acor))>0);
    acor(1:max(ind,minInd))=0;
    [~,offset]=max(acor);
    fn=F(offset);
    dlgParam.fn=num2str(fn); %Store in dialog box
end

%Similar  to 'Q1' but more accurate
%For details see IEEE SP 2008 (5), pg 113
K=str2num(dlgParam.K);
fn=str2num(dlgParam.fn);
stop=str2num(dlgParam.stop);
beta=1+K;
M=floor(stop/fn);
A=[];
B=[];
for i=1:M
    f=pi*fn/Fs;
    numA=tan(pi/4 - f);
    denA=sin(2*f)+cos(2*f)*numA;
    c=numA/denA;
    b=[1 -2*c c.^2];
    a=[ (beta + K*(c^2)) -2*c*(beta+K) ((c^2)*beta + K)];
    B=[B b];
    A=[A a];
end

try
    analysisSignal=filtfilt(B,A,analysisSignal);
catch
    errordlg(['Unable to filter data! Error: ' lasterr])
end
close(h)



function [analysisSignal]=wfdbSgolayfilt(analysisSignal)
persistent dlgParam
if(isempty(dlgParam))
    dlgParam.prompt={'Polynomial Order (K)','Window Size in samples (F):'};
    dlgParam.K='3';
    dlgParam.F='21';
    dlgParam.name='Savitzky-Golay Filtering Options';
    dlgParam.numlines=1;
end

answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines,...
    {dlgParam.K,dlgParam.F});
if(isempty(answer))
    analysisSignal=[];
    return;
end


h = waitbar(0,'Filtering Data. Please wait...');
dlgParam.K= answer{1};
dlgParam.F= answer{2};

%Similar  to 'Q1' but more accurate
%For details see IEEE SP 2008 (5), pg 113
K=str2num(dlgParam.K);
F=str2num(dlgParam.F);
try
    analysisSignal=sgolayfilt(analysisSignal,K,F);
catch
    errordlg(['Unable to filter data! Error: ' lasterr])
end
close(h)

function [analysisSignal,analysisTime]=wfdbFunction(analysisSignal,analysisTime,Fs)

persistent dlgParam

if(isempty(dlgParam))
    dlgParam.prompt={'Custom Function must output variables ''analysisSignal'' and ''analysisTime'''};
    dlgParam.answer={'[analysisSignal,analysisTime]=foo(analysisSignal,analysisTime,Fs)'};
    dlgParam.name='Evaluate Command:';
    dlgParam.numlines=1;
end

answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines,dlgParam.answer);
if(isempty(answer))
    analysisSignal=[];
    analysisTime=[];
    return;
end

dlgParam.answer=answer(1);
h = waitbar(0,'Executing code on signal. Please wait...');
try
    eval([dlgParam.answer{1} ';']);
catch
    errordlg(['Error: ' lasterr])
end
close(h)


function [analysisSignal,analysisTime,analysisYAxis,analysisUnits]=wfdbSpect(analysisSignal,Fs)

persistent dlgParam
if(isempty(dlgParam))
    dlgParam.prompt={'window size','overlap size','Min Frequency (Hz)','Max Frequency (Hz)','colormap'};
    dlgParam.window=2^10;
    dlgParam.minY= 0;
    dlgParam.maxY= floor(Fs/2);
    dlgParam.noverlap=round(dlgParam.window/2);
    dlgParam.map='jet';
    dlgParam.name='Spectogram Parameters';
    dlgParam.numlines=1;
end

dlgParam.defaultanswer={num2str(dlgParam.window),num2str(dlgParam.noverlap),...
    num2str(dlgParam.minY),num2str(dlgParam.maxY),dlgParam.map};

answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines,dlgParam.defaultanswer);
if(isempty(answer))
    analysisSignal=[];
    analysisUnits=[];
    analysisYAxis=[];
    analysisTime=[];
    return;
end

h = waitbar(0,'Calculating spectogram. Please wait...');
dlgParam.window= str2num(answer{1});
dlgParam.noverlap= str2num(answer{2});
analysisYAxis.minY= str2num(answer{3});
analysisYAxis.maxY= str2num(answer{4});
analysisYAxis.map=answer{5};

dlgParam.minY=analysisYAxis.minY;
dlgParam.maxY=analysisYAxis.maxY;
dlgParam.map=analysisYAxis.map;

[~,F,analysisTime,analysisSignal] = spectrogram(analysisSignal,dlgParam.window,...
    dlgParam.noverlap,dlgParam.window,Fs,'yaxis');

analysisSignal=10*log10(abs(analysisSignal));
analysisYAxis.values=F;
analysisUnits='Frequency (Hz)';
close(h)


function [analysisSignal,analysisYAxis,analysisUnits]=wfdbWavelets(analysisSignal,Fs)

persistent dlgParam
if(isempty(dlgParam))
    dlgParam.prompt={'wavelet','scales','colormap','logScale'};
    dlgParam.wavelet='coif2';
    dlgParam.scales='1:28';
    dlgParam.map='jet';
    dlgParam.log='false';
    dlgParam.name='Wavelet Parameters';
    dlgParam.numlines=1;
end

dlgParam.defaultanswer={num2str(dlgParam.wavelet),num2str(dlgParam.scales),dlgParam.map,dlgParam.log};

answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines,dlgParam.defaultanswer);
if(isempty(answer))
    analysisSignal=[];
    analysisUnits=[];
    analysisYAxis=[];
    return;
end

h = waitbar(0,'Calculating wavelets. Please wait...');
dlgParam.wavelet= answer{1};
dlgParam.scales = str2num(answer{2});
dlgParam.map= answer{3};
dlgParam.log= answer{4};
analysisYAxis.minY= dlgParam.scales(1);
analysisYAxis.maxY= dlgParam.scales(end);
analysisYAxis.map=dlgParam.map;
analysisYAxis.isImage=1;

coefs = cwt(analysisSignal,dlgParam.scales,dlgParam.wavelet);
analysisSignal = wscalogram('',coefs);
if(strcmp(dlgParam.log,'true'))
    analysisSignal=log(analysisSignal);
end
analysisYAxis.values=dlgParam.scales;
analysisUnits='Scale';
close(h)

function [analysisSignal,analysisUnits]=wfdbPCA(signal)

persistent dlgParam

if(isempty(dlgParam))
    dlgParam.M=['1:' num2str(size(signal,2))];
    dlgParam.P='1';
    dlgParam.prompt={'Indices of signals to include in PCA:',...
        'Selecta a Principal Component (equal or less than the number of indices above):'};
    dlgParam.name='Parameters for ploting principal component';
    dlgParam.numlines=1;
end

answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines, {dlgParam.M, ...
    dlgParam.P});
if(isempty(answer))
    analysisSignal=[];
    analysisUnits=[];
    return;
end

dlgParam.M=answer{1};
dlgParam.P=answer{2};
h = waitbar(0,'Estimating K-L Basis. Please wait...');
analysisUnits='Amplitude';

signal=signal(:,str2num(dlgParam.M));
[u,~,~]=svd(signal,0);
ind=str2num(dlgParam.P);
analysisSignal=u(:,ind);
close(h)


function [analysisSignal,analysisUnits]=wfdbKL(signal)

persistent dlgParam
maxM=11;
if(isempty(dlgParam))
    dlgParam.P='1';
    dlgParam.prompt={['Select index of desired Principal Component (<= ' num2str(maxM) ') :']};
    dlgParam.numlines=1;
    dlgParam.name='Parameters for K-L Expansion';
end

answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines, {dlgParam.P});
if(isempty(answer))
    analysisSignal=[];
    analysisUnits=[];
    return;
end

dlgParam.P=answer{1};
h = waitbar(0,'Estimating fundamental frequency. Please wait...');
analysisUnits='Amplitude';

signal=signal-mean(signal);
ind=num2str(dlgParam.P);
R=corrmtx(signal,maxM);
[u,s,v]=svd(R,0);
N=length(signal);
ind=str2num(dlgParam.P);
analysisSignal=u(1:N,ind);
close(h)

function [tm,signal,info,ann, annLabels]=loadWorkspaceRecord(handles)

dlgParam.prompt={'Enter List of variable names (ie: x,y,z )', ...
    'Sampling Frequency in Hz (ie: 250)',...
    'Enter list of label names (ie: ECG,BP,EEG} )',...
    'Enter Annotation name: '};

dlgParam.name='Select signals to load from workspace:';
dlgParam.numlines=1;

answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines);

%Convert variables to format expected by the GUI
varnames=regexp(answer{1},',','split');
tentative_Fs=answer{2};
Fs=[];
try
    Fs=str2num(tentative_Fs);
catch
    %Maybe it is a variable, evaluate it! 
    Fs=evalin('base',tentative_Fs);
end

tags=regexp(answer{3},',','split');
M=length(varnames);
signal=[];
info=[];
ann=[];
annLabels=[];
if(length(answer{4})>1)
    ann=evalin('base',answer{4});
    if(~isempty(ann))
        wfdbShowAnn1Labels(1)
        [annType,annSubtype,annChan,annNum,annComments]=getAnnFields();
        annLabels=struct([]);
        for n=1:length(ann)
            annLabels(end+1).type=annType;
            annLabels(end).subtype=annSubtype;
            annLabels(end).chan=annChan;
            annLabels(end).num=annNum;
            annLabels(end).comment=annComments;
        end
        
    end
end
signalDescription=cell(M,1);
for m=1:M
    signal(:,m)=evalin('base',varnames{m});
    info(m).Description=tags{m};
    info(m).SamplingFrequency=Fs;
    info(m).Gain='1';
    info(m).Baseline=0;
    signalDescription(m)={info(m).Description};
end
N=length(signal);
tm=[0:N-1]'./Fs;
set(handles.signalList,'String',signalDescription)

function [analysisSignal,analysisUnits]=wfdbF1Track(analysisSignal,Fs)

persistent dlgParam

if(isempty(dlgParam))
    dlgParam.prompt={'Order of how many harmonics to track:',...
        'Initial guess of the fundamental frequency (Hz)', 'Step size of the LMS algorithm',...
        'Magnitude of the pole for the harmonic comb filter'};
    dlgParam.P='3';
    dlgParam.theta='[]';
    dlgParam.mu='10^-3';
    dlgParam.r='0.85';
    dlgParam.answer={'[analysisSigal,analysisTime]=foo(analysisSignal,analysisTime,Fs)'};
    dlgParam.name='Parameters for tracking fundamental frequency';
    dlgParam.numlines=1;
end

answer=inputdlg(dlgParam.prompt,dlgParam.name,dlgParam.numlines, {dlgParam.P, ...
    dlgParam.theta,dlgParam.mu,dlgParam.r});

if(isempty(answer))
    analysisSignal=[];
    analysisUnits=[];
    return;
end

dlgParam.P=answer{1};
dlgParam.theta=answer{2};
dlgParam.mu=answer{3};
dlgParam.r=answer{4};

if(strcmp(dlgParam.theta,'[]'))
    %use peak on low range of FFT as an estimate
    [Pxx,F] = pwelch(analysisSignal,[],[],[],Fs);
    FUP=100;
    [~,maxInd]=min(abs(F-FUP));
    [~,maxP]=max(Pxx(1:maxInd));
    dlgParam.theta=num2str(F(maxP));
end

h = waitbar(0,'Estimating fundamental frequency. Please wait...');
analysisSignal=harmonic_est(analysisSignal,str2num(dlgParam.P),Fs,...
    str2num(dlgParam.theta),str2num(dlgParam.mu),str2num(dlgParam.r));
analysisUnits='Hz';
close(h)

function wfdbShowAnn1Labels(promptMe)
global ann1LabelsDisplaySetting
if(~isfield(ann1LabelsDisplaySetting,'prompt'))
    ann1LabelsDisplaySetting.prompt={'Ann1 Types (if empty, show all types, if -1 don''t display):',...
        'Show Comments (true/false):','Display label on original channel only (true/false):',...
        'Display Label Types & Comments when there are at most N labels:', ...
        'Display abnormal labels only (true/false):'};
    ann1LabelsDisplaySetting.showType='[]';
    ann1LabelsDisplaySetting.showComment='true';
    ann1LabelsDisplaySetting.channelSpecific='false';
    ann1LabelsDisplaySetting.threshold='100';
    ann1LabelsDisplaySetting.abnormalOnly='false';
    ann1LabelsDisplaySetting.name='Displays Ann1 Type and Comments';
    ann1LabelsDisplaySetting.numlines=1;
end
if(promptMe)
    answer=inputdlg(ann1LabelsDisplaySetting.prompt,ann1LabelsDisplaySetting.name,ann1LabelsDisplaySetting.numlines,...
        {ann1LabelsDisplaySetting.showType,ann1LabelsDisplaySetting.showComment, ann1LabelsDisplaySetting.channelSpecific,...
        ann1LabelsDisplaySetting.threshold,ann1LabelsDisplaySetting.abnormalOnly});
    ann1LabelsDisplaySetting.showType= answer{1};
    ann1LabelsDisplaySetting.showComment= answer{2};
    ann1LabelsDisplaySetting.channelSpecific=answer{3};
    ann1LabelsDisplaySetting.threshold=answer{4};
    ann1LabelsDisplaySetting.abnormalOnly=answer{5};
end


function theta_curve=harmonic_est(x,varargin)
%
%[theta_curve]=harmonic_est(x,P,Fs,theta,mu,r)
% Implements harmonic frequency tracking algorithm as described in
% IEEE Signal Processing Magazine (189) 11/09 by Tan and Jiang.
% Parameters are:
%
% Input:
%     x     - (Nx1, required) Signal to be tracked
%     P     - (1x1, required) Order of how many harmonics (including fundamental)
%              to be estimated.
%     Fs    - (1x1, optional) Sampling frequency (Hz). If present output is returned
%             in Hertz, if absent output is returned in radians.
%     theta - (1x1, optional) Initial guess of the fundamental frequency capture range.
%     mu    - (1x1, optional) Step size of the LMS algorithm.
%     r     - (1x1, optinoal) Magnitude of the pole for the harmonic comb filter
%             (0<r<1).
%
% Output:
%     theta -       (1x1,required) Final estimate of the fundamental frequency.
%     theta_curve - (Nx1,optional) Tracking curve of the instantenous fundamental
%                                  frequency.
%     b-            (1xP,optional) b coefficients of the comb filter.
%     a-            (1xP,optional) a coefficients fo the comb filter.
%
%
% %Example
% clear all;close all;clc
% N=1200;
% Fs=8000;
% tm=[0:1/Fs:(N-1)/Fs]';
% t=[0:400:N]+1;
% snr=10^(-18/20);
% F=[1000 1075 975];
% x=[];
% true=[];
% for i=1:3
%     T=2*pi*F(i).*tm(t(i):t(i+1)-1);
%     sig=sin(T)+0.5*cos(T*2)+0.25*cos(T*3)+randn(N/3,1).*snr;
%     x=[x;sig];
%     true=[true;ones(N/3,1)*F(i)];
% end
%
% [theta,theta_curve,b,a]=harmonic_est(x,3,Fs);
% subplot(211)
% plot(tm,theta_curve)
% hold on
% plot(tm,true,'r--','LineWidth',3)
% grid on
% xlabel('Time')
% ylabel('Fundamental Frequency Estimate')
% legend('Tracking','True')
% subplot(212)
% [H,F]=freqz(b,a,N,Fs);
% plot(F,log10(abs(H)))
% title('Final Comb Filter')
% xlabel('Frequency')
% ylabel('Magnitude')
%%%  Written by Ikaro Silva, 2010


[N,M]=size(x);
step=500;
r= 0.85;
Fs=[];
theta=[];
mu=10^-3;


P=varargin{1}; %required
if(nargin>2 && ~isempty(varargin{2}) )
    Fs=varargin{2};
end
if(nargin>3 && ~isempty(varargin{3}) )
    theta=varargin{3};
end
if(nargin>4 && ~isempty(varargin{4}) )
    mu=varargin{4};
end
if(nargin>5 && ~isempty(varargin{5}) )
    r=varargin{5};
end

THETA=linspace(0,pi/P,step);
F=THETA;
Ntheta=length(THETA);
MSE=zeros(Ntheta,1);
MSE1=zeros(Ntheta,1);

if(isempty(theta))
    %Default guess to a frequency on the low range
    theta=60;
end

%Step 1- Convert theta to radians
theta=theta*2*pi/Fs;

%Step 2 - Apply LMS to optimize Theta
beta=zeros(P+1,1);
ym=zeros(P+1,1);
theta_curve=zeros(N,1)+NaN;
ym_old=zeros(P+1,2);
beta_old=zeros(P+1,2);
ym_const=zeros(P+1,2);
ym_old(1,:)=[0 0];

for n=1:N
    
    ym=rec_step(ym_old,ym_const,x(n),theta,P+1,r);
    beta=rec_step(beta_old,ym_old(:,1),0,theta,P+1,r);
    
    ym_old=[ym ym_old(:,1)];
    beta_old=[beta beta_old(:,1)];
    
    theta_curve(n)=theta;
    theta= theta - 2*mu*ym(end)*beta(end);
end

theta_curve=theta_curve*Fs/(2*pi);



function out = rec_step(out_old,const,init,theta,P,r)

out=zeros(P,1);
out(1)=init;

for p=2:P
    
    w=(p-1)*theta;
    out(p)= out(p-1) - 2*cos(w)*out_old(p-1,1) + ...
        2*(p-1)*sin(w)*const(p-1) + out_old(p-1,2) + ...
        2*r*cos(w)*out_old(p,1) - (r^2)*out_old(p,2) - ...
        2*r*(p-1)*sin(w)*const(p);
end