Lightweight 12-lead ECG viewer for MATLAB 1.0.0

File: <base>/ECG_viewer.m (15,368 bytes)
function ECG_viewer()
    warning('off')
    close all
    
    %% ECG Viewer for phyisians
    % Written by Annabel Li-Pershing in Tereshchenko Lab at OHSU
    % annabel2@pdx.edu;lipershi@ohsu.edu
    
    %% setup labels
    ECG_labels={'I','aVR','V1','V4','II','aVL','V2','V5','III','aVF','V3','V6','V1','II'};
    % GE report format lead order:
    % GE_lead= {'I','II','v1','v2','v3','v4','v5','v6','III','avR','avL','avF'};
    
    %% resolve screen resolution
    % get pixel count
    set(0,'units','pixels')
    pix = get(0,'screensize');
    % get screen size
    set(0,'unit','inches')
    inch= get(0,'screensize');
    res= pix/inch;
    if res == 96
        fontsize = 10;
        label_size = 10;
    elseif res < 96
        fontsize = 8;
        label_size = 10;
    else 
        fontsize = 10;
        label_size = 12;
    end
    
    %% setup variables
    graph_var = struct();
    graph_var.file_id = '';
    
    %% Graphic positioning 
    %(left corner x, left corner y, width, height) at normalize position
    % Plot position
    main_ECG_pos = [0.16,0.41,0.82,0.58];
    scroll_ECG_pos = [0.16,0.09,0.45,0.29];
    scroll_pos = [0.16,0.02,0.45,0.02];
    %  Left Panel Items
    getfile_pos = [0.02,0.7,.12,0.05];
    getfiletxt_pos = [0.02,0.65,0.12,0.03];
    plot_scale_pos = [0.02,0.5,0.12,0.08];
    plot_10sec_btn_pos = [0.02,0.58,0.12,0.05];
    fs_txt_pos = [0.02,0.90,0.08,0.05];
    fs_pos= [0.10,0.92,0.03,0.03];
    amp_txt_pos = [0.02,0.80,0.08,0.05];
    amp_pos= [0.10,0.81,0.03,0.03];
    %% setup graphics
    main_fig = figure('Name', '12 Lead ECG Viewer','Units','normalized','Position',[0 0 0.85 0.85]);
    main_ax = axes('Units','normalized','Position',main_ECG_pos);
    scroll_plt_ax = axes('Units','normalized','Position',scroll_ECG_pos);
    
    %% setup UI items
    get_file_btn = uicontrol('Style','pushbutton','units','normalized',...
                             'position',getfile_pos,'String','Load ECG file',...
                             'FontWeight','bold','FontSize',fontsize+1);
    get_file_txt= uicontrol('Style','text','units','normalized',...
                             'position',getfiletxt_pos,'String',graph_var.file_id,...
                             'FontWeight','bold','FontSize',fontsize);
    slider = uicontrol('Style','slider','Units','normalized',...
                        'Position',scroll_pos);

    scale_txt = uicontrol('Style','text','units','normalized',...
                       'position',plot_scale_pos,'String','scale: 100uV/box,40ms/box',...
                       'FontWeight','bold','FontSize',fontsize+1);
    plot_10sec_btn = uicontrol('Style','pushbutton','Units','normalized',...
                         'Position',plot_10sec_btn_pos,'String','Select Lead',...
                         'FontWeight','bold','FontSize',fontsize);
    fs_input = uicontrol('Style','edit','units','normalized',...
                       'position',fs_pos,'String',num2str(500),...
                       'FontWeight','bold','FontSize',fontsize);
    fs_txt = uicontrol('Style','text','units','normalized',...
                       'position',fs_txt_pos,'String','Sampling Frequency :  ',...
                       'FontWeight','bold','FontSize',fontsize);
    amp_input = uicontrol('Style','edit','units','normalized',...
                       'position',amp_pos,'String',num2str(1),...
                       'FontWeight','bold','FontSize',fontsize);
    amp_txt = uicontrol('Style','text','units','normalized',...
                       'position',amp_txt_pos,'String','Amplitude Resolution (uV) :  ',...
                       'FontWeight','bold','FontSize',fontsize);               
                   
                   
    %% ui callback 
    get_file_btn.Callback = @(src,eventdata)get_file(src,eventdata);
    slider.Callback = @(src,eventdata)scroll(src,eventdata);
    plot_10sec_btn.Callback = @(src,eventdata)plot_10sec(src,eventdata);
    
    %% ui handling function
    function get_file(src,eventdata)
        % get file txt
        [txtFile, txtPath] = uigetfile('*.txt*','Please select files for viewing');
        graph_var.ecg = read_txt(fullfile(txtPath,txtFile));
        graph_var.fullpath = fullfile(txtPath,txtFile);
        plot_ecg(src,eventdata)
        filename = strsplit(txtFile,'.');
        graph_var.file_id = filename{1};
        get_file_txt.String = filename{1};
        
    end

    function plot_ecg(src,eventdata)
        graph_var.fs = str2double(fs_input.String);
        % extract data from ecg textfile
        graph_var.ecg_mat = read_txt(graph_var.fullpath);
        % join ecg data with offset based on the scale
        graph_var.ecg_pink = ecg_2sec(graph_var.ecg_mat);
        graph_var.amp = str2double(amp_input.String);
        % clear the scroll axis
        cla(scroll_plt_ax)
        % set current main axes and plot
        axes(main_ax)
        plot(graph_var.ecg_pink,'k')
        hold on
        % make major plot grid
        ylim = main_ax.YLim;
        xlim = main_ax.XLim;
        main_gridx= [0:0.2*graph_var.fs:10*graph_var.fs;0:0.2*graph_var.fs:10*graph_var.fs];
        main_gridy = [ylim(1):500/graph_var.amp:ylim(2),ylim(1):500/graph_var.amp:ylim(2)];
        minor_gridx= [0:0.04*graph_var.fs:10*graph_var.fs;0:0.04*graph_var.fs:10*graph_var.fs];
        minor_gridy = [ylim(1):100/graph_var.amp:ylim(2),ylim(1):100/graph_var.amp:ylim(2)];
        % plot grid major and minor
        % colour code [r g b alpha]
        plot(main_gridx,[ones(1,size(main_gridx,2))*ylim(1);ones(1,size(main_gridx,2))*ylim(2)],...
             'Color',[1 0 0 0.4],'LineWidth',1)
        plot(minor_gridx,[ones(1,size(minor_gridx,2))*ylim(1);ones(1,size(minor_gridx,2))*ylim(2)],...
             'Color',[1 0 0 0.2],'LineWidth',0.01)
        plot([ones(1,size(main_gridy,2))*0;ones(1,size(main_gridy,2))*10*graph_var.fs],...
             [main_gridy;main_gridy],'Color',[1 0 0 0.4],'LineWidth',1)
        plot([ones(1,size(minor_gridy,2))*0;ones(1,size(minor_gridy,2))*10*graph_var.fs],...
             [minor_gridy;minor_gridy],'Color',[1 0 0 0.2],'LineWidth',0.01)

        % turn tick off
        set(main_ax,'TickLength',[0 0])
        main_ax.XTickLabel  = '';
        main_ax.YTickLabel  = '';
        
        n = 20; % the x axis text location shift @ first label columns
        m = 0; % shift of x axis location @ the rest of the labels
        seg_length = xlim(2)-xlim(1);
        x = [n,m+round(seg_length/4),m+round(seg_length/2),m+round(seg_length/4*3),...
            n,m+round(seg_length/4),m+round(seg_length/2),m+round(seg_length/4*3),...
            n,m+round(seg_length/4),m+round(seg_length/2),m+round(seg_length/4*3),n,n];
        % Make the y location based on the data's baseline
        plot_baseline = mean(graph_var.ecg_pink(2:30,:))-200;
        y = [[1 1 1 1].*plot_baseline(1),[1 1 1 1].*plot_baseline(2),...
             [1 1 1 1].*plot_baseline(3),plot_baseline(4),plot_baseline(5)];
        text(x,y,ECG_labels,'FontSize',label_size,'FontWeight','bold')
        
        % set y based on the baseline estimate. 
