function [F,V,C]=delaunayZip(F1,V1,F2,V2,inputStruct)

% function [Fn]=delaunayZip(F1,V1,F2,V2,inputStruct)
%-------------------------------------------------------------------------
%
%
% Change log:
% 2018/05/09: Added growing of region size if error occurs
% 2018/05/09: Added stepwise removal of last triangle since it does not
% include next points.
% 2018/11/02: Added self-triangulation as an option (default on)
% 2018/11/02: Added walk-back for paths so that one will not be too long
% 2018/11/02: Fixed bug in relation to one of the paths "running out of
% points". Remainder is triangulated properly.
%-------------------------------------------------------------------------

%%

maxD=max([patchEdgeLengths(F1,V1);patchEdgeLengths(F2,V2)]);

defaultInputStruct.ind1=[];
defaultInputStruct.ind2=[];
defaultInputStruct.distLocal=2*maxD;
defaultInputStruct.startInd=[];
defaultInputStruct.plotOn=0;
defaultInputStruct.selfTriangulate=1; %Option to self-triangulate surfaces first
defaultInputStruct.angleThreshold=(60/180)*pi; %Angular threshold for self-triangulation
[inputStruct]=structComplete(inputStruct,defaultInputStruct,0); %Complement provided with default if missing or empty

ind1=inputStruct.ind1;
ind2=inputStruct.ind2;
distLocal=inputStruct.distLocal;
startInd=inputStruct.startInd;
selfTriangulate=inputStruct.selfTriangulate;
angleThreshold=inputStruct.angleThreshold;
plotOn=inputStruct.plotOn;

%% Self-triangulate if needed

if selfTriangulate==1
    if ind1(1)==ind1(end)
        isClosedLoop=1;
    else
        isClosedLoop=0;
    end
    numFacesInitial_1=size(F1,1);
    [F1,V1,ind1]=triSurfSelfTriangulateBoundary(F1,V1,ind1,angleThreshold,isClosedLoop);
    numFaces_1=size(F1,1);
    numFacesInitial_2=size(F2,1);
    [F2,V2,ind2]=triSurfSelfTriangulateBoundary(F2,V2,ind2,angleThreshold,isClosedLoop);
    numFaces_2=size(F2,1);

    L1=false(size(F1,1),1);
    if numFaces_1>numFacesInitial_1
        L1(end-(numFaces_1-numFacesInitial_1-1):end)=1;
    end
    L2=false(size(F2,1),1);
    if numFaces_2>numFacesInitial_2
        L2(end-(numFaces_2-numFacesInitial_2-1):end)=1;
    end
else
    L1=zeros(size(F1,1),1);
    L2=zeros(size(F2,1),1);
end

%%
[F,V,C]=joinElementSets({F1,F2},{V1,V2});
[~,~,Nv]=patchNormal(F,V);

%%

ind2=ind2+size(V1,1);
startInd(2)=startInd(2)+size(V1,1);

%%
%Create edges list
E=[ind1(1:end-1) ind1(2:end); ind2(1:end-1) ind2(2:end)];

%%

