Heart Vector Origin Point Detection and Time-Coherent Median Beat Construction 1.0.0

File: <base>/origin_point_github.m (9,800 bytes)
clear
clc
close all
warning('OFF');


% Description: calculate origin point from XYZ lead and generate image of XYZ 
% median beat based on the modified baseline.
% SYNTAX : origin_point_algorithm(file_name,source_folder,image_folder,destination_folder)
% INPUT  : file_name          - file name for the matfile containing XYZ 
%                               median beat detected R peak indexed location.
%          source_folder      - the folder location where the file is placed.
%          image_folder       - the destination folder to place generated image.
%          destination_folder - where the matfile will be placed after 
%                               calculation is done.

% Origin Point Calculation Code V.1.1
% Authors:
%       Erick Andres Perez Alday, PhD, <perezald@ohsu.edu>
%       Annabel Li-Pershing, BS, <annabel2@pdx.edu>
% Principle Investigator:
%       Larisa Tereshchenko, MD, PhD, < tereshch@ohsu.edu > 
% Last update: August 2nd, 2018



% sampling frequency
prompt='What is the sampling frequency in Hz';
dlg_title='Sample Frequency';
fs_d = inputdlg(prompt,dlg_title,1);
fs=str2num(fs_d{1});


% load mat files
[file_name,path_name] = uigetfile('*','Select file for Median beat');

%% file import
file_ID          = strsplit(file_name,'.');
file_path        = fullfile(path_name,file_name);

A= load(file_path);

%% Create Results folder
Results_folder = [path_name, '/', 'Results', '/'];
if (exist(Results_folder,'file') ~= 7)
    mkdir (Results_folder);
end



    %% Load data from matfile
%   A=load(strcat(source_folder,'/',file_name));
    XYZ_M=A.XYZ_M_T;
    Rx=A.Rpeaks(:,1);	
    disp(file_name)
    
    %% Calculate various windows analysis
    w=floor((mean(diff(Rx))*.8)-(372.69*(fs/1000)));

    %% Is the RR interval < 600ms (0.6*fs)
    if mean(diff(Rx))<(600*(fs/1000)) && (A.Label_beat~='S')
	cent=(600-260)*(fs/1000);
    else
	cent=(600-320)*(fs/1000);
    end


    w1_start=cent-w;
    w1_end=cent+w;

    w2_start=cent-(160*(fs/1000));
    w2_end=cent+(160*(fs/1000));

    if w1_start < w2_start
       w1_start=w2_start;
    end

    if w1_end > w2_end
        w1_end=w2_end;
    end

    %% cut xyz median beat based on the window
    xyz_w1 = XYZ_M(w1_start:w1_end,:);
    xyz_w2 = XYZ_M(w2_start:w2_end,:);

    %% calculate origin point with clustering algorithm ori_clustering
    [cluster_pt,~]=ori_clustering(xyz_w1);

    if isnan(cluster_pt)
        [cluster_pt,~]=ori_clustering(xyz_w2);
        Ori_pt(1) = cluster_pt+w2_start;
    else 
        Ori_pt(1) = cluster_pt+w1_start;  % convert the calculated index to real time window
    end


    % calculate the first origin point and the modified vector magnitude
    for ii=1:length(XYZ_M(:,1))
        VecMag_M1(ii)=norm([XYZ_M(Ori_pt(1),1)-XYZ_M(ii,1),XYZ_M(Ori_pt(1),2)-XYZ_M(ii,2),XYZ_M(Ori_pt(1),3)-XYZ_M(ii,3)]);
    end


    % calculate the sum of the absolute gradient on XYZ within w1
    [abs_grad_ori,~]=abs_grad_fun(xyz_w1);

    % calcaulate the second origin point
    Ori_pt2(1)=abs_grad_ori+w1_start;

    % calculate the second origin point and the modified vector magnitude
    VecMag_M2 = nan(1,length(XYZ_M(:,1)));
    for ii=1:length(XYZ_M(:,1))
        VecMag_M2(ii)=norm([XYZ_M(Ori_pt2(1),1)-XYZ_M(ii,1),XYZ_M(Ori_pt2(1),2)-XYZ_M(ii,2),XYZ_M(Ori_pt2(1),3)-XYZ_M(ii,3)]);
    end

    % calculate the area under the curve of the given angle
    Area_m1=trapz(VecMag_M1(w1_start:w1_end));
    Area_m2=trapz(VecMag_M2(w1_start:w1_end));

    % modify the XYZ median beat with the two origon point calculated
    XYZ_M1 = repmat(XYZ_M(Ori_pt(1),:),size(XYZ_M,1),1) - XYZ_M;
    XYZ_M2 = repmat(XYZ_M(Ori_pt2(1),:),size(XYZ_M,1),1) - XYZ_M;

    % calculate the sum of the absolute gradient on modified XYZ median beats
    data_grad_M1 = data_processing(XYZ_M1,'gradsum',5);
    data_grad_M2 = data_processing(XYZ_M2,'gradsum',5);

    % calcualte the average of the sum of the absolute gradient about the
    % origin points with 3 points on either side
    Sum_M1=mean(data_grad_M1(Ori_pt-3:Ori_pt+3));
    Sum_M2=mean(data_grad_M2(Ori_pt2-3:Ori_pt2+3));

    % decide which origin point to use based on the area under the curve and
    % the average gradient about the origin points
    if Area_m1 < Area_m2 && Sum_M1 < Sum_M2 
        dat_ori=Ori_pt;
        VecMag_ML=VecMag_M1;
    else
        dat_ori=Ori_pt2;
        VecMag_ML=VecMag_M2;
    end

    %% plotting
    VCG_fig = figure('visible','off');

    subplot(211)
    hold on
    plot(VecMag_ML,'b','Linewidth',1.5);
    plot([0 600],[0 0],'k:');
    hold off



    subplot(212)
    hold on;
    plot(XYZ_M(:,1),'Linewidth',1);
    plot(XYZ_M(:,2),'Linewidth',1);
    plot(XYZ_M(:,3),'Linewidth',1);
    ylabel('XYZ');
    hold off

    %% write variable to file and move file to destination
    saveas(VCG_fig,strcat(Results_folder,file_name(1:length(file_name)-4),'_origin.jpg'));
    copyfile(file_path,Results_folder);
    save(strcat(Results_folder,file_name(1:length(file_name)-4),'.mat'),'-append','VecMag_ML','dat_ori','-mat');




