% The code was created by Egor Dzyubenko to quantify the density and
% topology of perineuronal nets
% This code is the modified and improved vesion of the algorythm from our previous work
% (Dzyubenko et al, 2018)

% Required import variables
% OriginalImageName (filename)
% xyz (positions of PNN mesh vertices in space)
% V (volume of each node), optional, we use it for filtration purposes

[ind,name]=grp2idx(OriginalImageName);
range=1.5;  % range should not be smaller than the expected distance between objects
nn=length(name);

%The part below allows to filter the dot objects depending on their size
%filter dots below 0.005
fltrd=find(V>=0.005);     %V>=0.001 & V<0.03   V>=0.03
OriginalImageName=OriginalImageName(fltrd,:);
xyz=xyz(fltrd,:);
V=V(fltrd,:);

%check the number of dots
dotNum=zeros(nn,1);
for ww=1:nn
 pp=length(find(ind==ww));
 dotNum(ww,1)=pp;
end

%find the mesh vertices and whether they should be connected
for n=1:nn        %  nn is length(name). can use intervals [1:9, 11:16, 18:30, 32:98]
%preassign matrices for sinle cells
tic
cellind=find(OriginalImageName==name(n));
nodexyz=xyz(cellind,:);
NodeN=length(cellind);
%find indexes Idx and distances D of nodes within the range
[Idx,D] = rangesearch(nodexyz, nodexyz, range);
neighborD=NaN(NodeN,2);
neighborIdx=zeros(NodeN,2);
conn=zeros(NodeN,NodeN);
dist=NaN(NodeN, NodeN);
%get two neighbors for the node if they exist. their places in the cell are
%always 2nd and 3rd
for k=1:NodeN
    if length(D{k})>1
    neighborD(k,1)=D{k}(2);
    neighborIdx(k,1)=Idx{k}(2);
    end
    if length(D{k})>2
    neighborD(k,2)=D{k}(3);
    neighborIdx(k,2)=Idx{k}(3);
    end
end
%create connections with 2 closest neibors nodes if they exist
for i=1:NodeN
        if any(neighborIdx(i,1))        %first neighbor
        first=neighborIdx(i,1);
        conn(i,first)=1;
        dist(i,first)=neighborD(i,1);
        conn(first,i)=0;                %remove duplicates
        dist(first,i)=NaN;
        end

        if any(neighborIdx(i,2))        %second neighbor
        second=neighborIdx(i,2);
        conn(i,second)=1;
        dist(i,second)=neighborD(i,2);
        conn(second,i)=0;               %remove duplicate
        dist(second,i)=NaN;
        end
end

% histogram(dist);    %optional    

%Construct the graph
[row,col]=find(conn==1);
EdgeTable=table([row,col],'VariableNames',{'EndNodes'});
if ~isempty(EdgeTable)
G = graph(EdgeTable);
%you can optionally plot the graph to see if it resembles the PNN
% (not recommended for batch mode)
% figure 
% plot(G,'EdgeColor','g','EdgeAlpha',0.4,'NodeColor','none','XData', nodexyz(:,1),'YData', nodexyz(:,2),'ZData', nodexyz(:,3));
% You can optionally create and analyse the total surface of the PNN
% shp = alphaShape(nodexyz(:,1),nodexyz(:,2),nodexyz(:,3),radiusmax);
% plot(shp);
% Surf=surfaceArea(shp);

%Getting quantification data (feel free to add what you like!)
NodeDeg=degree(G);
NodeTotal=NodeN;
NodeConnDeg=NodeDeg(NodeDeg>1);
PercentNodesInMesh=100*length(NodeConnDeg)/NodeTotal;
MeanDegreeConnectivity=mean(NodeConnDeg);
MeanInternodeDistance=mean(dist,'all','omitnan');
%Set results display
results{n,1}=name(n);
results{n,2}=NodeTotal;
results{n,3}=PercentNodesInMesh;
results{n,4}=MeanDegreeConnectivity;
results{n,5}=MeanInternodeDistance;
toc
end

clearvars -except results V n OriginalImageName range xyz name
end

SummaryTable=array2table(results, 'VariableNames',{'name','NodeTotal','PercentNodesInMesh','MeanDegreeConnectivity','MeanInternodeDistance',});