logicNotUsed=ismember((1:1:size(V,1))',E); %Logic to keep track of points that are used

%Initialize other parameters
Fn=[]; %Faces
Cn=[]; %"Colors"=step count for face group
c=1; %While loop counter variable

indGroup=startInd(:); %Initialize current group
numGroup=numel(indGroup); %Initialize current number of members of the current group
numGroupPrevious=0;

if plotOn==1
    h1=[]; %Initiate empty plot handle
    markerSize=25;

    hf=cFigure; hold on;
    gpatch(F1(~L1,:),V1,'r','none',0.2);
    gpatch(F1(L1,:),V1,'rw','k',1);
    gpatch(F2(~L2,:),V2,'b','none',0.2);
    gpatch(F2(L2,:),V2,'bw','k',1);
    plotV(V(ind1,:),'r.-','LineWidth',1,'MarkerSize',10);
    plotV(V(ind2,:),'b.-','LineWidth',1,'MarkerSize',10);

    axisGeom;
    camlight headlight;
    colormap(gjet(250));
    colorbar;
    drawnow;
end

%Turn off Delaunay warning as this case is handled properly
warning('off','MATLAB:delaunayTriangulation:ConsConsSplitWarnId');

%%
while 1

    numGroupStep=1;
    lastTry=0;
    while 1
        try
            if plotOn==1 %%Plot if plotting is on
                delete(h1); h1=[];
            end

            if plotOn==1 %%Plot if plotting is on
                figure(hf);
                h1(end+1)=plotV(V(startInd,:),'y.','MarkerSize',markerSize);
                drawnow
            end

            %Grow the current region
            while 1 %Loop to form local group (stuff attached to current group within a given distance)
                logicMember=any(ismember(E,indGroup),2); %Logic for all edges touching the current groupt
                E_sub=E(logicMember,:); %The subset of touching edges

                indGroup=unique([indGroup; E_sub(:)]); %Grow group with point indices in the edges that are touching

                d1=sqrt(sum((V(indGroup,:)-V(startInd(1)*ones(numel(indGroup),1),:)).^2,2));
                d2=sqrt(sum((V(indGroup,:)-V(startInd(2)*ones(numel(indGroup),1),:)).^2,2));
                D=min(d1,d2);

                logicKeep= (D<distLocal) & (logicNotUsed(indGroup));
                indGroup=indGroup(logicKeep); %Remove points that are too far
                if numGroup==numel(indGroup) %Compare current group size to previous step
                    break %break while loop if the group is no longer growing
                end
                numGroup=numel(indGroup); %Get new current group size
            end

            %Get curve start and end points
            logicMember=all(ismember(E,indGroup),2); %Logic for all edges touching the current group
            E_sub=E(logicMember,:); %The subset of touching edges

            [~,~,~,vCount]=cunique(E_sub); %Get vertex occurance counts
            indEndPoints=unique(E_sub(vCount==1));
            indEndPoints1=indEndPoints(ismember(indEndPoints,ind1));
            indEndPoints2=indEndPoints(ismember(indEndPoints,ind2));

            %Compose sub-curves
            logicSub1=all(ismember(E_sub,ind1),2);
            %             if ~any(logicSub1)
            %                 warning('None of the current edges nodes are a member of ind1');
            %             end
            E_sub1=E_sub(logicSub1,:);
            [indListSub1]=edgeListToCurve(E_sub1);
            indListSub1=indListSub1(:);

            logicSub2=all(ismember(E_sub,ind2),2);
            %             if ~any(logicSub2)
            %                 warning('None of the current edges nodes are a member of ind2');
            %             end
            E_sub2=E_sub(logicSub2,:);
            [indListSub2]=edgeListToCurve(E_sub2);
            indListSub2=indListSub2(:);

            %Alter segments so they can be merged into closed loop curve
            if isempty(Fn) %First iteration
                d=sqrt(sum((V([indListSub2(1) indListSub2(end)],:)-V(indListSub1(1)*ones(1,2),:)).^2,2));
                [~,indClosest]=min(d);
                if indClosest~=2 %If the start is closest to the other start
                    indListSub2=flipud(indListSub2);
                end
            else
                indStart1=find(ismember(indListSub1,startInd));
                if indStart1~=1
                    indListSub1=flipud(indListSub1);
                end
                indStart2=find(ismember(indListSub2,startInd));
                if indStart2==1
                    indListSub2=flipud(indListSub2);
                end
            end

            if any(ismember(indGroup,ind1))==0
                startInd
                ind1
                indGroup
            end

            %             if any(ismember(indGroup,ind1)) && any(ismember(indGroup,ind2))
            %             else
            %                 %One of the two sets contains only 1 member, triangulate
            %                 %remaining points manually
            %
            %                 %-----------------------------------
            %                 if nnz(ismember(indGroup,ind1))==0
            %                     indLeft=flipud(indListSub2);
            %                     sKeep=startInd(ismember(startInd,ind1));
            %                 else%if nnz(ismember(indGroup,ind2))==0
            %                     indLeft=indListSub1;
            %                     sKeep=startInd(ismember(startInd,ind2));
            %                 end
            %
            %                 f=[];
            %                 for qStep=1:1:numel(indLeft)-1
            %                     sVary=indLeft(1);
            %                     f=[f; [sKeep sVary indLeft(2)]];
            %                     indLeft=indLeft(2:end); %First is part of startInd
            %                 end
            %             end

            %Shorten curves if they stick out to much wrt end of other curve
            d=sqrt(sum((V(indListSub2,:)-V(indListSub1(end)*ones(1,numel(indListSub2)),:)).^2,2));
            [~,indClosest]=min(d);
            if indClosest~=1 && indClosest~=numel(indListSub2)
                indListSub2=indListSub2(indClosest:end);
            end

            d=sqrt(sum((V(indListSub1,:)-V(indListSub2(1)*ones(1,numel(indListSub1)),:)).^2,2));
            [~,indClosest]=min(d);
            if indClosest~=numel(indListSub1) && indClosest~=1
                indListSub1=indListSub1(1:indClosest);
            end

            %Create closed curve
            if nnz(ismember(indGroup,ind1))==0
                indListSub=[startInd(ismember(startInd,ind1)); indListSub2; startInd(ismember(startInd,ind1))]; %Closed loop
            elseif nnz(ismember(indGroup,ind2))==0
                indListSub=[startInd(ismember(startInd,ind2)); indListSub1; startInd(ismember(startInd,ind2))]; %Closed loop
            else
                indListSub=[indListSub1;indListSub2;indListSub1(1)]; %Closed loop
            end

            indGroup=indListSub(1:end-1);

            if plotOn==1 %%Plot if plotting is on
                figure(hf);
                if ~isempty(indEndPoints1)
                    h1(end+1)=plotV(V(indEndPoints1,:),'r.','MarkerSize',markerSize);
                end
                if ~isempty(indEndPoints2)
                    h1(end+1)=plotV(V(indEndPoints2,:),'b.','MarkerSize',markerSize);
                end
                if ~isempty(indListSub1)
                    h1(end+1)=plotV(V(indListSub1,:),'r-','LineWidth',3);
                end
                if ~isempty(indListSub2)
                    h1(end+1)=plotV(V(indListSub2,:),'b-','LineWidth',3);
                end
                if ~isempty(indListSub)
                    h1(end+1)=plotV(V(indListSub,:),'k-','LineWidth',2);
                end
                drawnow
            end

            %Rotate current point set
            V_now=V(indListSub(1:end-1),:); %Current closed curve coordinate set
            [R]=pointSetPrincipalDir(V_now); %Fit local coordinate system with 3rd direction pointing outward of local planar-ish region
            V_now_R=V_now*R; %Rotate coordinate set to prepare for 2D Delaunay based triangulation

            %Do 2D Delaunay triangulation
            DT_contraints=[(1:numel(indGroup))' ([2:numel(indGroup) 1])']; %Constraints are edges forming boundary
            DT = delaunayTriangulation(V_now_R(:,[1 2]),DT_contraints); % Initial triangulation
            f=DT.ConnectivityList; %Get faces set
            L = isInterior(DT); %Remove faces not inside region
            f=f(L,:); %Faces excluding external faces (only keep those inside constraint edges)
            f=indGroup(f); %Change indices to overall system

            %Remove first and last triangles if there are more than 3 triangles, this
            %will improve triangulation quality as these triangles were not fully
            %embedded in the point set.
            %                 if size(f,1)>2
            %                     logicKeep=~(any(ismember(f,indEndPoints1(~ismember(indEndPoints1,Fn))),2)...
            %                         & any(ismember(f,indEndPoints2(~ismember(indEndPoints2,Fn))),2))...
            %                         | any(ismember(f,startInd),2);
            %                     f=f(logicKeep,:);
            %
            %                     [~,~,IND_FF]=tesIND(f,V);
            %                     logicNeighbours=sum(IND_FF>1,2)>1;
            %                     logicNeighbours(any(ismember(f,startInd),2))=1;
            %                     f=f(logicNeighbours,:);
            %                 end

            break

        catch ME
            if lastTry==1
                rethrow(ME)
            end

            if numGroupStep==numGroupPrevious
                lastTry=1;
            end
            distLocal=distLocal+maxD;
            warning(['Failed using current step size, increasing to: ',num2str(distLocal)]);
            numGroupPrevious=numGroupStep;
        end
    end
    distLocal=inputStruct.distLocal; %reset

    if dot(mean(patchNormal(f,V),1),mean(Nv(indListSub(1:end-1),:),1))<0
        f=fliplr(f);
    end

    if plotOn==1 %%Plot if plotting is on
        figure(hf);
        V_now_mean=mean(V_now); %Mean of coordinate set
        h1(end+1)=plotV(V_now_mean,'kx','MarkerSize',markerSize);
        gpatch(f,V,c*ones(size(f,1),1),'k',1);
        drawnow
    end

    %Collect faces and color data
    Fn=[Fn;f];
    Cn=[Cn;c*ones(size(f,1),1)];

    E_Fn=sort(patchEdges(Fn,0),2);
    ind_E_Fn= reshape(sub2indn(size(V,1)*ones(1,2),E_Fn),size(Fn));

    Es=sort(E,2);
    ind_E= sub2indn(size(V,1)*ones(1,2),Es);

    logicEdgesUsed=ismember(ind_E,ind_E_Fn);

    E_sub=E(~logicEdgesUsed,:);
    indUnusedPoints=unique(E_sub);
    logicNotUsed=false(size(logicNotUsed));
    logicNotUsed(indUnusedPoints)=1;

    if any(logicNotUsed)==0
        break
    end

    %Get curve start and end points
    [~,~,~,vCount]=cunique(E_sub); %Get vertex occurance counts
    indEndPoints=unique(E_sub(vCount==1));
    indEndPoints1=indEndPoints(ismember(indEndPoints,ind1));
    indEndPoints2=indEndPoints(ismember(indEndPoints,ind2));

    e=patchEdges(Fn,1);
    edgeEndPoints=e(any(ismember(e,indEndPoints1),2)&any(ismember(e,indEndPoints2),2),:);
    logicMemberOfLast=any(ismember(edgeEndPoints,f),2);
    edgeEndPoints=edgeEndPoints(logicMemberOfLast,:);
    if ~isempty(edgeEndPoints)
        startInd=edgeEndPoints(1,:);
    else
        startInd=[indListSub1(end) indListSub2(1)];
    end

    c=c+1;
end

F=[F;Fn];
C=[C; max(C(:))+Cn];

%Turn Delaunay warning back on
warning('on','MATLAB:delaunayTriangulation:ConsConsSplitWarnId');

%%
% _*GIBBON footer text*_
%
% License: <https://github.com/gibbonCode/GIBBON/blob/master/LICENSE>
%
% GIBBON: The Geometry and Image-based Bioengineering add-On. A toolbox for
% image segmentation, image-based modeling, meshing, and finite element
% analysis.
%
% Copyright (C) 2006-2022 Kevin Mattheus Moerman and the GIBBON contributors
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.


function [N,Mmax,catalog,IDAll,HistoryAll] = ETESProject3(cat,vEtas,vAfter,StartDateProj,EndDate,FaultParam)

    %This program is like ETESProject.m except that background earthquakes are
    %taken from an input grid, in the same manner as in TotalAftSim.8, instead
    %of smoothed directly from the catalog.  See the companion MakeRateGrid.m
    %program also in this directory.
    %
    %This program starts with A real earthquake catalog and then
    %projects what seismicity will follow further in time.  This is A Monte
    %Carlo modeler -- ideally it should be run multiple times to get average
    %results and error.  It also has A background component.  The background
    %component is set by BckgrndR -- this gives the number of earthquakes that
    %you want each year as
    %background earthquakes.  The background earthquakes will have an r^1.37
    %distribution in one dimension distribution away from hypocenters in the
    %input catalog which are identified as being background earthquakes
    %themselves (e.g. not within one fault length and 10 years of A bigger
    %earthquake).
    %
    %Earthquakes that are smaller than magb are treated as point sources;
    %earthquakes that are larger than magb are modeled as planes.  Where the
    %middle of the fault plane is not well constrained we use the median of the
    %first two days of the aftershock sequence.  For simplicity, large
    %simulated aftershocks are assigned the same focal mechanisms as their
    %mainshocks.  Where the mainshocks are too small for their focal mechanism
    %to be listed we use 75% 332 degree strike aqnd 25% 242 degree strike,
    %based on approximate assesement of trends in the Southern California
    %catalog, with A 90 degree dip.
    %
    %Written by Karen Felzer, 2007.
    %
    %Input:
    %
    % cat: Real eartquake catalog.  Ten column format: year, month, day, hour,
    % minute, second, lat, lon, depth, magnitude.
    %
    %RateGrid: Background rate, spatially varying.  minlon,maxlon, minlat,
    %maxlat, earthquake rate of M>=4.
    %
    %CX, P, A: Omori's law parameters, c, p, and A.  They should be entered
    %as direct triggering parameters, with A recommended p = 1.37.  The value
    %of A should be appropriate for the rate of A mainshock of magnitude M
    %triggering aftershocks of magnitude >=M.  For Mmin = 2.5, we recommend CX = 0.095, P = 1.34, and
    %A = 0.008. (from Felzer and Kilb, in preparation, 2007).
    %
    % Mmin: Minimum earthquake magnitude to report and simulate
    % MminR: Minimum earthquake magnitude for reporting in the output catalog
    % maxm: Maximum earthquake magnitude
    % DMax: Maximum distance for aftershocks
    % EndDate: Last date for the simulated catalog, to be entered as float
    %StartDate; First date for the projected catalog
    % (e.g. 1975.54  'January 1, 2007').  The simulated catalog will begin immediately
    % after the last date in the input catalog cat.
    %
    %magb: Cutoff magnitude for the point source vs. plane source
    %representation of the mainshock
    %
    %FaultParam: Fault parameters for the M>magb earthquakes, listed in order
    %of their occurrence in the catalog.  Columns give fault length, width,
    %strike, dip, year, month, day.
    %
    %
    %OUTPUT:
    %
    %N: total number of aftershocks in the simulated catalog.  Comparing N with
    %the total length of the catalog produced gives the percentage of the total
    %that is aftershocks.
    %
    %MMax: Maximum magnitude in the simulated catalog
    %
    %catalog: The simulated catalog
    %columns 1-10: years, month, day, hour, minute, second, lat, lon, depth,
    %magnitude.  Column 11: ID number for this earthquake.  Column12: If this
    %earthquake is an aftershock, column 12 gives the ID number of its
    %mainshock.  Column 13: If the earthquake is an aftershock, the distance
    %between its epicenter and the nearest point on the fault plane of the
    %mainshock that triggered it.
    %
    %IDAll: The ID numbers for each earthquake; same output as is listed in
    %Column 11 of the catalog
    %
    %HistoryAll: Mainshock of each earthquake; same output as is listed in
    %Column 13 of the catalog.

    %%%%Starting the program
    %set up
    CX=vEtas(1);
    P=vEtas(2);
    A=vEtas(3);

    Mmin=vAfter(1);
    MminR=vAfter(2);
    maxm=vAfter(3);
    DMax=vAfter(4);
    magb=vAfter(5);

    N = zeros(1,1);
    Mmax = zeros(1,1);
    tdistAll = [];

    [fYr1, nMn1, nDay1, nHr1, nMin1, nSec1]=decyear2mat(StartDateProj);
    StartDateProj=datenum(floor(fYr1),nMn1,nDay1,nHr1, nMin1,nSec1);
    [fYr2, nMn2, nDay2, nHr2, nMin2, nSec2]=decyear2mat(EndDate);
    EndDate=datenum(floor(fYr2),nMn2,nDay2,nHr2, nMin2,nSec2);

    T = EndDate - StartDateProj;  %Gives longest time that aftershocks may be generated over; for simplicity all aftershocks will
    %be generated over this time and then be trimmed as needed.

    T3 = EndDate;   %Gives the end time of the simulation

    T2 = StartDateProj;  %The starting date of the projected catalog

    %And limiting the big earthquakes in FaultParam to before the beginning of
    %the simulation

    % tp = datenum(FaultParam(:,5),FaultParam(:,6),FaultParam(:,7));
    % xp = find(tp>T2 | tp<datenum(StartDate));
    % changes vst
    FaultParam = [NaN NaN NaN NaN NaN NaN NaN];
    % FaultParam(xp,:) = [];

    %Finding the average number of aftershocks expected in time T for M - Maft
    %= 0.

    if(P ~= 0)
        NM = (A/(1-P))*((T+CX)^(1-P) - CX^(1-P));
    else
        NM = A*(log(T+CX) - log(CX));
    end

    %Setting up rotation matrices for later so that simulated M>magb mainshocks will have
    %either 332 degree strike or 242 degree strike.  Based on A quick eye
    %assessment of the So Cal fault map, 75% of mainshocks will be assigned the
    %332 strike and 25% the 242 strike.

    z1 = deg2rad(303);
    lambda1 = [cos(z1) sin(z1); -sin(z1) cos(z1)];

    z2 = deg2rad(213);
    lambda2 = [cos(z2) sin(z2); -sin(z2) cos(z2)];

    %And converting catalog times to Julian days

    tdist = datenum(cat(:,1),cat(:,2),cat(:,3),cat(:,4),cat(:,5),cat(:,6));

    m = cat(:,10);

    %eliminating catalog earthquakes that are past the start of the projection

    A=[];
    cat(A,:) = [];
    tdist(A) = [];
    m(A) = [];

    %And changing the lat, lon to xy
    catxy = cat;
    locCen(1) = mean(cat(:,7));
    locCen(2) = mean(cat(:,8));
    catxy(:,7:8) = latlon2xy2(catxy(:,7:8),locCen);
    Locs = [catxy(:,7) catxy(:,8) catxy(:,9)];
    NBack=size(Locs,1);

    %And initiating ID, history, and fault distance tracking matrices, and
    %putting magnitudes in A vector
    ID = [1:1:length(tdist)];
    History = zeros(size(ID));
    FaultDist = -1*ones(size(ID));

    aa2 = find(m>=MminR & tdist>=T2 & tdist<T3);  %we only report earthquakes in the final catalog for m>=MminR

    if(~isempty(aa2))
        tdistAll(1:length(aa2)) = tdist(aa2)';
        LocsAll(1:length(aa2),:) = Locs(aa2,:);
        mAll(1:length(aa2),:) = m(aa2);
        IDAll(1:+length(aa2),:) = ID(aa2)';
        HistoryAll(1:length(aa2),:) = 0;
        FaultDistAll(1:+length(aa2),:) = -1;
    end

    % And doing some final setting up for the ETAS run
    N = 0;
    Mmax = 0;
    ID = ID';
    History = History';
    FaultDist = FaultDist';

    %Now running the ETAS simulation***********%%%%%%%%%%%%

    %The loop gets all of the aftershocks of each earthquake occurring within
    %the specified duration of the catalog and over the minimum magnitude for
    %the simulation

    xm = find(m>=MminR);
    maxID = max(ID);

    countAll = length(tdistAll)+1;

    A = find(m>=Mmin & tdist<=T3);

    NIttr = 1;

    while(~isempty(A))

        IDold = ID(A);

        %Running the small point source and larger extended source earthquakes
        %separtely, through different versions of the aftershock program.
        %First, the small point source earthquakes

        a2 = find(m(A)<magb);

        a2 = A(a2);

        tdistSave = tdist;
        IDSave = ID;
        mSave = m;
        LocsSave = Locs;

        try
            [tdist,m,Locs,ID,History,FaultDist] = GAft3(tdist(a2),maxID,ID(a2),m(a2),Mmin,maxm,NM,T,Locs(a2,:),DMax,P,CX);
        catch
            disp('No point source events');
        end
        %And then doing the larger, plane source earthquakes

        a3 = find(mSave(A)>=magb);

        a3 = A(a3);

        if(~isempty(a3))

            if(NIttr>0)  %These are simulated large earthquakes which need FaultParam assigned.  Doing widths and lengths from Wells & Coppersmith

                FaultParam = BuildFaultParam(mSave(a3),LocsSave(a3,:),lambda1,lambda2);
            end

            %And getting the aftershocks for the big mainshocks

            try
                [tdist2,m2,Locs2,ID2,History2,FaultDist2] = GAft4(tdistSave(a3),maxID,IDSave(a3),mSave(a3),Mmin,maxm,NM,T,LocsSave(a3,:),DMax,FaultParam,P,CX);
            catch
                disp('No plane source events');
            end

        else
            tdist2 = [];
            m2 = [];
            Locs2 = [];
            ID2 = [];
            History2 = [];
            FaultDist2 = [];
        end

        %And putting the results from the two runs together

        tdist = [tdist; tdist2];

        m = [m; m2];
        Locs = [Locs; Locs2];
        ID = [ID ID2];
        History = [History'; History2];
        FaultDist = [FaultDist; FaultDist2];

        maxID = max([maxID max(ID)]);

        A = find(tdist>T2 & tdist<=T3);

        aa2 = find(m(A)>=MminR);  %we only report earthquakes in the final catalog for m>=MminR

        aa2 = A(aa2);

        if(~isempty(aa2))

            tdistAll(countAll:countAll+length(aa2)-1) = tdist(aa2)';

            LocsAll(countAll:countAll+length(aa2)-1,:) = Locs(aa2,:);

            mAll(countAll:countAll+length(aa2)-1,:) = m(aa2);

            IDAll(countAll:countAll+length(aa2)-1,:) = ID(aa2)';

            HistoryAll(countAll:countAll+length(aa2)-1,:) = History(aa2);

            FaultDistAll(countAll:countAll+length(aa2)-1,:) = FaultDist(aa2);

        end

        countAll = countAll+length(aa2);

        N = N + length(aa2);

        if(max(m(A))>Mmax)
            Mmax = max(m(A));
        end

        N;
        Mmax;

        NIttr = NIttr + 1;

    end

    %And translating the results into A traditional earthquake catalog

    % tdays = datenum(StartDate) + tdistAll;

    tvect = datevec(tdistAll);

    latlon = xy2latlon(LocsAll(:,1:2),locCen);

    catalog = [tvect latlon LocsAll(:,3) mAll IDAll HistoryAll FaultDistAll];

    catalog = sortrows(catalog,[1 2 3 4 5 6]);

    %cat = gquakes(catalog,StartDate,EndDate);

    %At this point solving for the background rate, RateGrid, if it was
    %initially input as zero.
end

    %--------------------------------------%%%

    %Getting aftershock times, locations, and mags for point source mainshocks

function [tdist,m,Locs,ID,History,FaultDist] = GAft3(tdist,maxID,IDold,m,Mmin,maxm,NM,T,Locs,DMax,P,CX)

    %First getting the number of aftershocks produced by each mainshock; stored
    %in the vector A.

    A = NM.*10.^(m - Mmin);

    try
        A = poissinv(rand(size(A,1),1),A);
    catch
        poisstest.NM=NM;
        poisstest.A=A;
        poisstest.m=m;
        save poisstest.mat poisstest -mat
        A = poissinv(rand(size(A,1),1),A);
    end

    counter = 1;

    x = find(A>0);

    tnew2 = [];

    %Making A vector with all of the mainshock times and locations

    if(~isempty(x))

        Axx = A(x);
        Axx = cumsum(Axx);
        Sp = [1; Axx(1:end-1)+1];
        Ep = Axx;
        tdist = tdist(x);
        Locs = Locs(x,:);
        xl = Locs(:,1);
        yl = Locs(:,2);
        zl = Locs(:,3);

        %The loop below is the time limiting factor for the whole calculation in
        %matlab

        AddOn = zeros(Axx(end),1);

        for j=1:length(x)

            AddOn(Sp(j):Ep(j)) = tdist(j);
            lx(Sp(j):Ep(j)) = xl(j);
            ly(Sp(j):Ep(j)) = yl(j);
            lz(Sp(j):Ep(j)) = zl(j);
            History(Sp(j):Ep(j)) = IDold(x(j));

        end

        AddDist = [lx' ly' lz'];

        %IN THIS VERSION: For each aftershock that will be produced, listing the
        %location of the mainshock that will produce it.

        %NOTE: COMMENTING THIS OUT RIGHT NOW TO SAVE TIME

        %for j = 1:length(tdist(x))

        %   idx = A(x(j));

        %   LocsT = [Locs(x(j),1)*ones(idx,1) Locs(x(j),2)*ones(idx,1) Locs(x(j),3)*ones(idx,1)];

        %  AddDist(counter:counter+A(x(j))-1,1:3) = LocsT;
        %  History(counter:counter+A(x(j))-1,1) = IDold(x(j));
        %  counter = counter + A(x(j));

        %end

        %Summing the total number of aftershocks

        AX = sum(A);

        %Now getting distances of all of the aftershocks from the faults
        %Allowing A closest approach of 1 m.

        dnew = GPowDistR(1.37,AX,0.001,DMax);

        FaultDist = dnew;

        %Converting the distances into x, y using A random theta.
        %Although not completely accutate, to save computation time
        %just keeping z the same as the generating point on the fault
        %so that we don't need to worry about staying within seismogenic
        %depth.  NOTE: there is code in the program for Joan that can be used
        %to fix this!  GET IT FIXED!!

        theta = rand(length(dnew),1).*2*pi;

        xnew = dnew.*cos(theta);
        ynew = dnew.*sin(theta);
        znew = zeros(length(ynew),1);

        LocsNew = AddDist + [xnew ynew znew];

    end

    %Now generating the aftershock times

    if(~isempty(x))

        %Then calculating times for all of the aftershocks, in terms of time since
        %the mainshock

        %tnew = GPowDistR(1.37,AX,0.0104,T);

        tnew = GPowDistRc(P,CX,AX,0,T);

        %And finally adding each aftershock time to the time of its mainshock to
        %get the absolute time of the aftershock in the catalog

        tnew2 = tnew + AddOn;

    end

    %And assigning the aftershock magnitudes

    if(~isempty(tnew2))

        m = GetMag(length(tnew2),Mmin,maxm);

        tdist = tnew2;
        Locs = LocsNew;

    else
        tdist = [];
        m = [];
        Locs = [];
        ID = [];
        History = [];
        FaultDist = [];
    end

    %And generating ID numbers for all of the aftershocks

    if(~isempty(x))

        ID = [maxID+1:1:maxID+AX+1];
    end
end
    %---------------------------------------

    %Getting aftershock times, locations, and mags for fault plane mainshocks

function [tdist,m,Locs,ID,History,FaultDist] = GAft4(tdist,maxID,IDold,m,Mmin,maxm,NM,T,Locs,DMax,FaultParam,P,CX)

    %First getting the number of aftershocks produced by each mainshock; stored
    %in the vector A.

    A = NM.*10.^(m - Mmin);

    A = poissinv(rand(length(A),1),A);

    counter = 1;

    x = find(A>0);

    tnew2 = [];

    %And setting up the fault plane

    %And for each aftershock that will be produced, picking A random reference
    %point on the mainshock fault that the distance of the aftershock from the
    %fault will be measured from.

    %IN THIS VERSION: For each aftershock that will be produced, listing the
    %location of the mainshock that will produce it.

    for j = 1:length(tdist(x))

        FaultY = FaultParam(j,4);
        FaultZ = FaultParam(j,5);

        YLow = FaultParam(j,2) - FaultY/2;
        ZLow = FaultParam(j,3) - FaultZ/2;

        %And getting points on the fault that will generate the earthquakes

        rpy = rand(A(x(j)),1).*FaultY;
        rpz = rand(A(x(j)),1)*FaultZ;
        rpx = zeros(size(rpy,1),1);

        rpy = rpy - FaultY/2;

        %Then rotating to the proper strike and dip

        %matrix for rotation around strike

        z1 = deg2rad(FaultParam(x(j),6));
        lambda1 = [cos(z1) sin(z1); -sin(z1) cos(z1)];

        z2 = deg2rad(FaultParam(x(j),7) - 90);  %rotation around dip

        if(FaultParam(x(j),6)>180)
            z2 = deg2rad(90-FaultParam(x(j),7));
        end

        lambda2 = [cos(z2) sin(z2); -sin(z2) cos(z2)];

        %And rotating.  Fist going to A 0 degree strike, and then to A 90 degree
        %dip.
        rot = lambda1*[rpx rpy]';

        rpx = rot(1,:)';
        rpy = rot(2,:)';

        rot = lambda2*[rpz rpy]';

        rpz = rot(1,:)';
        rpy = rot(2,:)';

        Lx = rpx + FaultParam(x(j),1);
        Ly = rpy + FaultParam(x(j),2);
        Lz = rpz + FaultParam(x(j),3);

        %And assigning the points on the fault from which aftershocks will be generated
        %And placing ID values in the history matrix

        AddDist(counter:counter+A(x(j))-1,1:3) = [Lx Ly Lz];
        History(counter:counter+A(x(j))-1,1) = IDold(x(j));
        counter = counter + A(x(j));

    end

    if(~isempty(x))

        %Summing the total number of aftershocks

        AX = sum(A);

        %Now getting distances of all of the aftershocks from the faults
        %Allowing A closest approach of 1 m.

        dnew = GPowDistR(1.37,AX,0.001,DMax);

        FaultDist = dnew;

        %Converting the distances into x, y using A random theta.
        %Although not completely accurate, to save computation time
        %just keeping z the same as the generating point on the fault
        %so that we don't need to worry about staying within seismogenic
        %depth.  NOTE: there is code in the program for Joan that can be used
        %to fix this!  GET IT FIXED!!

        theta = rand(length(dnew),1).*2*pi;

        xnew = dnew.*cos(theta);
        ynew = dnew.*sin(theta);
        znew = zeros(length(ynew),1);

        LocsNew = AddDist + [xnew ynew znew];

    end

    %Now generating the aftershock times

    %Making A vector with all of the mainshock times

    counter = 1;

    for j = 1:length(tdist(x))
        AddOn(counter:counter+A(x(j))-1) = tdist(x(j));
        counter = counter + A(x(j));
    end

    if(~isempty(x))

        %Then calculating times for all of the aftershocks, in terms of time since
        %the mainshock

        %tnew = GPowDistR(1.37,AX,0.0104,T);

        tnew = GPowDistRc(P,CX,AX,0,T);

        %And finally adding each aftershock time to the time of its mainshock to
        %get the absolute time of the aftershock in the catalog

        tnew2 = tnew + AddOn';

    end

    %And getting aftershock magnitudes

    if(~isempty(tnew2))

        m = GetMag(length(tnew2),Mmin,maxm);

        tdist = tnew2;
        Locs = LocsNew;

    else
        tdist = [];
        m = [];
        Locs = [];
        ID = [];
        History = [];
        FaultDist = [];
    end

    %And generating ID numbers for all of the aftershocks

    if(~isempty(x))

        ID = [maxID+1:1:maxID+AX+1];
    end
end

    %------------------------------------

    %Getting aftershock magnitudes

function m = GetMag(L,Mmin,maxm)

    m = Mmin - log10(rand(L,1));

    x = find(m>maxm);

    for i=1:length(x)

        while(m(x(i))>maxm)
            m(x(i)) = Mmin - log10(rand(1));

        end
    end
end


function plot_optfit(a,time,timef,bootloops,maepi)
    % function plot_optfit(a,time,timef,bootloops,maepi);
    % ---------------------------------------------------
    % Plots Ncum observed vs. Ncum modeled for specified time windows
    %
    % Input variables:
    % a         : earthquake catalog (Complete in magnitude!)
    % time      : learning period fo fit Omori parameters
    % timef     : forecast period
    % bootloops : Number of bootstraps
    % maepi     : mainshock
    %
    % Samuel Neukomm
    % last update: 01.03.04

    warning off;

    [fMc] = calc_Mc(a, 1, 0.1)+0.2;
    l = a.Magnitude >= fMc;
    a = a.subset(l);

    if size(a,2) == 9
        date_matlab = datenum(a.Date.Year,a.Date.Month,a.Date.Day,a.Date.Hour,a.Date.Minute,zeros(size(a,1),1));
        date_main = datenum(floor(maepi(3)),maepi(4),maepi(5),maepi(8),maepi(9),0);
    else
        date_matlab = datenum(a.Date.Year,a.Date.Month,a.Date.Day,a.Date.Hour,a.Date.Minute,a(:,10));
        date_main = datenum(floor(maepi(3)),maepi(4),maepi(5),maepi(8),maepi(9),maepi(10));
    end
    time_aftershock = date_matlab-date_main;
    % Select biggest aftershock earliest in time, but more than 1 day after mainshock
    fDay = 1;
    ft_c=fDay/365; % Time not considered to find biggest aftershock
    vSel = (a.Date > maepi(:,3)+ft_c & a.Date<= maepi(:,3)+time/365);
    mCat = a.subset(vSel);
    vSel = mCat(:,6) == max(mCat(:,6));
    vBigAf = mCat(vSel,:);
    if length(mCat(:,1)) > 1
        vSel = vBigAf(:,3) == min(vBigAf(:,3));
        vBigAf = vBigAf(vSel,:);
    end

    if size(a,2) == 9
        date_biga = datenum(floor(vBigAf(3)),vBigAf(4),vBigAf(5),vBigAf(8),vBigAf(9),0);
    else
        date_biga = datenum(floor(vBigAf(3)),vBigAf(4),vBigAf(5),vBigAf(8),vBigAf(9),vBigAf(10));
    end
    fT1 = date_biga - date_main; % Time of big aftershock

    % Aftershock times
    l = time_aftershock(:) > 0;
    tas = time_aftershock(l);
    eqcatalogue = a.subset(l);

    % time_as: Learning period
    l = tas <= time;
    time_as = tas(l);
    time_as = sort(time_as);
    if length(time_as) < 100 % at least 100 events in learning period
        return
    end

    % Times up to the forecast time
    lf = tas <= time+timef ;
    time_asf= [tas(lf) ];
    time_asf=sort(time_asf);

    figure_w_normalized_uicontrolunits('Numbertitle','off','Name','Forecast aftershock occurence')
    hold on

    n = length(time_as);
    loopout = []; nCnt = 0;

    for j = 1:bootloops
        clear newtas
        randnr = ceil(rand(n,1)*n);
        i = (1:n)';
        newtas(i,:) = time_as(randnr(i),:); % bootstrap sample

        % Calculate fits of different models
        mRes = [];
        % Modified Omori law (pck)
        nMod = 1; [pval1, pval2, cval1, cval2, kval1, kval2, fAIC, fL] = bruteforceloglike_a2(newtas,fT1,nMod);
        mRes = [mRes; nMod, pval1, pval2, cval1, cval2, kval1, kval2, fAIC, fL];
        % MOL with secondary aftershock (pckk)
        nMod = 2; [pval1, pval2, cval1, cval2, kval1, kval2, fAIC, fL] = bruteforceloglike_a2(newtas,fT1,nMod);
        mRes = [mRes; nMod, pval1, pval2, cval1, cval2, kval1, kval2, fAIC, fL];
        % MOL with secondary aftershock (ppckk)
        nMod = 3; [pval1, pval2, cval1, cval2, kval1, kval2, fAIC, fL] = bruteforceloglike_a2(newtas,fT1,nMod);
        mRes = [mRes; nMod, pval1, pval2, cval1, cval2, kval1, kval2, fAIC, fL];
        % MOL with secondary aftershock (ppcckk)
        nMod = 4; [pval1, pval2, cval1, cval2, kval1, kval2, fAIC, fL] = bruteforceloglike_a2(newtas,fT1,nMod);
        mRes = [mRes; nMod, pval1, pval2, cval1, cval2, kval1, kval2, fAIC, fL];

        % Select best fitting model by AIC
        vSel = (mRes(:,8)==min(mRes(:,8)));
        mRes = mRes(vSel,:);
        if length(mRes(:,1)) > 1
            vSel = (mRes(:,1)==min(mRes(:,1)));
            mRes = mRes(vSel,:);
        end
        % Model to use for bootstrapping as of lowest AIC to observed data
        nMod = mRes(1,1);
        pval1= mRes(1,2); pval2= mRes(1,3);
        cval1= mRes(1,4); cval2= mRes(1,5);
        kval1= mRes(1,6); kval2= mRes(1,7);

        % Calculate goodness of fit with KS-Test and RMS
        [H,P,KSSTAT,fRMS] = calc_llkstest_a2(newtas,fT1,pval1, pval2, cval1, cval2, kval1, kval2, nMod);
        if H == 1
            nCnt = nCnt + 1;
            if nCnt/bootloops >= 0.5
                return
            end

        else
            cumnr_model = [];

            if nMod == 1
                for i=1:length(time_asf)
                    if pval1 ~= 1
                        nrmod = kval1/(pval1-1)*(cval1^(1-pval1)-(time_asf(i)+cval1)^(1-pval1));
                    else
                        nrmod = kval1*log(time_asf(i)/cval1+1);
                    end
                    cumnr_model = [cumnr_model; nrmod];
                end % END of FOR on length(time_asf)

                if pval1 ~= 1
                    cm = kval1/(pval1-1)*(cval1^(1-pval1)-(max(time_asf)+cval1)^(1-pval1));
                    cl = kval1/(pval1-1)*(cval1^(1-pval1)-(max(time_as)+cval1)^(1-pval1));
                else
                    cm = kval1*log(max(time_asf)/cval1+1);
                    cl = kval1*log(max(time_as)/cval1+1);
                end
            else
                for i=1:length(time_asf)
                    if time_asf(i) <= fT1
                        if pval1 ~= 1
                            nrmod = kval1/(pval1-1)*(cval1^(1-pval1)-(time_asf(i)+cval1)^(1-pval1));
                        else
                            nrmod = kval1*log(time_asf(i)/cval1+1);
                        end
                        cumnr_model = [cumnr_model; nrmod];
                    else
                        if (pval1 ~= 1 & pval2 ~= 1)
                            nrmod = kval1/(pval1-1)*(cval1^(1-pval1)-(time_asf(i)+cval1)^(1-pval1))+ kval2/(pval2-1)*(cval2^(1-pval2)-(time_asf(i)-fT1+cval2)^(1-pval2));
                        elseif (pval1 ~= 1  &&  pval2 == 1)
                            nrmod = kval1/(pval1-1)*(cval1^(1-pval1)-(time_asf(i)+cval1)^(1-pval1))+ kval2*log((time_asf(i)-fT1)/cval2+1);
                        elseif (pval1 == 1  &&  pval2 ~= 1)
                            nrmod = kval1*log(time_asf(i)/cval1+1)+ kval2/(pval2-1)*(cval2^(1-pval2)-(time_asf(i)-fT1+cval2)^(1-pval2));
                        else
                            nrmod = kval1*log(time_asf(i)/cval1+1) + kval2*log((time_asf(i)-fT1)/cval2+1);
                        end
                        cumnr_model = [cumnr_model; nrmod];
                    end %END of IF on fT1
                end % End of FOR length(time_asf)

                if (pval1 ~= 1 & pval2 ~= 1)
                    cm = kval1/(pval1-1)*(cval1^(1-pval1)-(max(time_asf)+cval1)^(1-pval1)) + kval2/(pval2-1)*(cval2^(1-pval2)-(max(time_asf)-fT1+cval2)^(1-pval2));
                    cl = kval1/(pval1-1)*(cval1^(1-pval1)-(max(time_as)+cval1)^(1-pval1)) + kval2/(pval2-1)*(cval2^(1-pval2)-(max(time_as)-fT1+cval2)^(1-pval2));
                elseif (pval1 ~= 1  &&  pval2 == 1)
                    cm = kval1/(pval1-1)*(cval1^(1-pval1)-(max(time_asf)+cval1)^(1-pval1)) + kval2*log((max(time_asf)-fT1)/cval2+1);
                    cl = kval1/(pval1-1)*(cval1^(1-pval1)-(max(time_as)+cval1)^(1-pval1)) + kval2*log((max(time_as)-fT1)/cval2+1);
                elseif (pval1 == 1  &&  pval2 ~= 1)
                    cm = kval1*log(max(time_asf)/cval1+1) + kval2/(pval2-1)*(cval2^(1-pval2)-(max(time_asf)-fT1+cval2)^(1-pval2));
                    cl = kval1*log(max(time_as)/cval1+1) + kval2/(pval2-1)*(cval2^(1-pval2)-(max(time_as)-fT1+cval2)^(1-pval2));
                else
                    cm = kval1*log(max(time_asf)/cval1+1) + kval2*log((max(time_asf)-fT1)/cval2+1);
                    cl = kval1*log(max(time_as)/cval1+1) + kval2*log((max(time_as)-fT1)/cval2+1);
                end
            end % End of if on nMod
            loopout = [loopout; pval1, pval2, cval1, cval2, kval1, kval2, fAIC, fL, cm, cl, nMod];
            pfloop = plot(time_asf,cumnr_model,'color',[0.8 0.8 0.8]);
        end % End of KS-Test
    end

    % 2nd moment of bootstrap number of forecasted number of events
    fStdBst = calc_StdDev(loopout(:,9));

    % plot observed number in learning period
    cumnrf = (1:length(time_asf))';
    cumnr = (1:length(time_as))';
    p2 = plot(time_as,cumnr,'b','Linewidth',2,'Linestyle','--');

    % Plot observed events in forecast period from endpoint of modeled events in learning period
    vSel = time_asf >= max(time_as);
    vCumnr_forecast = cumnrf(vSel,:);
    vTime_forecast = time_asf(vSel,:);
    % Difference of modelled and observed number of events at time_as
    fDiff_timeas = mean(loopout(:,10))-cumnrf(length(time_as));
    vCumnr_forecast = vCumnr_forecast+fDiff_timeas;
    pf3 = plot(vTime_forecast, vCumnr_forecast,'m-.','Linewidth',2);

    xlim([0 max(time_asf)]);
    xlabel('Delay time [days]')
    ylabel('Cumulative number of aftershocks')

    % Plot standard deviation from bootstrap
    ps2=errorbar(max(time_asf),mean(loopout(:,9)),fStdBst,fStdBst);
    set(ps2,'Linewidth',2,'Color',[1 0 0])

    % calculate rate change
    nr_forecast = mean(loopout(:,9));
    nr_learn = mean(loopout(:,10));
    nummod = nr_forecast-nr_learn;
    l = time_asf <= time+timef & time_asf > time;
    numreal = sum(l);
    fRc_Bst = (numreal-nummod)/fStdBst;

    % Set line for learning period
    yy = get(gca,'ylim');
    plot([max(time_as) max(time_as)],[0 yy(2)],'k-.')
    string=['\sigma = ' num2str(fStdBst) '; RC = ' num2str(fRc_Bst) ];
    text(max(time_asf)*0.15,yy(2)*0.15,string,'FontSize',10);

    legend([p2 pf3 min(ps2)],'data','observed','\sigma (Bst)','location', 'NorthWest');


% [INPUT]
% ds = A structure representing the dataset.
% sn = A string representing the serial number of the result file.
% temp = A string representing the full path to the Excel spreadsheet used as template for the result file.
% out = A string representing the full path to the Excel spreadsheet to which the results are written, eventually replacing the previous ones.
% bw = An integer [21,252] representing the dimension of each rolling window (optional, default=252).
% sst = A float (0.0,0.1] representing the statistical significance threshold for the linear Granger-causality test (optional, default=0.05).
% rp = A boolean indicating whether to use robust p-values for the linear Granger-causality test (optional, default=false).
% k = A float (0.00,0.20] representing the Granger-causality threshold for non-causal relationships (optional, default=0.06).
% analyze = A boolean that indicates whether to analyse the results and display plots (optional, default=false).
%
% [OUTPUT]
% result = A structure representing the original dataset inclusive of intermediate and final calculations.
% stopped = A boolean that indicates whether the process has been stopped through user input.

function [result,stopped] = run_connectedness(varargin)

    persistent ip;

    if (isempty(ip))
        ip = inputParser();
        ip.addRequired('ds',@(x)validateattributes(x,{'struct'},{'nonempty'}));
        ip.addRequired('sn',@(x)validateattributes(x,{'char'},{'nonempty' 'size' [1 NaN]}));
        ip.addRequired('temp',@(x)validateattributes(x,{'char'},{'nonempty' 'size' [1 NaN]}));
        ip.addRequired('out',@(x)validateattributes(x,{'char'},{'nonempty' 'size' [1 NaN]}));
        ip.addOptional('bw',252,@(x)validateattributes(x,{'double'},{'real' 'finite' 'integer' '>=' 21 '<=' 252 'scalar'}));
        ip.addOptional('sst',0.05,@(x)validateattributes(x,{'double'},{'real' 'finite' '>' 0 '<=' 0.1 'scalar'}));
        ip.addOptional('rp',false,@(x)validateattributes(x,{'logical'},{'scalar'}));
        ip.addOptional('k',0.06,@(x)validateattributes(x,{'double'},{'real' 'finite' '>' 0 '<=' 0.20 'scalar'}));
        ip.addOptional('analyze',false,@(x)validateattributes(x,{'logical'},{'scalar'}));
    end

    ip.parse(varargin{:});

    ipr = ip.Results;
    ds = validate_dataset(ipr.ds,'Connectedness');
    sn = ipr.sn;
    temp = validate_template(ipr.temp);
    out = validate_output(ipr.out);
    bw = ipr.bw;
    sst = ipr.sst;
    rp = ipr.rp;
    k = ipr.k;
    analyze = ipr.analyze;

    nargoutchk(1,2);

    [result,stopped] = run_connectedness_internal(ds,sn,temp,out,bw,sst,rp,k,analyze);

end

function [result,stopped] = run_connectedness_internal(ds,sn,temp,out,bw,sst,rp,k,analyze)

    result = [];
    stopped = false;
    e = [];

    ds = initialize(ds,sn,bw,sst,rp,k);
    t = ds.T;

    bar = waitbar(0,'Initializing connectedness measures...','CreateCancelBtn',@(src,event)setappdata(gcbf(),'Stop',true));
    setappdata(bar,'Stop',false);
    cleanup = onCleanup(@()delete(bar));

    pause(1);
    waitbar(0,bar,'Calculating connectedness measures...');
    pause(1);

    try

        windows = extract_rolling_windows(ds.Returns,ds.BW);

        futures(1:t) = parallel.FevalFuture;
        futures_max = 0;
        futures_results = cell(t,1);

        for i = 1:t
            futures(i) = parfeval(@main_loop,1,windows{i},ds.SST,ds.RP,ds.GroupDelimiters);
        end

        for i = 1:t
            if (getappdata(bar,'Stop'))
                stopped = true;
                break;
            end

            [future_index,value] = fetchNext(futures);
            futures_results{future_index} = value;

            futures_max = max([future_index futures_max]);
            waitbar((futures_max - 1) / t,bar);

            if (getappdata(bar,'Stop'))
                stopped = true;
                break;
            end
        end

    catch e
    end

    try
        cancel(futures);
    catch
    end

    if (~isempty(e))
        delete(bar);
        rethrow(e);
    end

    if (stopped)
        delete(bar);
        return;
    end

    pause(1);
    waitbar(1,bar,'Finalizing connectedness measures...');
    pause(1);

    try
        ds = finalize(ds,futures_results);
    catch e
        delete(bar);
        rethrow(e);
    end

    pause(1);
    waitbar(1,bar,'Writing connectedness measures...');
    pause(1);

    try
        write_results(ds,temp,out);
        delete(bar);
    catch e
        delete(bar);
        rethrow(e);
    end

    if (analyze)
        analyse_result(ds);
    end

    result = ds;

end

%% PROCESS

function ds = initialize(ds,sn,bw,sst,rp,k)

    n = ds.N;
    t = ds.T;

    ds.Result = 'Connectedness';
    ds.ResultDate = now(); %#ok<TNOW1>
    ds.ResultAnalysis = @(ds)analyse_result(ds);
    ds.ResultSerial = sn;

    ds.BW = bw;
    ds.K = k;
    ds.RP = rp;
    ds.SST = sst;

    if (ds.RP)
        all_label = [' (SST=' num2str(ds.SST) ', K=' num2str(ds.K) ', R)'];
    else
        all_label = [' (SST=' num2str(ds.SST) ', K=' num2str(ds.K) ')'];
    end

    ds.LabelsCentralities = {'Betweenness Centrality' 'Closeness Centrality' 'Degree Centrality' 'Eigenvector Centrality' 'Katz Centrality' 'Clustering Coefficient'};

    ds.LabelsIndicatorsSimple = {'DCI' 'CIO' 'CIOO'};
    ds.LabelsIndicators = {['DCI' all_label] ['CIO' all_label] ['CIOO' all_label]};

    ds.LabelsSheetsSimple = {'Indicators' 'Average Adjacency Matrix' 'Average Centrality Measures'};
    ds.LabelsSheets = {['Indicators' all_label] 'Average Adjacency Matrix' 'Average Centrality Measures'};

    ds.AdjacencyMatrices = cell(t,1);
    ds.BetweennessCentralities = NaN(t,n);
    ds.ClosenessCentralities = NaN(t,n);
    ds.DegreeCentralities = NaN(t,n);
    ds.EigenvectorCentralities = NaN(t,n);
    ds.KatzCentralities = NaN(t,n);
    ds.ClusteringCoefficients = NaN(t,n);
    ds.Degrees = NaN(t,n);
    ds.DegreesIn = NaN(t,n);
    ds.DegreesOut = NaN(t,n);

    ds.Indicators = NaN(t,numel(ds.LabelsIndicators));

    ds.AverageAdjacencyMatrix = NaN(n);
    ds.AverageBetweennessCentralities = NaN(1,n);
    ds.AverageClosenessCentralities = NaN(1,n);
    ds.AverageDegreeCentralities = NaN(1,n);
    ds.AverageEigenvectorCentralities = NaN(1,n);
    ds.AverageKatzCentralities = NaN(1,n);
    ds.AverageClusteringCoefficients = NaN(1,n);
    ds.AverageDegreesIn = NaN(1,n);
    ds.AverageDegreesOut = NaN(1,n);
    ds.AverageDegrees = NaN(1,n);

    if (ds.Groups == 0)
        ds.ComparisonReferences = {'Indicators' 1:2 strcat({'CO-'},ds.LabelsIndicatorsSimple)};
    else
        ds.ComparisonReferences = {'Indicators' 1:3 strcat({'CO-'},ds.LabelsIndicatorsSimple)};
    end

end

function window_results = main_loop(r,sst,rp,gd)

    window_results = struct();

    am = causal_adjacency(r,sst,rp);
    window_results.AdjacencyMatrix = am;

    [dci,cio,cioo] = connectedness_metrics(am,gd);
    window_results.DCI = dci;
    window_results.ConnectionsInOut = cio;
    window_results.ConnectionsInOutOther = cioo;

    [bc,cc,dc,ec,kc,clc,deg,deg_in,deg_out] = network_centralities(am);
    window_results.BetweennessCentralities = bc;
    window_results.ClosenessCentralities = cc;
    window_results.DegreeCentralities = dc;
    window_results.EigenvectorCentralities = ec;
    window_results.KatzCentralities = kc;
    window_results.ClusteringCoefficients = clc;
    window_results.Degrees = deg;
    window_results.DegreesIn = deg_in;
    window_results.DegreesOut = deg_out;

end

function ds = finalize(ds,results)

    t = ds.T;

    for i = 1:t
        result = results{i};

        ds.AdjacencyMatrices{i} = result.AdjacencyMatrix;
        ds.BetweennessCentralities(i,:) = result.BetweennessCentralities;
        ds.ClosenessCentralities(i,:) = result.ClosenessCentralities;
        ds.DegreeCentralities(i,:) = result.DegreeCentralities;
        ds.EigenvectorCentralities(i,:) = result.EigenvectorCentralities;
        ds.KatzCentralities(i,:) = result.KatzCentralities;
        ds.ClusteringCoefficients(i,:) = result.ClusteringCoefficients;
        ds.Degrees(i,:) = result.Degrees;
        ds.DegreesIn(i,:) = result.DegreesIn;
        ds.DegreesOut(i,:) = result.DegreesOut;

        ds.Indicators(i,:) = [result.DCI result.ConnectionsInOut result.ConnectionsInOutOther];
    end

    am = sum(cat(3,ds.AdjacencyMatrices{:}),3) ./ numel(ds.AdjacencyMatrices);
    am_threshold = mean(mean(am));
    am(am < am_threshold) = 0;
    am(am >= am_threshold) = 1;
    ds.AverageAdjacencyMatrix = am;

    [bc,cc,dc,ec,kc,clc,deg,deg_in,deg_out] = network_centralities(am);
    ds.AverageBetweennessCentralities = bc;
    ds.AverageClosenessCentralities = cc;
    ds.AverageDegreeCentralities = dc;
    ds.AverageEigenvectorCentralities = ec;
    ds.AverageKatzCentralities = kc;
    ds.AverageClusteringCoefficients = clc;
    ds.AverageDegrees = deg;
    ds.AverageDegreesIn = deg_in;
    ds.AverageDegreesOut = deg_out;

end

function write_results(ds,temp,out)

    [out_path,~,~] = fileparts(out);

    try
        if (exist(out_path,'dir') ~= 7)
            mkdir(out_path);
        end

        if (exist(out,'file') == 2)
            delete(out);
        end
    catch
        error('A system I/O error occurred while writing the results.');
    end

    copy_result = copyfile(temp,out,'f');

    if (copy_result == 0)
        error('The output file could not be created from the template file.');
    end

    firm_names = ds.FirmNames';

    vars = [ds.DatesStr num2cell(ds.Indicators)];
    labels = [{'Date'} ds.LabelsIndicatorsSimple];
    tab = cell2table(vars,'VariableNames',labels);
    writetable(tab,out,'FileType','spreadsheet','Sheet',ds.LabelsSheetsSimple{1},'WriteRowNames',true);

    vars = [firm_names num2cell(ds.AverageAdjacencyMatrix)];
    labels = {'Firms' ds.FirmNames{:,:}};
    tab = cell2table(vars,'VariableNames',labels);
    writetable(tab,out,'FileType','spreadsheet','Sheet',ds.LabelsSheetsSimple{2},'WriteRowNames',true);

    vars = [firm_names num2cell(ds.AverageBetweennessCentralities') num2cell(ds.AverageClosenessCentralities') num2cell(ds.AverageDegreeCentralities') num2cell(ds.AverageEigenvectorCentralities') num2cell(ds.AverageKatzCentralities') num2cell(ds.AverageClusteringCoefficients')];
    labels = [{'Firms'} strrep(ds.LabelsCentralities,' ','')];
    tab = cell2table(vars,'VariableNames',labels);
    writetable(tab,out,'FileType','spreadsheet','Sheet',ds.LabelsSheetsSimple{3},'WriteRowNames',true);

    worksheets_batch(out,ds.LabelsSheetsSimple,ds.LabelsSheets);

end

%% PLOTTING

function analyse_result(ds)

    safe_plot(@(id)plot_indicators(ds,id));
    safe_plot(@(id)plot_network(ds,id));
    safe_plot(@(id)plot_adjacency_matrix(ds,id));
    safe_plot(@(id)plot_centralities(ds,id));

end

function plot_indicators(ds,id)

    dci = smooth_data(ds.Indicators(:,1));
    cio = smooth_data(ds.Indicators(:,2));
    cioo = smooth_data(ds.Indicators(:,3));

    connections_max = max(max([cio cioo])) * 1.1;

    threshold_indices = dci >= ds.K;
    threshold = NaN(ds.T,1);
    threshold(threshold_indices) = connections_max;

    if (ds.RP)
        label = [' (SST=' num2str(ds.SST) ', K=' num2str(ds.K) ', R)'];
    else
        label = [' (SST=' num2str(ds.SST) ', K=' num2str(ds.K) ')'];
    end

    f = figure('Name','Connectedness Measures > Indicators','Units','normalized','Position',[100 100 0.85 0.85],'Tag',id);

    sub_1 = subplot(2,1,1);
    p1 = plot(sub_1,ds.DatesNum,dci);
    hold on;
        p2 = plot(sub_1,ds.DatesNum,repmat(ds.K,[ds.T 1]),'Color',[1.000 0.400 0.400]);
    hold off;
    set(sub_1,'XLim',[ds.DatesNum(1) ds.DatesNum(end)],'XTickLabelRotation',45);
    set(sub_1,'XGrid','on','YGrid','on');
    legend(sub_1,[p1 p2],'Indicator','Threshold','Location','eastoutside');
    title(sub_1,['DCI' label]);

    sub_2 = subplot(2,1,2);
    a1 = area(sub_2,ds.DatesNum,threshold,'EdgeColor','none','FaceColor',[1.000 0.400 0.400]);
    hold on;
        a2 = area(sub_2,ds.DatesNum,cio,'EdgeColor','none','FaceColor','b');
        if (ds.Groups == 0)
            a3 = area(sub_2,ds.DatesNum,NaN(ds.T,1),'EdgeColor','none','FaceColor',[0.678 0.922 1]);
        else
            a3 = area(sub_2,ds.DatesNum,cioo,'EdgeColor','none','FaceColor',[0.678 0.922 1]);
        end
    hold off;
    set(sub_2,'XLim',[ds.DatesNum(1) ds.DatesNum(end)],'XTickLabelRotation',45,'YLim',[0 connections_max]);
    legend(sub_2,[a2 a3 a1],'CIO','CIOO','Threshold Exceeded','Location','eastoutside');
    title(sub_2,['Connections' label]);

    if (ds.MonthlyTicks)
        date_ticks([sub_1 sub_2],'x','mm/yyyy','KeepLimits','KeepTicks');
    else
        date_ticks([sub_1 sub_2],'x','yyyy','KeepLimits');
    end

    sub_1_position = get(sub_1,'Position');
    sub_2_position = get(sub_2,'Position');
    set(sub_1,'Position',[sub_2_position(1) sub_1_position(2) sub_2_position(3) sub_2_position(4)]);

    figure_title(f,'Indicators');

    maximize_figure(f);

end

function plot_network(ds,id)

    if (ds.Groups == 0)
        group_colors = repmat(lines(1),ds.N,1);
    else
        group_colors = zeros(ds.N,3);
        group_delimiters_len = length(ds.GroupDelimiters);
        group_lines = lines(ds.Groups);

        for i = 1:group_delimiters_len
            group_delimiter = ds.GroupDelimiters(i);

            if (i == 1)
                group_colors(1:group_delimiter,:) = repmat(group_lines(i,:),group_delimiter,1);
            else
                group_delimiter_prev = ds.GroupDelimiters(i-1) + 1;
                group_colors(group_delimiter_prev:group_delimiter,:) = repmat(group_lines(i,:),group_delimiter - group_delimiter_prev + 1,1);
            end

            if (i == group_delimiters_len)
                group_colors(group_delimiter+1:end,:) = repmat(group_lines(i+1,:),ds.N - group_delimiter,1);
            end
        end
    end

    weights = mean(ds.Degrees,1,'omitnan');
    weights = weights ./ mean(weights);
    weights = (weights - min(weights)) ./ (max(weights) - min(weights));
    weights = (weights .* 3.75) + 0.25;

    theta = linspace(0,(2 * pi),(ds.N + 1)).';
    theta(end) = [];
    xy = [cos(theta) sin(theta)];
    [i,j] = find(ds.AverageAdjacencyMatrix);
    [~,order] = sort(max(i,j));
    i = i(order);
    j = j(order);
    x = [xy(i,1) xy(j,1)].';
    y = [xy(i,2) xy(j,2)].';

    f = figure('Name','Connectedness Measures > Network Graph','Units','normalized','Position',[100 100 0.85 0.85],'Tag',id);

    sub = subplot(100,1,10:100);

    hold on;
        for i = 1:size(x,2)
            index = ismember(xy,[x(1,i) y(1,i)],'rows');
            plot(sub,x(:,i),y(:,i),'Color',group_colors(index,:));
        end
    hold off;

    if (ds.Groups == 0)
        hold on;
            for i = 1:size(xy,1)
                line(xy(i,1),xy(i,2),'Color',group_colors(i,:),'LineStyle','none','Marker','.','MarkerSize',(35 + (15 * weights(i))));
            end
        hold off;
    else
        d_inc = ds.GroupDelimiters + 1;

        lines_ref = NaN(ds.Groups,1);
        lines_off = 1;

        hold on;
            for i = 1:size(xy,1)
                group_color = group_colors(i,:);
                line(xy(i,1),xy(i,2),'Color',group_color,'LineStyle','none','Marker','.','MarkerSize',(35 + (15 * weights(i))));

                if ((i == 1) || any(d_inc == i))
                    lines_ref(lines_off) = line(xy(i,1),xy(i,2),'Color',group_color,'LineStyle','none','Marker','.','MarkerSize',35);
                    lines_off = lines_off + 1;
                end
            end
        hold off;

        legend(sub,lines_ref,ds.GroupShortNames,'Units','normalized','Position',[0.725 0.131 0.040 0.076]);
    end

    axis(sub,[-1 1 -1 1]);
    axis('equal','off');

    labels = text((xy(:,1) .* 1.075), (xy(:,2) .* 1.075),ds.FirmNames,'FontSize',10);
    set(labels,{'Rotation'},num2cell(theta * (180 / pi())));

    figure_title(f,'Network Graph');

    maximize_figure(f);

end

function plot_adjacency_matrix(ds,id)

    am = ds.AverageAdjacencyMatrix;
    am(logical(eye(ds.N))) = 0.5;
    am = padarray(am,[1 1],'post');

    off = ds.N + 0.5;

    f = figure('Name','Connectedness Measures > Average Adjacency Matrix','Units','normalized','Position',[100 100 0.85 0.85],'Tag',id);

    pcolor(am);
    colormap([1 1 1; 0.65 0.65 0.65; 0.749 0.862 0.933]);
    axis image;

    ax = gca();
    set(ax,'TickLength',[0 0]);
    set(ax,'XAxisLocation','top','XTick',1.5:off,'XTickLabels',ds.FirmNames,'XTickLabelRotation',45);
    set(ax,'YDir','reverse','YTick',1.5:off,'YTickLabels',ds.FirmNames,'YTickLabelRotation',45);

    figure_title(f,'Average Adjacency Matrix');

    maximize_figure(f);

end

function plot_centralities(ds,id)

    seq = 1:ds.N;

    [bc,order] = sort(ds.AverageBetweennessCentralities);
    bc_names = ds.FirmNames(order);
    [cc,order] = sort(ds.AverageClosenessCentralities);
    cc_names = ds.FirmNames(order);
    [dc,order] = sort(ds.AverageDegreeCentralities);
    dc_names = ds.FirmNames(order);
    [ec,order] = sort(ds.AverageEigenvectorCentralities);
    ec_names = ds.FirmNames(order);
    [kc,order] = sort(ds.AverageKatzCentralities);
    kc_names = ds.FirmNames(order);
    [clc,order] = sort(ds.AverageClusteringCoefficients);
    clc_names = ds.FirmNames(order);

    f = figure('Name','Connectedness Measures > Average Centrality Measures','Units','normalized','Position',[100 100 0.85 0.85],'Tag',id);

    sub_1 = subplot(2,3,1);
    bar(sub_1,seq,bc,'FaceColor',[0.749 0.862 0.933]);
    set(sub_1,'XTickLabel',bc_names);
    title(ds.LabelsCentralities{1});

    sub_2 = subplot(2,3,2);
    bar(sub_2,seq,cc,'FaceColor',[0.749 0.862 0.933]);
    set(sub_2,'XTickLabel',cc_names);
    title(ds.LabelsCentralities{2});

    sub_3 = subplot(2,3,3);
    bar(sub_3,seq,dc,'FaceColor',[0.749 0.862 0.933]);
    set(sub_3,'XTickLabel',dc_names);
    title(ds.LabelsCentralities{3});

    sub_4 = subplot(2,3,4);
    bar(sub_4,seq,ec,'FaceColor',[0.749 0.862 0.933]);
    set(sub_4,'XTickLabel',ec_names);
    title(ds.LabelsCentralities{4});

    sub_5 = subplot(2,3,5);
    bar(sub_5,seq,kc,'FaceColor',[0.749 0.862 0.933]);
    set(sub_5,'XTickLabel',kc_names);
    title(ds.LabelsCentralities{5});

    sub_6 = subplot(2,3,6);
    bar(sub_6,seq,clc,'FaceColor',[0.749 0.862 0.933]);
    set(sub_6,'XTickLabel',clc_names);
    title(ds.LabelsCentralities{6});

    set([sub_1 sub_2 sub_3 sub_4 sub_5 sub_6],'XLim',[0 (ds.N + 1)],'XTick',seq,'XTickLabelRotation',90);
    set([sub_1 sub_2 sub_3 sub_4 sub_5 sub_6],'YGrid','on');

    figure_title(f,'Average Centrality Measures');

    maximize_figure(f);

end

%% VALIDATION

function out = validate_output(out)

    [path,name,extension] = fileparts(out);

    if (~strcmpi(extension,'.xlsx'))
        out = fullfile(path,[name extension '.xlsx']);
    end

end

function temp = validate_template(temp)

    sheets = {'Indicators' 'Average Adjacency Matrix' 'Average Centrality Measures'};
    file_sheets = validate_xls(temp,'T');

    if (~all(ismember(sheets,file_sheets)))
        error(['The template must contain the following sheets: ' sheets{1} sprintf(', %s',sheets{2:end}) '.']);
    end

    worksheets_batch(temp,sheets);

end


function MeshStruct = read_gmsh(fileName)

T=txtfile2cell(fileName);

start_physicals =  find(contains(T,'$PhysicalNames'));
start_entities = find(contains(T,'$Entities'));
start_nodes = find(contains(T,'$Nodes'));
start_elements = find(contains(T,'$Elements'));

entity_info = str2num(T{start_entities+1});
node_info = str2num(T{start_nodes+1});
element_info = str2num(T{start_elements+1});

num_nodes = node_info(2);
num_node_blocks = node_info(1);
num_elements = element_info(2);
num_element_blocks = element_info(1);
num_entities = sum(entity_info);

%%%read physicals
num_physicals = str2num(T{start_physicals+1});
Physicals = T(start_physicals+2:start_physicals+1+num_physicals);
for i = 1:size(Physicals, 1)
    Physicals{i} = strsplit(Physicals{i},' ');
    Physicals{i}{1} = str2num(Physicals{i}{1});
    Physicals{i}{2} = str2num(Physicals{i}{2});
end

%%%read and sort entities
Entities = cell(4,1);
Points = {};Curves = {};Surfaces = {};Volumes = {};
line = start_entities+2;
for i = 1:entity_info(1)
    Temp = str2num(T{line});
    Points{i,1} = Temp;
    line = line+1;
end
for i = 1:entity_info(2)
    Temp = str2num(T{line});
    Curves{i,1} = Temp;
    line = line+1;
end
for i = 1:entity_info(3)
    Temp =  str2num(T{line});
    Surfaces{i,1} = Temp;
    line = line+1;
end
for i = 1:entity_info(4)
    Temp = str2num(T{line});
    Volumes{i,1} = Temp;
    line = line+1;
end
Entities{1} = Points;
Entities{2} = Curves;
Entities{3} = Surfaces;
Entities{4} = Volumes;

%%%read and sort nodes
node_block_info = zeros(num_node_blocks,4);
node_block_numbers = cell(num_node_blocks,1);
node_block_coords = cell(num_node_blocks,1);
line = start_nodes+2;
for i = 1:num_node_blocks

    node_block_info_i = str2num(T{line});
    node_block_size= node_block_info_i(1,4);

    node_block_info(i,:) = node_block_info_i;
    node_block_numbers_temp = T(line+1:line+node_block_size);
    node_block_coords_temp = T(line+node_block_size +1:line+node_block_size+node_block_size);
    line = line+2*node_block_size+1;

    for j = 1:node_block_size
        node_block_numbers{i}(j,:) = str2num(node_block_numbers_temp{j});
        node_block_coords{i}(j,:) = str2num(node_block_coords_temp{j});
    end

end

%%%Read and sort elements
element_block_info = zeros(num_element_blocks,4);
element_block_numbers = cell(num_element_blocks,1);
line = start_elements+2;
for i = 1:num_element_blocks

    element_block_info_i = str2num(T{line});element_block_size= element_block_info_i(1,4);
    element_block_info(i,:) = element_block_info_i;
    element_block_numbers_temp = T(line+1:line+element_block_size);

    for j = 1:element_block_size
        element_block_numbers{i}(j,:) = str2num(element_block_numbers_temp{j});
    end

    line = line+element_block_size+1;
end

node_ids = [];
nodes = [];
for i = 1:num_node_blocks

    nodes = [nodes;node_block_coords{i}];
    node_ids = [node_ids;node_block_numbers{i}];

end

nodes = double(nodes);
Mesh.Physicals = Physicals;
Mesh.entity_info = entity_info;
Mesh.node_info = node_info;
Mesh.element_info = element_info;
Mesh.nodes = nodes;
Mesh.Node_Blocks = node_block_numbers;
Mesh.Node_ids = node_ids;
Mesh.node_block_info = node_block_info;
Mesh.Entities = Entities;
Mesh.Elements = element_block_numbers;
Mesh.element_block_info = element_block_info;

%%
vol_ct = 0;
surf_ct = 0;
curve_ct= 0;

[~,part_name,~] = fileparts(fileName);

 node_ids = Mesh.Node_ids; %The node id's
 nodes = Mesh.nodes; %The nodel coordinates

 %%

for j = 1:size(Mesh.Physicals,1)

    physical = Mesh.Physicals{j};
    phys_name = physical{3}(2:end-1);
    dim = physical{1};
    tag = physical{2};

    switch dim
        case 3

            vol_ct = vol_ct+1;
            entities = Mesh.Entities{4,1};
            element_ids = [];
            element_mat = [];

            for k = 1:size(entities, 1)
                num_phys = entities{k}(8);
                phys_tags = entities{k}(9:9+num_phys-1);
                if sum(find(phys_tags==tag))
                   entity_tag = entities{k}(1);
                   elements_3 = (Mesh.element_block_info(:,1) == dim).*Mesh.element_block_info;
                   entity_ind = find(elements_3(:,2)==entity_tag);
                   element_ids = [element_ids;Mesh.Elements{entity_ind}(:,1)];
                   element_mat = [element_mat;Mesh.Elements{entity_ind}(:,2:end)];
                end

            end

            %febio_spec.Mesh.Elements{vol_ct}.ATTR.type='tet4'; %Element type
            volumes_names{vol_ct,1} = phys_name; %Name of this part
            elements_IDs_blocks{vol_ct} = element_ids; %Element id's
            elements_blocks{vol_ct} = element_mat; %The element matrix

        case 2

            surf_ct = surf_ct+1;
            entities = Mesh.Entities{3,1};
            element_mat = [];
            element_start = 0;

            for k = 1:size(entities, 1)
                num_phys = entities{k}(8);
                phys_tags = entities{k}(9:9+num_phys-1);
                if sum(find(phys_tags==tag))
                   entity_tag = entities{k}(1);
                   elements_2 = (Mesh.element_block_info(:,1) == dim).*(Mesh.element_block_info(:,2) == entity_tag);
                   entity_ind = find(elements_2);
                   element_mat = [element_mat;Mesh.Elements{entity_ind}(:,2:end)];
                   element_start = element_start+1;
                   element_start = element_start+size(element_mat,1);
                end

            end

            element_ids = [1:size(element_mat,1)]';
            facesBoundary_Names{surf_ct,1} = phys_name; %Name of this surface
            facesBoundary_IDs_Block{surf_ct} = element_ids; %Element id's
            facesBoundary_Block{surf_ct} =element_mat; %The element matrix

            case 1

            curve_ct = curve_ct+1;
            entities = Mesh.Entities{2,1};
            element_mat = [];
            element_start = 0;

            for k = 1:size(entities, 1)
                num_phys = entities{k}(8);
                phys_tags = entities{k}(9:9+num_phys-1);
                if sum(find(phys_tags==tag))
                   entity_tag = entities{k}(1);
                   elements_1 = (Mesh.element_block_info(:,1) == dim).*(Mesh.element_block_info(:,2) == entity_tag);
                   entity_ind = find(elements_1);
                   element_mat = [element_mat;Mesh.Elements{entity_ind}(:,2:end)];
                   element_start = element_start+1;
                   element_start = element_start+size(element_mat,1);
                end

            end

             element_ids_block{curve_ct} = element_mat(:,1);
             curve_names{curve_ct} = phys_name; %Name of this curve
             curve_elements_block{curve_ct} = element_ids; %Element id's

   end
end

%%
facesBoundary = [];
boundaryMarker = [];
elements = [];
elementsMaterialID = [];

%%
for i = 1:vol_ct

    elementMaterialID_block = ones(length(elements_IDs_blocks{i}),1).*i;
    elements = [elements;elements_blocks{i}];
    elementsMaterialID = [elementsMaterialID;elementMaterialID_block];

end

for i = 1:surf_ct

    boundaryMarker_block = ones(length(facesBoundary_IDs_Block{i}),1).*i;
    facesBoundary = [facesBoundary;facesBoundary_Block{i}];
    boundaryMarker = [boundaryMarker;boundaryMarker_block];

end

% for i = 1:curve_ct
%
% end

MeshStruct.node_IDs = node_ids;
MeshStruct.nodes = nodes;
MeshStruct.facesBoundary = facesBoundary;
MeshStruct.boundaryMarker = boundaryMarker;
MeshStruct.elements = elements;
MeshStruct.elementMaterialID = elementsMaterialID;
MeshStruct.loadNameStruct.MeshName = part_name;
MeshStruct.loadNameStruct.VolumeNames = volumes_names;
MeshStruct.loadNameStruct.SurfaceNames = facesBoundary_Names;

%                 nodes: [415×3 double]
%         facesBoundary: [320×3 double]
%        boundaryMarker: [320×1 double]
%                 faces: [8144×3 double]
%              elements: [2036×4 double]
%     elementMaterialID: [2036×1 double]
%        faceMaterialID: [8144×1 double]
%        loadNameStruct: [1×1 struct]

%%
% _*GIBBON footer text*_
%
% License: <https://github.com/gibbonCode/GIBBON/blob/master/LICENSE>
%
% GIBBON: The Geometry and Image-based Bioengineering add-On. A toolbox for
% image segmentation, image-based modeling, meshing, and finite element
% analysis.
%
% Copyright (C) 2006-2022 Kevin Mattheus Moerman and the GIBBON contributors
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.


function fiberPath = dtiFiberTensorlinePreinterp(seedPoint, dt6, faImg, voxSize, faThresh, angleThresh, ...
    stepSizeMm, wPuncture, whichAlgorithm)

% fiberPath = dtiFiberTensorlinePreinterp(seedPoint, dt6, faImg, voxSize, faThresh, angleThresh, stepSizeMm, wPuncture, whichAlgorithm)
%
% Implementation of Lazar's Tensorline algorithm. Eg:
% White Matter Tractography Using Diffusion Tensor Deflection.
% Lazar, et. al. Human Brain Mapping 18:306 321(2003).
%
%
% Can be used to find Tensorline, FACT, or TEND fiber tracing
% Choosing Algorithm using whichAlgorithm:
%   0 = TensorLine
%   1 = FACT
%   2 = TEND
%
% Step Size:
% Determines resolution of DTI grid; default is 2x2x2mm voxels
% 1. Step Size < voxel size of inputted DTI matrix.
% Algorithm is forced to reinterpolate tensors at each point (SLOW!).
% 2. Step Size > voxel size of inputted matrix
% Step Size is reinitialized to voxel size of inputted matrix.
% Algorithm uses "nearest neighbor" concept to find proper tensor
% from the inputted dt6 file
%
% RETURNS:
% fiberPath, a list of coordinates (in voxels, but may be real-valued)
% that define the fiber path.
%
% HISTORY:
%   2004.02.09 GSM (gmulye@stanford.edu) wrote it.
%   2004.02.09 GSM Minor updates, more userdefined parameters
%   2004.02.13 GSM Modified to use pre-interpolated tensors

%voxSize = [1 1 1]'; %%%FORCE SMALL VOXELS
%oneMmDt6GridSize = [145 196 131 6]; %Size of dt6 array with 1x1x1 voxels
%origDt6GridSize = size(dt6); %Size of dt6 array inputted into function
%origDt6VoxSize = (origDt6GridSize + 1) ./ oneMmDt6GridSize; %vox size of inputted array

stepSize = stepSizeMm*ones(3,1);

if all(voxSize <= stepSize)
    reinterp = 0;
else
    reinterp = 1;
end

%stepSize = stepSizeMm./voxSize; %Step size in voxels - voxSize is 3x1 column vector

% Initialize variables for tracing
% Find major direction (originalDir) of seedPoint voxel
if (reinterp == 0)
    %   voxCoords = nearestNeighbor(seedPoint,voxSize);
    voxCoords = round(seedPoint);
    dt6Tensor = dt6(voxCoords(1),voxCoords(2),voxCoords(3),1:6);
    dt6Tensor = dt6Tensor(:); %Vectorize
elseif (reinterp == 1)
    dt6Tensor = findTensor(seedPoint,dt6,voxSize);
end
[originalDir junk] = majorEigVec(dt6Tensor);
nextDir = originalDir; % Initialize to seedPoint direction
nextPosition = seedPoint; %Absolute position in voxels
fiberPath = seedPoint; % First point is seed point
fiberPath = tracer(whichAlgorithm,nextPosition,originalDir,voxSize,dt6,reinterp,faImg,faThresh,angleThresh,wPuncture,stepSize,fiberPath,1);
fiberPath = tracer(whichAlgorithm,nextPosition,originalDir,voxSize,dt6,reinterp,faImg,faThresh,angleThresh,wPuncture,stepSize,fiberPath,-1);
return;

function fiberPath = tracer(whichAlgorithm,nextPosition,nextDir,voxSize,dt6,reinterp,faImg,faThresh,angleThresh,wPuncture,stepSize,fiberPath,fwdBkwd)
%==================
% MAIN FUNCTION
%==================
%Traces fiberpath forwards and backwards
iter = 0;
done = 0;
maxIter = 1000;
imSize = size(dt6(:,:,:,1));
nextDir = fwdBkwd*nextDir;
while (~done & iter<maxIter)
    dir = nextDir; %Direction vectors are all 3x1 column vectors
    currentPosition = nextPosition; %Position coordinates are 1x3 row vectors
    if(any(currentPosition<1) | any(currentPosition>imSize))
        disp('Tracking terminated: path wandered outside image data.');
        done = 1;
    else
        % Get the FA for this voxel
        vCoord = floor(currentPosition);
        fa = faImg(vCoord(1), vCoord(2), vCoord(3));

        % check dir data is valid (we are in range)
        if (fa <= faThresh | sum(dir) == 0 | isnan(fa))
            disp(['Tracking terminated: fa=',num2str(fa)]);
            done = 1;
        else
            nextPosition = currentPosition + (stepSize'./voxSize') .*dir'; %convert stepSize to voxels
            % Find next diffusion tensor
            if (reinterp == 0)
                %voxCoords = nearestNeighbor(nextPosition,voxSize);
                voxCoords = round(nextPosition);
                if(any(voxCoords==0) | any(voxCoords>imSize) | ~any(isfinite(voxCoords)))
                    disp(['Tracking terminated: path wandered outside image']);
                    done = 1;
                    voxCoords(voxCoords==0) = 1;
                else
                    nextDt6Tensor = dt6(voxCoords(1),voxCoords(2),voxCoords(3),1:6);
                    nextDt6Tensor = nextDt6Tensor(:); %Vectorize
                end
            elseif (reinterp == 1)
                nextDt6Tensor = findTensor(nextPosition,dt6,voxSize);
            end
            % FACT direction, checked to see if angle is less than
            % threshold
            [factDir, nextTensor] = majorEigVec(nextDt6Tensor);
            [smallAngle,direction,angle] = checkAngle(dir,factDir,angleThresh);
            if (smallAngle & (direction == -1))
                factDir = -factDir;
            end
            % TEND direction
            tendDir = nextTensor*dir;
            nd = norm(tendDir);
            if(nd<=0)
                tendDir = tendDir/nd;
            else
                tendDir = NaN;
            end
            % Tensorline Algorithm
            if (whichAlgorithm == 0)
                if(tendDir==NaN)
                    disp(['Tracking terminated: tendDir = NaN']);
                    done = 1;
                else
                    if (~smallAngle) % Turning too sharp, ignore FACT vector
                        nextDir = (1-wPuncture)*dir + wPuncture*tendDir;
                    else  % Normal case: Use FACT vector
                        nextDir = fa*factDir + (1-fa)*((1-wPuncture)*dir + wPuncture*tendDir);
                    end
                end
                % FACT only
            elseif (whichAlgorithm == 1)
                if (~smallAngle) % Angle too big
                    done = 1; % End tracing due to sharp turn
                    disp(['Tracking terminated: angle (' num2str(round(angle)) ') exceeds threshold.']);
                else
                    nextDir = factDir;
                end
                % TEND only
            elseif (whichAlgorithm == 2)
                if(tendDir==NaN)
                    disp(['Tracking terminated: tendDir = NaN']);
                    done = 1;
                else
                    nextDir = tendDir;
                end
            end
            % Append to fiberpath
            if (fwdBkwd == 1)
                fiberPath = [fiberPath; nextPosition];
            elseif (fwdBkwd == -1)
                fiberPath = [nextPosition;fiberPath];
            end
        end
    end
end
return;

function dt6Tensor = findTensor(point,dt6,voxSize)
persistent initialized;
% Finds tensor interpolation at any point and return dt6 tensor
curPosMm = point.*voxSize';
if(isempty(initialized))
    dt6Tensor = dtiTensorInterp(dt6, curPosMm, voxSize', 1);
    initialized = 1;
else
    dt6Tensor = dtiTensorInterp([], curPosMm, voxSize', 1);
end
return;

function intVox = nearestNeighbor(realCoord,voxSize)
% Rounds mm input into voxel coordinates
decVox = realCoord./voxSize'; %decimal voxels is 1x3
intVox = round(decVox);
return;

function [majorDir, fullTensor] = majorEigVec(dt6Tensor)
% Finds major direction for given tensor (in 3x3 format)
fullTensor = [dt6Tensor(1) dt6Tensor(4) dt6Tensor(5); ...
        dt6Tensor(4) dt6Tensor(2) dt6Tensor(6); ...
        dt6Tensor(5) dt6Tensor(6) dt6Tensor(3)];
[eigVec,eigVal] = eig(fullTensor);
[maxVal,i] = max(max(eigVal));
majorDir = eigVec(:,i);
%majorDir = [majorDir(2) majorDir(3) majorDir(1)]';
return;

function [angleCheck, direction, angle] = checkAngle(a,b,angleThresh)
% Checks the angle between 2 vectors, returns if angle is less than thresh
% The angle between two vectors is given by acos(aDOTb/{mag(a)*mag(b)})
anglePos = 180*acos(a'*b)/pi; % In degrees; both vectors are unit vectors
angleNeg = 180*acos(-a'*b)/pi; % Angle with one vector reversed
[angle,i] = min(abs([anglePos angleNeg]));
direction = 0;
if (angle <= angleThresh)
    angleCheck = 1; % Angle is within permissable range
    if (i==2)
        direction = -1; % Use FACT vector in opposite direction
    end
else
    angleCheck = 0; % Does not pass check
end
return


function [fSet] = featureSetReduction_LGG(pathMINE,outcome,setSize,nonTextStruct,textCells,textCellsName,paramAll,paramUsed,baseline,alpha,delta,nBoot,seed,batchNum)
% -------------------------------------------------------------------------
% function [fSet] = featureSetReduction_LGG(pathMINE,outcome,setSize,nonTextStruct,textCells,textCellsName,paramAll,paramUsed,baseline,alpha,delta,nBoot,seed,batchNum)
% -------------------------------------------------------------------------
% DESCRIPTION:
% This function computes feature set reduction for an experiment with a
% specific degree of freedom on texture extraction parameters as defined by
% 'paramUsed', according to the methodology described in ref. [1].
% -------------------------------------------------------------------------
% REFERENCE:
% [1] Vallieres, M. et al. (2015). A radiomics model from joint FDG-PET and
%     MRI texture features for the prediction of lung metastases in soft-tissue
%     sarcomas of the extremities. Physics in Medicine and Biology, 60(14),
%     5471-5496. doi:10.1088/0031-9155/60/14/5471
% -------------------------------------------------------------------------
% INPUTS:
% 1. pathMINE: Full path to the MINE.jar executable. The executable can be
%              downloaded at: <http://www.exploredata.net/>.
% 2. outcome: Column vector of size [nInst X 1] specifying the outcome status
%             (1 or 0) for all instances.
% 3. setSize: Size of the output feature set (typically set to 25 in ref. [1]).
% 4. nonTextStruct: Structure data for non-texture features. This structure
%                   is of the same format as the one saved as output to
%                   computeAllNonTextureFeatures_LGG.m, for example.
% 5. textCells: Cell vector of organized texture cells for all the
%               different scans. Format: textCells = {cell1, cell2, etc.},
%               where cell1, cell2, etc. are the files saved as output to
%               organizeData_LGG.m
% 6. textCellsName: Cell of strings corresponding to the name of the
%                   corresponding cells in textCells.
% 7. paramAll: Cell vector incorporating all texture extraction parameters
%              tested in textCells. See EXAMPLE below for more details.
% 8. paramUsed: Vector of 1's and 0's to specify the degree of freedom on
%               texture extraction parameters for the current experiment.
%               For example, for an experiment where extraction parameters
%               1, 2 and 3 in paramAll are allowed to vary, use
%               paramUsed = [1,1,1].
% 9. baseline: Vector of numerical values specifying the baseline texture
%              extraction parameters for each entry in paramAll. See EXAMPLE
%              below for more details.
% 10. alpha: Numerical values specifying the coefficient of the first part of
%            the Gain equation, as defined in ref. [1].
% 11. delta: Numerical values specifying the coefficient of the second part
%            of the Gain equation, as defined in ref. [1] (third part is set
%            to 0 in this function).
% 12. nBoot: Number of bootstrap samples to use.
% 13. batchNum: (optional input). If present, integer that specifies the
%               batch number for parallelization purposes
%
% See masterScript_LGG.m for a complete example of how to utilize the
% current function.
% -------------------------------------------------------------------------
% OUTPUTS:
% 1. fSET: Structure specifying the resulting feature set for the current
%          experiment.
%          --> fSET.Data: Array of size [nPatient X setSize], specifying the
%                         numerical data of the chosen features, where 'nInst'
%                         refers to the number of instances.
%          --> fSET.Info: Vector of cells with strings specifying information
%                         about the chosen features.
% -------------------------------------------------------------------------
% EXAMPLE:
% scale_mat = [1,2,3,4,5];
% algo_cell = {'Equal','Uniform'};
% Ng_mat = [8,16,32,64];
%
% paramAll = {scale_mat,algo_cell,Ng_mat};
% paramUsed = [1 1 1]; (example for a given experiment)
% baseline = [1,2,3];
% -------------------------------------------------------------------------
% AUTHOR(S): Martin Vallieres <mart.vallieres@gmail.com>
% -------------------------------------------------------------------------
% HISTORY:
% - Creation: January 2017
%--------------------------------------------------------------------------
% STATEMENT:
% This file is part of <https://github.com/mvallieres/radiomics/>,
% a package providing MATLAB programming tools for radiomics analysis.
% --> Copyright (C) 2015-2017  Martin Vallieres
%
%    This package is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    This package is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with this package.  If not, see <http://www.gnu.org/licenses/>.
% -------------------------------------------------------------------------

% RANDOM NUMBER GENERATOR SEED
rng(seed);

% INITIALIZATION
nInst = numel(outcome); % Number of patients
nScan = numel(textCellsName); % Number of scans included in that feature set experiment
fSet = struct; fSet.Data=zeros(nInst,setSize); fSet.Info=cell(setSize,1); % Initializing final feature set structure
if nargin == 15
    micName = ['batch',num2str(batchNum)];
else
    micName = 'master';
end
if ~isempty(strfind(textCellsName{1},'CT'))
    name = 'CT';
else
    name = 'MR';
end

% Initializing names
nonTextName = fieldnames(nonTextStruct); nNonText = numel(nonTextName);
textTypeName = fieldnames(textCells{1}{1}); nTextType = numel(textTypeName);
nameF = cell(1,nTextType);
nText = 0;
for t = 1: nTextType
    nameF{t} = fieldnames(textCells{1}{1}.(textTypeName{t}));
    nText = nText + numel(nameF{t});
end
textName = cell(nText,1);
count = 1;
for t = 1:nTextType
    for f = 1:numel(nameF{t})
        textName{count} = [textTypeName{t},'-',nameF{t}{f}];
        count = count +1;
    end
end
nFeatures = nNonText + nScan*nText;
cellNames = cell(nFeatures,2);
count = 1;
for i = 1:nScan
    for j = 1:nText
        cellNames{count,1} = textCellsName{i};
        cellNames{count,2} = textName{j};
        count = count + 1;
    end
end
for i = 1:nNonText
    cellNames{count,1} = nonTextName{i};
    count = count + 1;
end

% Extraction parameters used in that experiment
nParamType = numel(paramUsed);
param = cell(1,nParamType);
paramSize = ones(1,nParamType);
nParam = 1;
for i = 1:nParamType
    if paramUsed(i)
        temp = length(paramAll{i});
        nParam = nParam * temp;
        paramSize(i) = temp;
        param{i} = paramAll{i};
    end
end

% FILLING UP WORKING DATA MATRIX
matData = zeros(nFeatures,nParam,nInst); % Matrix containing all the data vectors for each feature (used for part 2 of Gain equation)
count = 1;
for i = 1:nScan
    textCell = textCells{i};
    textCell = removeParam(textCell,paramUsed,baseline);
    for t = 1:nTextType
        for f = 1:numel(nameF{t})
            for p = 1:numel(textCell)
                matData(count,p,:) = textCell{p}.(textTypeName{t}).(nameF{t}{f}).Data;
            end
            count = count + 1;
        end
    end
end
for i = 1:nNonText
    matData(nText*nScan + i,1,:) = nonTextStruct.(nonTextName{i}).Data;
    for j = 2:nParam
        matData(nText*nScan + i,j,:) = matData(nText*nScan + i,1,:);
    end
end

% IMPORTANT: IF NAN, IT WOULD MEAN SOMETHING WENT WRONG IN SOME TEXTURE CALCULATIONS
matData(isnan(matData(:))) = 0;

% GETTING BOOTSTRAP SAMPLES TO BE USED FOR ALL EXPERIMENTS (using imbalance-adjusted resampling)
[bootSam,~] = buildBootSet(outcome,nBoot,'adjust');

% GETTING BOOTSTRAP RESULTS FOR THE FIRST PART OF THE GAIN EQUATION (ref. [1])
matCorr = zeros(nFeatures,nParam); % Matrix of Spearman correlation of each feature with the outcome (part 1 of Gain equation)
if sum(paramUsed(:)) % Need a transpose
    for n = 1:nBoot
        dataBoot = matData(:,:,bootSam(:,n));
        outcomeBoot = outcome(bootSam(:,n));
        for i = 1:size(dataBoot,1)
            matCorr(i,:) = matCorr(i,:) + corr(squeeze(dataBoot(i,:,:))',outcomeBoot,'type','Spearman','rows','pairwise')';
        end
    end
else % No transpose
    for n = 1:nBoot
        dataBoot = matData(:,:,bootSam(:,n));
        outcomeBoot = outcome(bootSam(:,n));
        for i = 1:size(dataBoot,1)
            matCorr(i,:) = matCorr(i,:) + corr(squeeze(dataBoot(i,:,:)),outcomeBoot,'type','Spearman','rows','pairwise')';
        end
    end
end
matCorr = abs(matCorr./nBoot);

% CHOOSING FIRST FEATURE(depends only on Spearman's correlation)
indChosen = zeros(setSize-1,1);

% Choosing feature
[~,index] = max(matCorr(:));
[row,col] = ind2sub([nFeatures,nParam],index);
indChosen(1) = row;
fSet.Data(:,1) = squeeze(matData(row,col,:));

% Obtaining feature name
if row <= nScan*nText
    [ind1,ind2,ind3] = ind2sub(paramSize,col);
    indBase = [ind1,ind2,ind3];
    indFinal = zeros(1,3);
    for i = 1:3
        if isempty(param{i})
            indFinal(i) = baseline(i);
        else
            indFinal(i) = indBase(i);
        end
    end
    string = [cellNames{row,1},'(R=',num2str(1,'%.2f'),',Scale=',num2str(paramAll{1}(indFinal(1))),',Quant.algo=',paramAll{2}{indFinal(2)},',Ng=',num2str(paramAll{3}(indFinal(3))),')--',cellNames{row,2}];
else % This is a nonTexture feature
    string = cellNames{row,1};
end
fSet.Info{1} = string;

% COMPUTING FOR OTHER FEATURES
PICtest = zeros(nFeatures,setSize-1);
for f = 1:setSize-1

    % Computing PIC for all bootstrap samples
    for n = 1:nBoot
        varBoot = fSet.Data(bootSam(:,n),f);
        dataBoot = matData(:,:,bootSam(:,n));
        dataBootAv = squeeze(mean(dataBoot,2))';
        try
            PICtest(:,f) = PICtest(:,f) + (1 - applyMIC(pathMINE,varBoot,dataBootAv,micName));
        catch % SOLVE THAT PROBLEM
            PICtest(:,f) = PICtest(:,f) + zeros(nFeatures,1);
        end
    end
    PICtest(:,f) = PICtest(:,f)./nBoot;
    PICtemp = zeros(nFeatures,1);
    for k = 1:f
        PICtemp = PICtemp + 2*(f-k+1)/(f*(f+1)).*PICtest(:,k);
    end
    PIC = repmat(PICtemp,1,nParam);

    % Choosing feature
    Gain = alpha.*matCorr + delta.*PIC;
    [~,index] = sort(Gain(:),'descend'); best = 1;
    [row,col] = ind2sub([nFeatures,nParam],index(best));
    while ~isempty(find(indChosen==row))
        best = best + 1;
        [row,col] = ind2sub([nFeatures,nParam],index(best));
    end
    indChosen(f+1) = row;
    fSet.Data(:,f+1) = squeeze(matData(row,col,:));

    % Obtaining feature name
    if row <= nScan*nText
        [ind1,ind2,ind3] = ind2sub(paramSize,col);
        indBase = [ind1,ind2,ind3];
        indFinal = zeros(1,3);
        for i = 1:3
            if isempty(param{i})
                indFinal(i) = baseline(i);
            else
                indFinal(i) = indBase(i);
            end
        end
        string = [cellNames{row,1},'(R=',num2str(1,'%.2f'),',Scale=',num2str(paramAll{1}(indFinal(1))),',Quant.algo=',paramAll{2}{indFinal(2)},',Ng=',num2str(paramAll{3}(indFinal(3))),')--',cellNames{row,2}];
    else % This is a nonTexture feature
        string = cellNames{row,1};
    end
    fSet.Info{f+1} = string;
end

end


function [mask3MU, zValues] = getMask3D(structNum,planC)
%function [mask3MU, zValues] = getMask3D(structNum,planC)
%Assemble the 3-D CT-registered mask (type UINT8).
%JOD.
%Latest modifications:  JOD, 19 Feb 03, added zValues output.
%
% Copyright 2010, Joseph O. Deasy, on behalf of the CERR development team.
%
% This file is part of The Computational Environment for Radiotherapy Research (CERR).
%
% CERR development has been led by:  Aditya Apte, Divya Khullar, James Alaly, and Joseph O. Deasy.
%
% CERR has been financially supported by the US National Institutes of Health under multiple grants.
%
% CERR is distributed under the terms of the Lesser GNU Public License.
%
%     This version of CERR is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or
%     (at your option) any later version.
%
% CERR is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
% without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
% See the GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with CERR.  If not, see <http://www.gnu.org/licenses/>.

indexS = planC{end};

scanNum = getStructureAssociatedScan(structNum, planC);

ROIImageSize   = [planC{indexS.scan}(scanNum).scanInfo(1).sizeOfDimension1  planC{indexS.scan}(scanNum).scanInfo(1).sizeOfDimension2];

numSlices = length(planC{indexS.scan}(scanNum).scanInfo);

mask3MU = false(ROIImageSize(1),ROIImageSize(2),numSlices);

zValues = [];

for sliceNum = 1 : numSlices

  z    = planC{indexS.scan}(scanNum).scanInfo(sliceNum).zValue;

  zValues = [zValues, z];

  [segmentsM, planC, isError] = getRasterSegments(structNum, planC);
%  segmentsM = planC{indexS.structures}(structNum).rasterSegments;

  indV = find(segmentsM(:,1) == z);  %mask values on this slice

  segmentsM = segmentsM(indV(:),7:9);     %segments

  %reconstruct the mask:

  for j = 1 : size(segmentsM,1)
    mask3MU(segmentsM(j,1),segmentsM(j,2):segmentsM(j,3),sliceNum) = 1;
  end

end


function [randCat] = syn_randomize_catalog(mCatalog, bLon, bLat, bDepth, bTimes, nMagnitudes, fBValue, fMc, fInc)
    %  Randomizes a given catalog.
    %
    %  [randCat] = syn_randomize_catalog(mCatalog, bLon, bLat, bDepth,
    %                                                   bTimes, nMagnitudes, fBValue, fMc, fInc)
    %
    %
    % Input parameters:
    %   mCatalog          Catalog for randomizing
    %   bLon              Perturb longitudes (true) or leave longitudes unchanged (false)
    %   bLat              Perturb latitudes (true) or leave latitudes unchanged (false)
    %   bDepth            Perturb depths (true) or leave depths unchanged (false)
    %   bTimes            Perturb focal times (true) or leave focal times unchanged (false)
    %   nMagnitudes       Magnitude switch
    %                     1: Leave magnitudes unchanged
    %                     2: Generate new magnitudes according to parameters fBValue, fMc, fInc
    %                     3: Perturb magnitudes
    %   fBValue           b-value for new magnitudes
    %   fMc               magnitude of completeness for new magnitudes
    %   fInc              magnitude increment for new magnitudes
    %
    % Output parameters:
    %   randCat     Randomized catalog
    %
    % Danijel Schorlemmer
    % April 29, 2002

    randCat = copy(mCatalog);
    if isnumeric(nMagnitudes)
        if nMagnitudes == 3
            nMagnitudes = 'perturb';
        else
            nMagnitudes = 'create';
        end
    end
    % Permute longitudes
    if bLon
        randCat.Longitude = randCat.Longitude(randperm(randCat.Count));
    end

    % Permute latitudes
    if bLat
        randCat.Latitude = randCat.Latitude(randperm(randCat.Count));
    end

    % Permute depths
    if bDepth
        randCat.Depth = randCat.Depth(randperm(randCat.Count));
    end

    % Permute times
    if bTimes
        randCat.Date = randCat.Date(randperm(randCat.Count));
    end

    if nMagnitudes == "perturb"  %perturb magnitudes
        randCat.Magnitude = randCat.Magnitude(randperm(randCat.Count));
    elseif nMagnitudes == "create" % create new magnitudes
        randCat.Magnitude= syn_create_magnitudes(randCat.Count, fBValue, fMc, fInc);
    end
end