%% INCLUDED FUNCTIONS



function [ori_pt_idx,ori_val]=abs_grad_fun(xyz_median)

    % calculate the sum of the absolute gradient on XYZ within w1
    diff_w1 = data_processing(xyz_median,'gradsum',2);

    iter_num=0;
    [~,min_idx]=min(diff_w1);

    % find the point at which has the least angle variation at both side of the
    % window
    for ii=1:length(diff_w1)-1
        if abs(diff_w1(length(diff_w1)-ii)-diff_w1(min_idx))<0.1
            iter_num=length(diff_w1)-ii;

            break
        end

    end

    ori_pt_idx=iter_num;
    ori_val=xyz_median(iter_num);	

end


function [ori_pt_idx,ori_pt_cluster] = ori_clustering(xyz_median)

    %% Find the clusters of possible origin point candidate 
    % SYNTAX :  [ori_pt_idx,ori_pt_cluster] = ori_clustering(xyz_median)
    % INPUT  :  xyz_median  - XYZ median beat (segmented to a window) 
    %                         size(M x 3).
    % OUTPUT :  ori_pt_idx - the index of the calculated origin point
    %           ori_pt_cluster - origin point candidate clusters index

    %% Process data
    processed_data = data_processing(xyz_median,'norm_mvvar',10);
    [N,~,bin_label] = histcounts(processed_data,'BinWidth',10);
    xyz_norm = nan(size(xyz_median,1),1);

    for ii=1:length(xyz_median(:,1))
        xyz_norm(ii)=norm([xyz_median(ii,1),xyz_median(ii,2),xyz_median(ii,3)]);
    end

    %% Find origin point candidate clusters
    idx = find(N == max(N));
    candidate_idx = find(bin_label==idx(1));
    idx_diff = diff(candidate_idx);
    diff_idx = [0; find(idx_diff>=4)];

    if length(diff_idx) == 1
        % when there's only one point found
        ori_pt_cluster = candidate_idx;
        ori_pt_idx = round(median(candidate_idx));
    else
        % look for two largest clusters
        cluster_ele_count = zeros(length(diff_idx),1);

        for ii = 1:length(diff_idx)
            if ii == length(diff_idx)
                cluster_ele_count(ii) = sum(idx_diff(diff_idx(ii)+1:...
                                            length(idx_diff)));
            else
                cluster_ele_count(ii) = sum(idx_diff(diff_idx(ii)+1:...
                                            diff_idx(ii+1)-1));
            end
        end

        [~,I]=sort(cluster_ele_count,'descend');

        if I(1) == max(I)
            cluster_group1 = candidate_idx(diff_idx(I(1))+1:end);
        else
            cluster_group1 = candidate_idx(diff_idx(I(1))+1:...
                                           diff_idx(I(1)+1)-1);
        end

        if I(2) == max(I)
                    cluster_group2 = candidate_idx(diff_idx(I(2))+1:end);
        else
            cluster_group2 = candidate_idx(diff_idx(I(2))+1:...
                                           diff_idx(I(2)+1)-1);
        end

        %% find the best cluster by total average gradient of the clusters
        data_gradient_sum = data_processing(xyz_median,'gradsum',5);
        cluster_avg_slope(1) = mean(data_gradient_sum(cluster_group1));
        cluster_avg_slope(2) = mean(data_gradient_sum(cluster_group2));

        [~,bestgroup] = min(cluster_avg_slope);

        if bestgroup == 1
            ori_pt_cluster = cluster_group1;
            ori_pt_idx = round(median(cluster_group1));
        else
            ori_pt_cluster = cluster_group2;
            ori_pt_idx = round(median(cluster_group2));
        end
    end
end

function processed_data = data_processing(data,process,process_window)
    %% find the clusters of possible origin point candidate 
    % SYNTAX : processed_data = data_processing(data,process,process_window)
    % INPUT  : data            - data matrix with size(M x 3) [X,Y,X leads].
    %          process         - the method of processing data.
    %                            'norm_var': norn of the moving variance 
    %                            with given window.
    %                            'gradsum': sum of the absolute gradient
    %                            of the smoothed XYZ signals with given window.
    %          process_window  - window for specific process
    % OUTPUT : processed_data  - data processed based on specified process
    %                             and process window
    switch process
        case 'norm_mvvar'
            data_norm = nan(size(data,1),1);
            for ii=1:length(data(:,1))
                data_norm(ii)=norm([data(ii,1),data(ii,2),data(ii,3)]);
            end
            processed_data = movvar(data_norm,process_window);
        case 'gradsum'
            processed_data = abs(gradient(smooth(data(:,1),process_window)))...
                            +abs(gradient(smooth(data(:,2),process_window)))...
                            +abs(gradient(smooth(data(:,3),process_window)));

    end
end