%         y_max = max(main_ax.YTick);
%         y = [(y_max/6*5-150),(y_max/6*5-150),(y_max/6*5-150),(y_max/6*5-150),...
%             (y_max/3*2-150),(y_max/3*2-150),(y_max/3*2-150),(y_max/3*2-150),...
%             (y_max/2-150),(y_max/2-150),(y_max/2-150),(y_max/2-150),...
%             (y_max/3-150),(y_max/6-150)];
        hold off
        main_ax.YLim(2) = max(max(graph_var.ecg_pink))+200;
        main_ax.YLim(1) = max(min(min(graph_var.ecg_pink)),min(min(graph_var.ecg_pink))-200);
        graph_var.x = x;
        graph_var.y = y;

    end
    
    function plot_10sec(src,eventdata)
%         lead_name = str2double(inputdlg('Please Enter the lead name'));
        [xx,yy] = ginput(1);
        % plot 10 second data of one lead
        % update when click is within the areas of the plots
        
        %{
        location for the plot 
        I(1)    avr(2)    v1(3)    v4(4)
        II(5)   avl(6)    v2(7)    v5(8)
        III(9)  avf(10)   v3(11)   v6(12)
        v1(13)
        II(14)
        %}
        
        ymin = 200;
        ymax = 400;
        xmin = graph_var.x(1)+10;
        xmax = 500;
        if xx<graph_var.x(1)+xmax && xx>graph_var.x(1)-xmin && yy<graph_var.y(1)+ymax &&yy>graph_var.y(1)-ymin 
            % lead I
            graph_var.ecg = graph_var.ecg_mat(:,1); 
        elseif xx<graph_var.x(5)+xmax && xx>graph_var.x(5)-xmin && yy<graph_var.y(5)+ymax &&yy>graph_var.y(5)-ymin 
            % lead II
            graph_var.ecg = graph_var.ecg_mat(:,2); 
        elseif xx<graph_var.x(3)+xmax && xx>graph_var.x(3)-xmin && yy<graph_var.y(3)+ymax &&yy>graph_var.y(3)-ymin 
            % lead v1
            graph_var.ecg = graph_var.ecg_mat(:,3);
        elseif xx<graph_var.x(7)+xmax && xx>graph_var.x(7)-xmin && yy<graph_var.y(7)+ymax &&yy>graph_var.y(7)-ymin 
            % lead v2
            graph_var.ecg = graph_var.ecg_mat(:,4);
        elseif xx<graph_var.x(11)+xmax && xx>graph_var.x(11)-xmin && yy<graph_var.y(11)+ymax &&yy>graph_var.y(11)-ymin  
            % lead v3
            graph_var.ecg = graph_var.ecg_mat(:,5);
        elseif xx<graph_var.x(4)+xmax && xx>graph_var.x(4)-xmin && yy<graph_var.y(4)+ymax &&yy>graph_var.y(4)-ymin
            % lead v4
            graph_var.ecg = graph_var.ecg_mat(:,6);
        elseif xx<graph_var.x(8)+xmax && xx>graph_var.x(8)-xmin && yy<graph_var.y(8)+ymax &&yy>graph_var.y(8)-ymin 
            % lead v5
            graph_var.ecg = graph_var.ecg_mat(:,7);
        elseif xx<graph_var.x(12)+xmax && xx>graph_var.x(12)-xmin && yy<graph_var.y(12)+ymax &&yy>graph_var.y(12)-ymin 
            % lead v6
            graph_var.ecg = graph_var.ecg_mat(:,8);
        elseif xx<graph_var.x(9)+xmax && xx>graph_var.x(9)-xmin && yy<graph_var.y(9)+ymax &&yy>graph_var.y(9)-ymin 
            % lead III
            graph_var.ecg = graph_var.ecg_mat(:,9);
        elseif xx<graph_var.x(2)+xmax && xx>graph_var.x(2)-xmin && yy<graph_var.y(2)+ymax &&yy>graph_var.y(2)-ymin 
            % lead avR
            graph_var.ecg = graph_var.ecg_mat(:,10);
        elseif xx<graph_var.x(6)+500 && xx>graph_var.x(6)+500 && yy<graph_var.y(6)+500 &&yy>graph_var.y(6)+500 
            % lead avL
            graph_var.ecg = graph_var.ecg_mat(:,11);
        elseif xx<graph_var.x(10)+xmax && xx>graph_var.x(10)-xmin && yy<graph_var.y(10)+ymax &&yy>graph_var.y(10)-ymin 
            % lead avF
            graph_var.ecg = graph_var.ecg_mat(:,12);
        elseif xx<graph_var.x(13)+xmax && xx>graph_var.x(13)-xmin && yy<graph_var.y(13)+ymax &&yy>graph_var.y(13)-ymin   
            % lead v1
            graph_var.ecg = graph_var.ecg_mat(:,3);
        elseif xx<graph_var.x(14)+xmax && xx>graph_var.x(14)-xmin && yy<graph_var.y(14)+ymax &&yy>graph_var.y(14)-ymin 
            % lead II
            graph_var.ecg = graph_var.ecg_mat(:,2);
        end
        % place holder for selecting based on the click
        axes(scroll_plt_ax)
        x_val = 1:1000;
        plot(x_val./graph_var.fs*1000,graph_var.ecg(1:1000),'k');
        xlabel('Time(ms)')
        ylabel('Voltage (uV)')
        grid(scroll_plt_ax,'on')
        grid(scroll_plt_ax,'minor')
        scroll_plt_ax.GridColor = 'r';
        scroll_plt_ax.MinorGridColor = 'r';
        scroll_plt_ax.GridAlpha = 0.5;
        scroll_plt_ax.MinorGridAlpha = 0.15;
        scroll_plt_ax.MinorGridLineStyle = '-';
    end

    function scroll(src,eventdata)
        % update based on current data value
        slide_val = round(slider.Value*length(graph_var.ecg));
        axes(scroll_plt_ax)
        if slide_val<4002
            x_val = (slide_val+1:min(length(graph_var.ecg),slide_val+1000))./graph_var.fs*1000;
            plot(x_val,graph_var.ecg(slide_val+1:min(size(graph_var.ecg_mat,1),slide_val+1000))./graph_var.amp,'k');
        else
            x_val = (length(graph_var.ecg)-999:size(graph_var.ecg))./graph_var.fs*1000;
            plot(x_val,graph_var.ecg(length(graph_var.ecg)-999:size(graph_var.ecg))./graph_var.amp,'k');
        end
        % set xlim
        scroll_plt_ax.XLim = [x_val(1) x_val(end)];
        xlabel('Time(ms)')
        ylabel('Voltage (uV)')
        grid(scroll_plt_ax,'on')
        grid(scroll_plt_ax,'minor')
        scroll_plt_ax.GridColor = 'r';
        scroll_plt_ax.MinorGridColor = 'r';
        scroll_plt_ax.GridAlpha = 0.5;
        scroll_plt_ax.MinorGridAlpha = 0.15;
        scroll_plt_ax.MinorGridLineStyle = '-';
        
    end
    
    function select_beat(src,eventdata)
    end
    %% appended function
    function ecg_mat = read_txt(txt_path)
        % function that reads in txt file for 12 lead ECG and output matrix
        % of the datafunction read_12lead_med_txt(filename, save_fname)
        delimiter = ' ';
        startRow = 2;
        formatSpec = '%f%f%f%f%f%f%f%f%f%f%f%f%[^\n\r]';
        fileID = fopen(txt_path,'r');
        textscan(fileID, '%[^\n\r]', startRow-1, 'ReturnOnError', false);
        dataArray = textscan(fileID, formatSpec, 'Delimiter', delimiter, 'MultipleDelimsAsOne', true, 'EmptyValue' ,NaN,'ReturnOnError', false);
        fclose(fileID);
        ecg_mat = [dataArray{1:end-1}];
    end
    
    function ecg_12L_2sec = ecg_2sec(ecg_mat)
        mmax = max(max(ecg_mat))*1.3;
        disp(mmax)
        % set plot offset
        if max(max(ecg_mat))*1.3 > 2000
            plot_offset = round(max(max(ecg_mat))*1.3/str2double(amp_input.String));
        elseif max(max(ecg_mat))*1.3 <1500
            plot_offset = round(2000/str2double(amp_input.String));
        else 
            plot_offset = round(2300/str2double(amp_input.String));
        end
        n = round(size(ecg_mat,1)./4)*4;
        % size checking for concatenating the matrix
        if n>size(ecg_mat,1)
            v1 = [ecg_mat(:,3);zeros(n-size(ecg_mat,1),1)];
            II = [ecg_mat(:,2);zeros(n-size(ecg_mat,1),1)];
        elseif n<size(ecg_mat,1)
            v1 = ecg_mat(1:n,3);
            II = ecg_mat(1:n,2);
        else
            v1 = ecg_mat(:,3);
            II = ecg_mat(:,3);
        end
        mm = round(size(ecg_mat,1)./4);
        ecg2sec1 = ecg_mat(1:mm,[1 2 9]); 
        ecg2sec1m = -mean(ecg2sec1);
        %ecg2sec1(1,:) = 800;
        ecg2sec2 = ecg_mat(mm+1:2*mm,[10 11 12]);
        ecg2sec2m = mean(ecg2sec2);
        ecg2sec2(1,:) = 1000;
        ecg2sec3 = ecg_mat(2*mm+1:3*mm,[3 4 5]);
        ecg2sec3m = mean(ecg2sec3);
        ecg2sec3(1,:) = 1000;
        ecg2sec4 = ecg_mat(3*mm+1:end,[6 7 8]);
        ecg2sec4m = mean(ecg2sec4);
        ecg2sec4(1,:) = 1000;

        
        graph_var.ax_mean1 = ecg2sec1m;
        graph_var.ax_mean2 = ecg2sec2m;
        graph_var.ax_mean3 = ecg2sec3m;
        graph_var.ax_mean4 = ecg2sec4m;
        m = 0.75;
        % make an offset matrix of the data for plotting
        ecg_12L_2sec = [[ecg2sec1(:,1)-ecg2sec1m(1);ecg2sec2(:,1)-ecg2sec2m(1);ecg2sec3(:,1)-ecg2sec3m(1);ecg2sec4(:,1)-ecg2sec4m(1)]+plot_offset*5*m,...
                        [ecg2sec1(:,2)-ecg2sec1m(2);ecg2sec2(:,2)-ecg2sec2m(2);ecg2sec3(:,2)-ecg2sec3m(2);ecg2sec4(:,2)-ecg2sec4m(2)]+plot_offset*4*m,...
                        [ecg2sec1(:,3)-ecg2sec1m(3);ecg2sec2(:,3)-ecg2sec2m(3);ecg2sec3(:,3)-ecg2sec3m(3);ecg2sec4(:,3)-ecg2sec4m(3)]+plot_offset*3*m,...
                         v1+plot_offset*2*m,II+plot_offset*1*m];
        
    end

end