function [ node_xyz, triangle_node ] = ply_to_tri_mesh ( ply_file_name )

%*****************************************************************************80
%
%% PLY_TO_TRI_MESH converts data from a PLY file to TRI_MESH data.
%
%  Discussion:
%
%    This routine reads a PLY file, searches for the data that defines
%    a polygonal surface, reduces polygons to triangles, and returns
%    a pair of arrays that define a TRI_MESH, that is a triangular
%    mesh of a surface in 3D.
%
%    The original version of this program had a mistake that meant it
%    did not properly triangulate files whose faces were not already triangular.
%    This has been corrected (JVB, 25 February 2007).
%
%    Glenn Ramsey pointed out and corrected a problem that occurred
%    with an uninitialized value of Type2, 27 August 2012.
%
%  Example:
%
%     [ node_xyz, triangle_node ] = ply_to_tri_mesh ( 'cow.ply' );
%
%     trisurf ( triangle_node', node_xyz(1,:), node_xyz(2,:), node_xyz(3,:) );
%
%     colormap ( gray );
%     axis equal;
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    27 August 2012
%
%  Author:
%
%    Pascal Getreuer 2004
%    Modifications by John Burkardt
%
%  Parameters:
%
%    Input, string PLY_FILE_NAME, the name of the PLY file to be read.
%
%    Output, real NODE_XYZ(3,NODE_NUM), the coordinates of the nodes.
%
%    Output, integer TRIANGLE_NODE(3,TRIANGLE_NUM), the indices of nodes
%    that make up the triangles.
%
%  Local Parameters:
%
%    COMMENTS, any comments from the file.
%
%    ELEMENTCOUNT, the number of each type of element in file.
%
%    ELEMENTS, the element data.
%
%    LISTFLAG is 1 if there is a list type.
%
%    PROPERTYTYPES, the element property types.
%
%    SAMEFLAT is 1 if all types are the same.
%
%    SIZEOF, size in bytes of each type.
%

%
%  Open the input file in "read text" mode.
%
  [ fid, Msg ] = fopen ( ply_file_name, 'rt' );

  if ( fid == -1 )
    error ( Msg );
  end

  Buf = fscanf ( fid, '%s', 1 );

  if ( ~strcmp ( Buf, 'ply' ) )
    fclose ( fid );
    error('Not a PLY file.');
  end
%
%  Read the header.
%
  Position = ftell(fid);
  Format = '';
  NumComments = 0;
  Comments = {};
  NumElements = 0;
  NumProperties = 0;
  Elements = [];
  ElementCount = [];
  PropertyTypes = [];
  ElementNames = {};  % list of element names in the order they are stored in the file
  PropertyNames = [];  % structure of lists of property names

  while ( 1 )
%
%  Read a line from the file.
%
    Buf = fgetl ( fid );
    BufRem = Buf;
    Token = {};
    Count = 0;
%
%  Split the line into tokens.
%
    while ( ~isempty(BufRem) )

      [ tmp, BufRem ] = strtok(BufRem);
%
%  Count the tokens.
%
      if ( ~isempty ( tmp ) )
        Count = Count + 1;
        Token{Count} = tmp;
      end

    end
%
%  Parse the line.
%
    if ( Count )

      switch lower ( Token{1} )
%
%  Read the data format.
%
      case 'format'

        if ( 2 <= Count )

          Format = lower ( Token{2} );

          if ( Count == 3 & ~strcmp ( Token{3}, '1.0' ) )
            fclose ( fid );
            error('Only PLY format version 1.0 supported.');
          end
        end
%
%  Read a comment.
%
      case 'comment'

        NumComments = NumComments + 1;
        Comments{NumComments} = '';
        for i = 2 : Count
          Comments{NumComments} = [Comments{NumComments},Token{i},' '];
        end
%
%  Read an element name.
%
      case 'element'

        if ( 3 <= Count )

          if ( isfield(Elements,Token{2}) )
            fclose ( fid );
            error(['Duplicate element name, ''',Token{2},'''.']);
          end

          NumElements = NumElements + 1;
          NumProperties = 0;
          Elements = setfield(Elements,Token{2},[]);
          PropertyTypes = setfield(PropertyTypes,Token{2},[]);
          ElementNames{NumElements} = Token{2};
          PropertyNames = setfield(PropertyNames,Token{2},{});
          CurElement = Token{2};
          ElementCount(NumElements) = str2double(Token{3});

          if ( isnan(ElementCount(NumElements)) )
            fclose ( fid );
            error(['Bad element definition: ',Buf]);
          end

        else

          error(['Bad element definition: ',Buf]);

        end
%
%  Read an element property.
%
      case 'property'

        if ( ~isempty(CurElement) & Count >= 3 )

          NumProperties = NumProperties + 1;
          eval(['tmp=isfield(Elements.',CurElement,',Token{Count});'],...
            'fclose(fid);error([''Error reading property: '',Buf])');

          if ( tmp )
            error(['Duplicate property name, ''',CurElement,'.',Token{2},'''.']);
          end
%
%  Add property subfield to Elements.
%
          eval(['Elements.',CurElement,'.',Token{Count},'=[];'], ...
            'fclose(fid);error([''Error reading property: '',Buf])');
%
%  Add property subfield to PropertyTypes and save type.
%
          eval(['PropertyTypes.',CurElement,'.',Token{Count},'={Token{2:Count-1}};'], ...
            'fclose(fid);error([''Error reading property: '',Buf])');
%
%  Record property name order.
%
          eval(['PropertyNames.',CurElement,'{NumProperties}=Token{Count};'], ...
            'fclose(fid);error([''Error reading property: '',Buf])');

         else

           fclose ( fid );

           if ( isempty(CurElement) )
             error(['Property definition without element definition: ',Buf]);
           else
             error(['Bad property definition: ',Buf]);
           end

         end
%
%  End of header.
%
        case 'end_header'
          break;

      end
    end
  end
%
%  Set reading for specified data format.
%
  if ( isempty ( Format ) )
    warning('Data format unspecified, assuming ASCII.');
    Format = 'ascii';
  end

  switch Format

    case 'ascii'
      Format = 0;
    case 'binary_little_endian'
      Format = 1;
    case 'binary_big_endian'
      Format = 2;
    otherwise
      fclose ( fid );
      error(['Data format ''',Format,''' not supported.']);

  end
%
%  Read the rest of the file as ASCII data...
%
  if ( ~Format )
    Buf = fscanf ( fid, '%f' );
    BufOff = 1;
  else
%
%  ...or, close the file, and reopen in "read binary" mode.
%
    fclose ( fid );
%
%  Reopen the binary file as LITTLE_ENDIAN or BIG_ENDIAN.
%
    if ( Format == 1 )
      fid = fopen ( Path, 'r', 'ieee-le.l64' );
    else
      fid = fopen ( Path, 'r', 'ieee-be.l64' );
    end
%
%  Find the end of the header again.
%  Using ftell on the old handle doesn't give the correct position.
%
    BufSize = 8192;
    Buf = [ blanks(10), char(fread(fid,BufSize,'uchar')') ];
    i = [];
    tmp = -11;

    while ( isempty(i) )
%
%  Look for end_header + CR/LF
%
      i = findstr(Buf,['end_header',13,10]);
%
%  Look for end_header + LF
%
      i = [i,findstr(Buf,['end_header',10])];

      if ( isempty(i) )
        tmp = tmp + BufSize;
        Buf = [Buf(BufSize+1:BufSize+10),char(fread(fid,BufSize,'uchar')')];
      end

    end
%
%  Seek to just after the line feed
%
    fseek ( fid, i + tmp + 11 + (Buf(i + 10) == 13), -1 );

  end
%
%  Read element data.
%
%  PLY and MATLAB data types (for fread)
%
  PlyTypeNames = {'char','uchar','short','ushort','int','uint','float','double', ...
    'char8','uchar8','short16','ushort16','int32','uint32','float32','double64'};

  MatlabTypeNames = {'schar','uchar','int16','uint16','int32','uint32','single','double'};

  SizeOf = [1,1,2,2,4,4,4,8];

  for i = 1 : NumElements
%
%  get current element property information
%
    eval(['CurPropertyNames=PropertyNames.',ElementNames{i},';']);
    eval(['CurPropertyTypes=PropertyTypes.',ElementNames{i},';']);
    NumProperties = size(CurPropertyNames,2);
%
%  Read ASCII data.
%
    if ( ~Format )

      for j = 1 : NumProperties

        Token = getfield(CurPropertyTypes,CurPropertyNames{j});

        if ( strcmpi(Token{1},'list') )
          Type(j) = 1;
        else
          Type(j) = 0;
        end
%
%  Glenn Ramsey 20120827
%  Initialise Type2{} to prevent uninitialised value error.
%
        Type2{j} = '';

      end
%
%  Parse the buffer.
%
      if ( ~any(Type) )
%
%  No list types
%
        Data = reshape ( ...
          Buf(BufOff:BufOff+ElementCount(i)*NumProperties-1), ...
          NumProperties, ElementCount(i) )';

        BufOff = BufOff + ElementCount(i) * NumProperties;

      else

        ListData = cell(NumProperties,1);

        for k = 1 : NumProperties
          ListData{k} = cell(ElementCount(i),1);
        end
%
%  list type
%
        for j = 1 : ElementCount(i)
          for k = 1 : NumProperties

            if ( ~Type(k) )
              Data(j,k) = Buf(BufOff);
              BufOff = BufOff + 1;
            else
              tmp = Buf(BufOff);
              ListData{k}{j} = Buf(BufOff+(1:tmp))';
              BufOff = BufOff + tmp + 1;
            end

          end
        end

      end

%
%  Read binary data.
%
    else

%
%  Translate PLY data type names to MATLAB data type names
%
      ListFlag = 0;
      SameFlag = 1;

      for j = 1 : NumProperties

        Token = getfield(CurPropertyTypes,CurPropertyNames{j});
%
%  Non-list type.
%
        if ( ~strcmp(Token{1},'list' ) )

          tmp = rem(strmatch(Token{1},PlyTypeNames,'exact')-1,8)+1;

          if ( ~isempty(tmp) )

            TypeSize(j) = SizeOf(tmp);
            Type{j} = MatlabTypeNames{tmp};
            TypeSize2(j) = 0;
            Type2{j} = '';

            SameFlag = SameFlag & strcmp(Type{1},Type{j});

          else

            fclose(fid);
            error(['Unknown property data type, ''',Token{1},''', in ', ...
              ElementNames{i},'.',CurPropertyNames{j},'.']);

          end
%
%  List type.
%
        else

          if ( length(Token) == 3 )

            ListFlag = 1;
            SameFlag = 0;
            tmp = rem(strmatch(Token{2},PlyTypeNames,'exact')-1,8)+1;
            tmp2 = rem(strmatch(Token{3},PlyTypeNames,'exact')-1,8)+1;
            disp(tmp, tmp2)

            if ( ~isempty(tmp) & ~isempty(tmp2) )
              TypeSize(j) = SizeOf(tmp);
              Type{j} = MatlabTypeNames{tmp};
              TypeSize2(j) = SizeOf(tmp2);
              Type2{j} = MatlabTypeNames{tmp2};
              disp('set Type2{j}')
            else
              fclose(fid);
              error(['Unknown property data type, ''list ',Token{2},' ',Token{3},''', in ', ...
                ElementNames{i},'.',CurPropertyNames{j},'.']);
            end

          else

            fclose(fid);
            error(['Invalid list syntax in ',ElementNames{i},'.',CurPropertyNames{j},'.']);

          end

        end

      end
%
%  read file
%
      if ( ~ListFlag )
%
%  No list types, all the same type (fast)
%
        if ( SameFlag )

          Data = fread(fid,[NumProperties,ElementCount(i)],Type{1})';
%
%  No list types, mixed type.
%
        else

          Data = zeros(ElementCount(i),NumProperties);

          for j = 1 : ElementCount(i)
            for k = 1 : NumProperties
              Data(j,k) = fread(fid,1,Type{k});
            end
          end

        end

      else

        ListData = cell(NumProperties,1);

        for k = 1 : NumProperties
          ListData{k} = cell(ElementCount(i),1);
        end

        if ( NumProperties == 1 )

          BufSize = 512;
          SkipNum = 4;
          j = 0;
%
%  List type, one property (fast if lists are usually the same length)
%
          while ( j < ElementCount(i) )

            BufSize = min(ElementCount(i)-j,BufSize);
            Position = ftell(fid);
%
%  Read in BufSize count values, assuming all counts = SkipNum
%
            [Buf,BufSize] = fread(fid,BufSize,Type{1},SkipNum*TypeSize2(1));
%
%  Find first count that is not SkipNum
%
            Miss = find(Buf ~= SkipNum);
%
%  Seek back to after first count
%
            fseek(fid,Position + TypeSize(1),-1);

            if ( isempty(Miss) )
%
%  All counts are SkipNum.
%
              Buf = fread(fid,[SkipNum,BufSize],[int2str(SkipNum),'*',Type2{1}],TypeSize(1))';
%
%  Undo the last skip.
%
              fseek(fid,-TypeSize(1),0);

              for k = 1:BufSize
                ListData{1}{j+k} = Buf(k,:);
              end

              j = j + BufSize;
              BufSize = floor ( 1.5 * BufSize );

            else
%
%  Some counts are SkipNum.
%
              if ( 1 < Miss(1) )

                Buf2 = fread ( fid, [SkipNum,Miss(1)-1],[int2str(SkipNum),'*',Type2{1}],TypeSize(1))';

                for k = 1:Miss(1)-1
                  ListData{1}{j+k} = Buf2(k,:);
                end

                j = j + k;

              end
%
%  Read in the list with the missed count.
%
              SkipNum = Buf(Miss(1));
              j = j + 1;
              ListData{1}{j} = fread ( fid, [1,SkipNum],Type2{1} );
              BufSize = ceil ( 0.6 * BufSize );

            end
          end

        else
%
%  List type(s), multiple properties (slow)
%
          Data = zeros(ElementCount(i),NumProperties);

          for j = 1 : ElementCount(i)
            for k = 1 : NumProperties

              if ( isempty(Type2{k}) )
                Data(j,k) = fread(fid,1,Type{k});
              else
                tmp = fread(fid,1,Type{k});
                ListData{k}{j} = fread(fid,[1,tmp],Type2{k});
              end

            end
          end
        end
      end
    end
%
%  Put data into Elements structure
%
    for k = 1 : NumProperties

      if ( ( ~Format & ~Type(k) ) || (Format & isempty(Type2{k})) )
        eval(['Elements.',ElementNames{i},'.',CurPropertyNames{k},'=Data(:,k);']);
      else
        eval(['Elements.',ElementNames{i},'.',CurPropertyNames{k},'=ListData{k};']);
      end

    end

  end

  clear Data
  clear ListData;

  fclose ( fid );
%
%  Output the data as a triangular mesh pair.
%
%  Find vertex element field.
%
  Name = {'vertex','Vertex','point','Point','pts','Pts'};
  Names = [];

  for i = 1 : length(Name)

    if ( any ( strcmp ( ElementNames, Name{i} ) ) )
      Names = getfield(PropertyNames,Name{i});
      Name = Name{i};
      break;
    end

  end

  if ( any(strcmp(Names,'x')) & any(strcmp(Names,'y')) & any(strcmp(Names,'z')) )
    eval(['node_xyz=[Elements.',Name,'.x,Elements.',Name,'.y,Elements.',Name,'.z];']);
  else
    node_xyz = zeros(1,3);
  end
  node_xyz = node_xyz';

  triangle_node = [];

% Find face element field

  Name = {'face','Face','poly','Poly','tri','Tri'};
  Names = [];

  for i = 1 : length(Name)
    if ( any(strcmp(ElementNames,Name{i})) )
      Names = getfield(PropertyNames,Name{i});
      Name = Name{i};
      break;
    end
  end

  if ( ~isempty(Names) )
      % find vertex indices property subfield
    PropertyName = {'vertex_indices','vertex_indexes','vertex_index','indices','indexes'};

    for i = 1 : length(PropertyName)
      if ( any(strcmp(Names,PropertyName{i})) )
        PropertyName = PropertyName{i};
        break;
      end
    end
%
%  Convert face index list to triangular connectivity.
%
    if ( ~iscell(PropertyName) )

      eval(['FaceIndices=Elements.',Name,'.',PropertyName,';']);
      N = length(FaceIndices);
      triangle_node = zeros(3,N*2);
      Extra = 0;

      for k = 1 : N

        triangle_node(1:3,k) = FaceIndices{k}(1:3)';

        for j = 4 : length(FaceIndices{k})
          Extra = Extra + 1;
          triangle_node(1,N + Extra) = FaceIndices{k}(1);
          triangle_node(2,N + Extra) = FaceIndices{k}(j-1);
          triangle_node(3,N + Extra) = FaceIndices{k}(j);
        end

      end
%
%  Add 1 to each vertex value; PLY vertices are zero based.
%
      triangle_node = triangle_node(:,1:N+Extra) + 1;

    end
  end

  return
end


% This file is part of TREEQSM.
%
% TREEQSM 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.
%
% TREEQSM 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 TREEQSM.  If not, see <http://www.gnu.org/licenses/>.

function QSM = simplify_qsm(QSM,MaxOrder,SmallRadii,ReplaceIterations,Plot,Disp)

% ---------------------------------------------------------------------
% SIMPLIFY_QSM.M   Simplifies cylinder QSMs by restricting the maximum
%                       branching order, by removing thin branches, and by
%                       replacing two concecutive cylinders with a longer cylinder
%
% Version 2.0.0
% Latest update     4 May 2022
%
% Copyright (C) 2015-2022 Pasi Raumonen
% ---------------------------------------------------------------------
%
% Inputs:
% QSM           QSM-structure, output of treeqsm.m, must contain only one model
% MaxOrder      Maximum branching order, higher order branches removed
% SmallRadii    Minimum acceptable radius for a branch at its base
% ReplaceIterations Number of iterations for replacing two concecutive
%                     cylinders inside one branch with one longer cylinder
% Plot          If true/1, then plots the cylinder models before and
%                 after the simplification
% Disp          If Disp == 1, then display the simplication results
%                 (the number of cylinders after each step). If
%                 Disp == 2, then display also the treedata results for
%                 the original and simplified QSMs. If Disp == 0, then
%                 nothing is displayed.
%
% Output:
% Modified QSM      NOTICE: cylinder, branch and treedata are modified.

% Changes from version 1.1.0 to 2.0.0, 4 May 2022:
% 1) Added modification of branch and treedata structures based on the
%     modified cylinders
% 2) Added input for plotting and displaying the results
% 3) Corrected some bugs that could cause errors in some special cases

if nargin <= 4
  Plot = 0;
  Disp = 1;
elseif nargin <= 5
  Disp = 1;
end

if Disp == 2
  inputs = QSM.rundata.inputs;
  display_treedata(QSM.treedata,inputs)
end
% Plot the cylinder model before the simplification
if Plot
  plot_cylinder_model(QSM.cylinder,'branch',1,20,1)
end

%% Maximum branching order
c = QSM.cylinder;
nc = size(c.radius,1);
if Disp >= 1
  disp([' ',num2str(nc),' cylinders originally'])
end

% Cylinders with branching order up to MaxBranchOrder
SmallOrder = c.BranchOrder <= MaxOrder;
N = fieldnames(c);
n = max(size(N));
for i = 1:n
  c.(N{i}) = c.(N{i})(SmallOrder,:);
end

% Modify topology information
Ind = (1:1:nc)';
m = nnz(SmallOrder);
Ind(SmallOrder) = (1:1:m)';
I = c.parent > 0;
c.parent(I) = Ind(c.parent(I));
I = c.extension > 0;
c.extension(I) = Ind(c.extension(I));

if Disp == 1
  nc = nnz(SmallOrder);
  disp([' ',num2str(nc),' cylinders after branching order simplification'])
end

%% Small branches
if nargin >= 3 && SmallRadii > 0

  nc = size(c.radius,1);
  % Determine child branches
  BPar = QSM.branch.parent;
  nb = size(BPar,1);
  BChi = cell(nb,1);
  for i = 1:nb
    P = BPar(i);
    if P > 0
      BChi{P} = [BChi{P}; i];
    end
  end

  % Remove branches whose radii is too small compared to its parent
  Large = true(nc,1);
  Pass = true(nb,1);
  for i = 1:nb
    if Pass(i)
      if QSM.branch.diameter(i) < SmallRadii
        B = i;
        BC = BChi{B};
        while ~isempty(BC)
          B = [B; BC];
          BC = vertcat(BChi{BC});
        end
        Pass(B) = false;
        m = length(B);
        for k = 1:m
          Large(c.branch == B(k)) = false;
        end
      end
    end
  end

  % Modify topology information
  Ind = (1:1:nc)';
  m = nnz(Large);
  Ind(Large) = (1:1:m)';
  I = c.parent > 0;
  c.parent(I) = Ind(c.parent(I));
  I = c.extension > 0;
  c.extension(I) = Ind(c.extension(I));

  % Update/reduce cylinders
  for i = 1:n
    c.(N{i}) = c.(N{i})(Large,:);
  end

  if Disp >= 1
    nc = nnz(Large);
    disp([' ',num2str(nc),' cylinders after small branch simplification'])
  end
end

%% Cylinder replacing
if nargin >= 4 && ReplaceIterations > 0

  % Determine child cylinders
  nc = size(c.radius,1);
  CChi = cell(nc,1);
  for i = 1:nc
    P = c.parent(i);
    if P > 0
      PE = c.extension(P);
      if PE ~= i
        CChi{P} = [CChi{P}; i];
      end
    end
  end

  % Replace cylinders
  for j = 1:ReplaceIterations

    nc = size(c.radius,1);
    Ind = (1:1:nc)';
    Keep = false(nc,1);
    i = 1;
    while i <= nc
      t = 1;
      while i+t <= nc && c.branch(i+t) == c.branch(i)
        t = t+1;
      end
      Cyls = (i:1:i+t-1)';
      S = c.start(Cyls,:);
      A = c.axis(Cyls,:);
      L = c.length(Cyls);
      if t == 1 % one cylinder in the branch
        Keep(i) = true;
      elseif ceil(t/2) == floor(t/2) % even number of cylinders in the branch
        I = (1:2:t)'; % select 1., 3., 5., ...
        % Correct radii, axes and lengths
        E = S(end,:)+L(end)*A(end,:);
        S = S(I,:);
        m = length(I);
        if m > 1
          A = [S(2:end,:); E]-S(1:end,:);
        else
          A = E-S(1,:);
        end
        L = sqrt(sum(A.*A,2));
        A = [A(:,1)./L A(:,2)./L A(:,3)./L];
        cyls = Cyls(I);
        Keep(cyls) = true;
        V = pi*c.radius(Cyls).^2.*c.length(Cyls);
        J = (2:2:t)';
        V = V(I)+V(J);
        R = sqrt(V./L/pi);
        c.radius(cyls) = R;

      else % odd number of cylinders
        I = [1 2:2:t]'; % select 1., 2., 4., 6., ...
        % Correct radii, axes and lengths
        E = S(end,:)+L(end)*A(end,:);
        S = S(I,:);
        l = L(1);
        a = A(I,:);
        m = length(I);
        if m > 2
          a(2:end,:) = [S(3:end,:); E]-S(2:end,:);
        else
          a(2,:) = E-S(2,:);
        end
        A = a;
        L = sqrt(sum(A.*A,2));
        L(1) = l;
        A(2:end,:) = [A(2:end,1)./L(2:end) A(2:end,2)./L(2:end) A(2:end,3)./L(2:end)];
        cyls = Cyls(I);
        Keep(cyls) = true;
        V = pi*c.radius(Cyls).^2.*c.length(Cyls);
        J = (3:2:t)';
        V = V(I(2:end))+V(J);
        R = sqrt(V./L(2:end)/pi);
        c.radius(cyls(2:end)) = R;
      end

      if t > 1
        % Modify cylinders
        c.length(cyls) = L;
        c.axis(cyls,:) = A;
        % Correct branching/topology information
        c.PositionInBranch(cyls) = (1:1:m)';
        c.extension(cyls) = [cyls(2:end); 0];
        c.parent(cyls(2:end)) = cyls(1:end-1);
        par = c.parent(cyls(1));
        if par > 0 && ~Keep(par)
          par0 = c.parent(par);
          if Keep(par0) && c.extension(par0) == par
            c.parent(cyls(1)) = par0;
          end
        end

        % Correct child branches
        chi = vertcat(CChi{Cyls});
        if ~isempty(chi)
          par = c.parent(chi);
          J = Keep(par);
          par = par(~J)-1;
          c.parent(chi(~J)) = par;

          par = c.parent(chi);
          rp = c.radius(par);
          sp = c.start(par,:);
          ap = c.axis(par,:);
          lc = c.length(chi);
          sc = c.start(chi,:);
          ac = c.axis(chi,:);
          ec = sc+[lc.*ac(:,1) lc.*ac(:,2) lc.*ac(:,3)];
          m = length(chi);
          for k = 1:m
            [d,V,h,B] = distances_to_line(sc(k,:),ap(k,:),sp(k,:));
            V = V/d;
            sc(k,:) = sp(k,:)+rp(k)*V+B;
          end
          ac = ec-sc;
          [ac,lc] = normalize(ac);
          c.length(chi) = lc;
          c.start(chi,:) = sc;
          c.axis(chi,:) = ac;
        end
      end

      i = i+t;
    end
    % Change topology (parent, extension) indexes
    m = nnz(Keep);
    Ind(Keep) = (1:1:m)';
    I = c.parent > 0;
    c.parent(I) = Ind(c.parent(I));
    I = c.extension > 0;
    c.extension(I) = Ind(c.extension(I));

    % Update/reduce cylinders
    for i = 1:n
      c.(N{i}) = c.(N{i})(Keep,:);
    end

    if j < ReplaceIterations
      % Determine child cylinders
      nc = size(c.radius,1);
      CChi = cell(nc,1);
      for i = 1:nc
        P = c.parent(i);
        if P > 0
          PE = c.extension(P);
          if PE ~= i
            CChi{P} = [CChi{P}; i];
          end
        end
      end
    end
  end

  if Disp >= 1
    nc = size(c.radius,1);
    disp([' ',num2str(nc),' cylinders after cylinder replacements'])
  end
end
if Disp >= 1
  nc = size(c.radius,1);
  disp([' ',num2str(nc),' cylinders after all simplifications'])
end

%% Updata the QSM
% Update the branch
branch = branches(c);

% Update the treedata
inputs = QSM.rundata.inputs;
inputs.plot = 0;
% Display
if Disp == 2
  inputs.disp = 2;
else
  inputs.disp = 0;
end
treedata = update_tree_data(QSM,c,branch,inputs);

% Update the cylinder, branch, and treedata of the QSM
QSM.cylinder = c;
QSM.branch = branch;
QSM.treedata = treedata;

% Plot the cylinder model after the simplification
if Plot
  plot_cylinder_model(QSM.cylinder,'branch',2,20,1)
end

end % End of main function

function display_treedata(treedata,inputs)
%% Generate units for displaying the treedata
Names = fieldnames(treedata);
n = size(Names,1);
Units = zeros(n,3);
m = 23;
for i = 1:n
  if ~inputs.Tria && strcmp(Names{i},'CrownVolumeAlpha')
    m = i;
  elseif inputs.Tria && strcmp(Names{i},'TriaTrunkLength')
    m = i;
  end
  if strcmp(Names{i}(1:3),'DBH')
    Units(i,:) = 'm  ';
  elseif strcmp(Names{i}(end-2:end),'ume')
    Units(i,:) = 'L  ';
  elseif strcmp(Names{i}(end-2:end),'ght')
    Units(i,:) = 'm  ';
  elseif strcmp(Names{i}(end-2:end),'gth')
    Units(i,:) = 'm  ';
  elseif strcmp(Names{i}(1:3),'vol')
    Units(i,:) = 'L  ';
  elseif strcmp(Names{i}(1:3),'len')
    Units(i,:) = 'm  ';
  elseif strcmp(Names{i}(end-2:end),'rea')
    Units(i,:) = 'm^2';
  elseif strcmp(Names{i}(1:3),'loc')
    Units(i,:) = 'm  ';
  elseif strcmp(Names{i}(end-4:end),'aConv')
    Units(i,:) = 'm^2';
  elseif strcmp(Names{i}(end-5:end),'aAlpha')
    Units(i,:) = 'm^2';
  elseif strcmp(Names{i}(end-4:end),'eConv')
    Units(i,:) = 'm^3';
  elseif strcmp(Names{i}(end-5:end),'eAlpha')
    Units(i,:) = 'm^3';
  elseif strcmp(Names{i}(end-2:end),'Ave')
    Units(i,:) = 'm  ';
  elseif strcmp(Names{i}(end-2:end),'Max')
    Units(i,:) = 'm  ';
  end
end
%% Display treedata
disp('------------')
disp('  Tree attributes before simplification:')
for i = 1:m
  v = change_precision(treedata.(Names{i}));
  if strcmp(Names{i},'DBHtri')
    disp('  -----')
    disp('  Tree attributes from triangulation:')
  end
  disp(['  ',Names{i},' = ',num2str(v),' ',Units(i,:)])
end
disp('  -----')
end


% readfid allows one to read in the Varian 4T FID data acquired with:
%
% [K, HDR, BLOCK_HDR] = READFID(FPATH, PAR, DC_CORRECT) reads k-space data
% from fid file if the FID directory FPATH. DC correction based on level
% and tilt can be can be suppressed by setting DC_CORRECT to false. If DC
% correction is applied, the level and tilt are set to zero. PAR is the
% parameters as read by getparam. The main header is returned in HDR and
% the block headers are returned in BLOCK_HDR
%
% see also WRITEFID, READPROCPAR, WRITEPROCPAR

function [k, hdr, block_hdr, par] = readfid(fpath, par, dc_correct, tfisp, ext)

if nargin < 1
   error('cfmm:fid:name','READFID requires fid file name');
end

if nargin < 5
   ext = [];
end

% Read procpar if not already provided
if nargin < 2 || isempty(par)
   par = readprocpar(fpath, ext);
end

% Turn on DC correction by default
if nargin < 3 || isempty(dc_correct)
   dc_correct = true;
end
% Turn off tfisp by default
if nargin < 4 || isempty(tfisp)
   tfisp = false;
end

% Check for validity of fid file
if exist([fpath '/fid' ext], 'file') ~= 2
   if exist([fpath '.fid/fid'], 'file') ~= 2
      error('cfmm:fid:name','Unable to open FID file: %s', fpath);
   else
      fpath = [fpath '.fid'];
   end
end

infid = fopen([fpath '/fid' ext],'r', 'ieee-be');
if infid == -1
   error('cfmm:fid:name','Unable to open FID file: %s', fpath);
end

% Read in all the information from the FID file
try
   hdr.nblocks    = fread(infid, 1, 'int32');
   hdr.ntraces    = fread(infid, 1, 'int32');
   hdr.np         = fread(infid, 1, 'int32');
   hdr.ebytes     = fread(infid, 1, 'int32');
   hdr.tbytes     = fread(infid, 1, 'int32');
   hdr.bbytes     = fread(infid, 1, 'int32');
   hdr.vers_id    = fread(infid, 1, 'int16');
   hdr.status     = fread(infid, 1, 'int16');
   hdr.nbheaders  = fread(infid, 1, 'int32');

   m = hdr.bbytes/hdr.ebytes;
   n = hdr.nblocks;

   % Read in the block headers
   % Imaging data all has only one block header and VERY BAD people use
   % nbheaders as their own spare field. Therefore we ignore the nbheaders
   % and treat everything as having one block header.
   if nargout > 2
      % Create the block_hdr structure
      block_hdr.scale   = 0;
      block_hdr.status  = 0;
      block_hdr.index   = 0;
      block_hdr.mode    = 0;
      block_hdr.ctcount = 0;
      block_hdr.lpval   = 0;
      block_hdr.rpval   = 0;
      block_hdr.lvl     = 0;
      block_hdr.tlt     = 0;
      block_hdr = block_hdr(ones(n, 1));

      % Read in the block header values
      for i = 1:n
         block_hdr(i).scale   = fread(infid, 1, 'short');
         block_hdr(i).status  = fread(infid, 1, 'short');
         block_hdr(i).index   = fread(infid, 1, 'short');
         block_hdr(i).mode    = fread(infid, 1, 'short');
         block_hdr(i).ctcount = fread(infid, 1, 'long');
         block_hdr(i).lpval   = fread(infid, 1, 'float');
         block_hdr(i).rpval   = fread(infid, 1, 'float');
         block_hdr(i).lvl     = fread(infid, 1, 'float');
         block_hdr(i).tlt     = fread(infid, 1, 'float');
         fseek(infid, hdr.bbytes-28*hdr.nbheaders, 'cof');
      end
   end

   % Get the data format from the header
   if bitand(hdr.status, 8)
      % Floating
      format = '*float32';
      if hdr.ebytes ~= 4
         error('cfmm:fid:readError','Inconsistent Format: %d-bytes and %s.', hdr.ebytes, format);
      end
   else
      % Integer
      if bitand(hdr.status, 4)
         % 32-bit integer
         format = '*int32';
         if hdr.ebytes ~= 4
            error('cfmm:fid:readError','Inconsistent Format: %d-bytes and %s.', hdr.ebytes, format);
         end
      else
         % 16-bit integer
         format = '*int16';
         if hdr.ebytes ~= 2
            error('cfmm:fid:readError','Inconsistent Format: %d-bytes and %s.', hdr.ebytes, format);
         end
      end
   end

   % Read in all the data
   fseek(infid, 32, 'bof');
   k = fread(infid, [m,n], format);

   % Strip block header and reorganize into complex values
   k = complex(k(28/hdr.ebytes+1:2:m,:), k(28/hdr.ebytes+2:2:m,:));

   % Apply the lvl and tlt correction
   if dc_correct && rem(par.nt(1),2)
      % Read in the lvl and tlt correction
      fseek(infid, 52, 'bof');
      cor = fread(infid, [2, n], '2*float32', (hdr.bbytes) - 8);

      % Set the block header values to zero after correcting
      if (nargout > 1)
         for i = 1:n
            block_hdr(i).lvl     = 0;
            block_hdr(i).tlt     = 0;
         end
      end

      % nt = 3,7,11,... have DC rotated by 90 degrees
      if rem(par.nt,4) == 3
         cor = complex(-cor(2,:), cor(1,:));
      else
         cor = complex(cor(1,:), cor(2,:));
      end
      if tfisp
         % tfisp switches detection direction on each phase encode
         % acquisition is always compressed phase encodes
         [m, n] = size(k);
         nro = par.np / 2;
         if par.nv == 0
            nv = 1;
         else
            nv = par.nv;
         end
         r = m / (nro * nv);
         k = reshape(k, [nro nv r n]);
         if any(cor ~= cor(1))
            cor = reshape(cor, [1 1 1 n]);
            nv_1 = ceil(nv / 2);
            nv_2 = floor(nv / 2);
            if rem(par.ssc, 2) == 0
               k(:,1:2:end,:,:) = k(:,1:2:end,:,:) + cor(ones(1,nro), ones(1,nv_1), ones(1,r), :);
               k(:,2:2:end,:,:) = k(:,2:2:end,:,:) - cor(ones(1,nro), ones(1,nv_2), ones(1,r), :);
            else
               k(:,2:2:end,:,:) = k(:,2:2:end,:,:) + cor(ones(1,nro), ones(1,nv_2), ones(1,r), :);
               k(:,1:2:end,:,:) = k(:,1:2:end,:,:) - cor(ones(1,nro), ones(1,nv_1), ones(1,r), :);
            end
            % cor = reshape(cor, [1 n]);
         else
            if rem(par.ssc, 2) == 0
               k(:,1:2:end,:) = k(:,1:2:end,:) + cor(1);
               k(:,2:2:end,:) = k(:,2:2:end,:) - cor(1);
            else
               k(:,2:2:end,:) = k(:,2:2:end,:) + cor(1);
               k(:,1:2:end,:) = k(:,1:2:end,:) - cor(1);
            end
         end
         k = reshape(k, [m n]);
      else
         if any(cor ~= cor(1))
            k = k - cor(ones(1,size(k,1)),:);
         else
            k = k - cor(1);
         end
      end
   end

catch err
    fclose(infid);
    rethrow(err);
end
fclose(infid);


%
%   [NSx, NEV] = combineNSxNEV(filename1, filename2)
%
%   This function loads two NSx and NEV files and it will combine them
%   together into a sinlge NSx and NEV structure in MATLAB. To merge two
%   NSx and NEV files into individual files see mergeNSxNEV. The time
%   difference between the two sets of recordings is removed. To determine
%   the time differnce between the two data files, use
%   NSx.MetaTags.DateTimeRaw or NEV.MetaTags.DateTimeRaw variables.
%
%
%   filename1:  The name of the first NSx file. This input is optional. In
%               its absense, a dialog will open and will prompt the user to
%               select an NSx file.
%               (OPTIONAL)
%
%   filename2:  The name of the second NSx file. This input is also
%               optional. In its absense, a dialog will open and will
%               prompt the user to select an NSx file.
%               (OPTIONAL)
%
%
%   Example:
%
%   [NSx, NEV] = combineNSxNEV('c:\data\saveddata1.ns5', 'c:\data\saveddata2.ns5');
%
%   The above example reads the two files (full path needed)
%   c:\data\saveddata1.ns5 and c:\data\saveddata2.ns5 and their corresponding
%   NEV files (saveddata1.nev and saveddata2.nev) in the same folder and
%   combines them into single variables NSx and NEV into MATLAB workspace.
%
%   Kian Torab
%   ktorab@blackrockmicro.com
%   Blackrock Microsystems
%
%   Version 1.1.2.0

function [NSx1, NEV1] = combineNSxNEV(filename1, filename2)

% Openning NSx files
if exist('filename1', 'var') && exist('filename2', 'var')
    disp('Load the first NSx file.');
    NSx1 = openNSx('read', filename1);
    disp('Load the second NSx file.');
    NSx2 = openNSx('read', filename2);
    if NSx1.MetaTags.SamplingFreq ~= NSx2.MetaTags.SamplingFreq;
        disp('The sampling frequencies are not the same.');
        return;
    end
else
    NSx1 = openNSx('read');
    NSx2 = openNSx('read');
end

% Determining length of the first NSx file
conversionFactor = 30000/NSx1.MetaTags.SamplingFreq;
NSx1DataLength = NSx1.MetaTags.DataPoints * conversionFactor;

% Combining NSx files
NSx1.Data = [NSx1.Data, NSx2.Data];

% Opening NEV files
fileNameNEV1 = [NSx1.MetaTags.FilePath '/' NSx1.MetaTags.Filename(1:end-3), 'nev'];
fileNameNEV2 = [NSx1.MetaTags.FilePath '/' NSx2.MetaTags.Filename(1:end-3), 'nev'];
clear NSx2;

if (exist(fileNameNEV1, 'file') == 2) && (exist(fileNameNEV2, 'file') ==2)
    disp('Openning corresponding NEV files...');
    NEV1 = openNEV('read', fileNameNEV1);
    NEV2 = openNEV('read', fileNameNEV2);
else
    disp('Load the first NEV file.');
    NEV1 = openNEV('read');
    disp('Load the second NEV file.');
    NEV2 = openNEV('read');
end

% Adjusting the timestamp on the second NEV file
NEV2.Data.Comments.TimeStamp = NEV2.Data.Comments.TimeStamp + NSx1DataLength;
NEV2.Data.Comments.TimeStampSec = NEV2.Data.Comments.TimeStampSec + double(NSx1DataLength)/30;
NEV2.Data.SerialDigitalIO.TimeStamp = NEV2.Data.SerialDigitalIO.TimeStamp + NSx1DataLength;
NEV2.Data.SerialDigitalIO.TimeStampSec = NEV2.Data.SerialDigitalIO.TimeStampSec + double(NSx1DataLength)/30;
NEV2.Data.Spikes.TimeStamp = NEV2.Data.Spikes.TimeStamp + NSx1DataLength;
NEV2.Data.VideoSync.TimeStamp = NEV2.Data.VideoSync.TimeStamp + NSx1DataLength;
if ~isempty(NEV2.Data.Tracking)
    trackingFieldNames = fieldnames(NEV2.Data.Tracking);
    for idx = 1:size(trackingFieldNames, 1)
        NEV2.Data.Tracking.(trackingFieldNames{idx}).TimeStamp = NEV2.Data.Tracking.(trackingFieldNames{idx}).TimeStamp + NSx1DataLength;
    end
end
NEV2.Data.PatientTrigger.TimeStamp = NEV2.Data.PatientTrigger.TimeStamp + NSx1DataLength;
NEV2.Data.Reconfig.TimeStamp = NEV2.Data.Reconfig.TimeStamp + NSx1DataLength;

% Combining the two NEV files
NEV1.Data.Spikes.Electrode      = [NEV1.Data.Spikes.Electrode, NEV2.Data.Spikes.Electrode];
NEV1.Data.Spikes.TimeStamp      = [NEV1.Data.Spikes.TimeStamp, NEV2.Data.Spikes.TimeStamp];
NEV1.Data.Spikes.Unit           = [NEV1.Data.Spikes.Unit, NEV2.Data.Spikes.Unit];
NEV1.Data.Spikes.Waveform       = [NEV1.Data.Spikes.Waveform, NEV2.Data.Spikes.Waveform];
NEV1.Data.Comments.TimeStamp    = [NEV1.Data.Comments.TimeStamp, NEV2.Data.Comments.TimeStamp];
NEV1.Data.Comments.TimeStampSec = [NEV1.Data.Comments.TimeStampSec, NEV2.Data.Comments.TimeStampSec];
NEV1.Data.Comments.CharSet      = [NEV1.Data.Comments.CharSet, NEV2.Data.Comments.CharSet];
NEV1.Data.Comments.Color        = [NEV1.Data.Comments.Color, NEV2.Data.Comments.Color];
NEV1.Data.Comments.Text         = [NEV1.Data.Comments.Text; NEV2.Data.Comments.Text];
if ~isempty(NEV2.Data.Tracking)
    for idx = 1:size(trackingFieldNames, 1)
        NEV1.Data.Tracking.(trackingFieldNames{idx}).TimeStamp = [NEV1.Data.Tracking.(trackingFieldNames{idx}).TimeStamp, NEV2.Data.Tracking.(trackingFieldNames{idx}).TimeStamp];
        NEV1.Data.Tracking.(trackingFieldNames{idx}).TimeStampSec = [NEV1.Data.Tracking.(trackingFieldNames{idx}).TimeStampSec, NEV2.Data.Tracking.(trackingFieldNames{idx}).TimeStampSec];
        NEV1.Data.Tracking.(trackingFieldNames{idx}).ParentID = [NEV1.Data.Tracking.(trackingFieldNames{idx}).ParentID, NEV2.Data.Tracking.(trackingFieldNames{idx}).ParentID];
        NEV1.Data.Tracking.(trackingFieldNames{idx}).NodeCount = [NEV1.Data.Tracking.(trackingFieldNames{idx}).NodeCount, NEV2.Data.Tracking.(trackingFieldNames{idx}).NodeCount];
        NEV1.Data.Tracking.(trackingFieldNames{idx}).MarkerCount = [NEV1.Data.Tracking.(trackingFieldNames{idx}).MarkerCount, NEV2.Data.Tracking.(trackingFieldNames{idx}).MarkerCount];
        NEV1.Data.Tracking.(trackingFieldNames{idx}).MarkerCoordinates = [NEV1.Data.Tracking.(trackingFieldNames{idx}).MarkerCoordinates NEV2.Data.Tracking.(trackingFieldNames{idx}).MarkerCoordinates];
    end
end


function play_movie(movs,labels,min_mov,max_mov)
    % play list (cell) of movies with labels, press key to stop playing
    % inputs:
    % movs: cell of movies
    % labels: cell of titles for movies
    % min_mov,max_mov: min and max of movie for setting clims on movies

    if ~iscell(movs)
        movs = {movs};
    end
    if ~exist('labels','var')
        labels = cell(size(movs));
    end
    if ~exist('min_mov','var') || ~exist('max_mov','var')
        nml = min(1e7,numel(movs{1}));
        min_mov = quantile(movs{1}(1:nml),0.001);
        max_mov = quantile(movs{1}(1:nml),1-0.001);
    end

    dialogBox = uicontrol('Style', 'PushButton', 'String', 'stop','Callback', 'delete(gcbf)');

    num_movs = numel(movs);
    len_movs = size(movs{1},3);

    t = 0;
    while (ishandle(dialogBox)) && t<len_movs
        t = t+1;
        for idx_mov = 1:num_movs
            subplot(1,num_movs,idx_mov); imagesc(movs{idx_mov}(:,:,t),[min_mov,max_mov]);
            axis square;
            axis off;
            colormap('gray');
            title([labels{idx_mov}, ', frame ',num2str(t)]);
        end
        if t == 1
            set(gcf,'Position',[100,100,1.2*length(movs)*size(movs{1},2),1.2*size(movs{1},1)]);
        end
        pause(0.001);
    end
    close()


function outList = transform2switches(stimList)
% assumes 2 stimtypes, 1 and 2
% classifies them into no switch (1 and 2) or switch (3 and 4)
outList(1,1) = stimList(1,1);
for i = 2:size(stimList,1)
   switch stimList(i-1,1)		% based on previous trial
   case 1
      if stimList(i,1) == 2, outList(i,1) = 4;,else outList(i,1) = stimList(i,1);,end
   case 2
      if stimList(i,1) == 1, outList(i,1) = 3;,else outList(i,1) = stimList(i,1);,end
   case 5
      if i > 2 & stimList(i,1) == 1 & stimList(i-2,1) == 2, outList(i,1) = 3;
      elseif i > 2 & stimList(i,1) == 2 & stimList(i-2,1) == 1, outList(i,1) = 4;
      else outList(i,1) = stimList(i,1);
      end
   otherwise outList(i,1) = stimList(i,1);
   end
end
return


function T = convert_to_table(CPD, domain, evidence)
% CONVERT_TO_TABLE Convert a discrete CPD to a table
% T = convert_to_table(CPD, domain, evidence)
%
% We convert the CPD to a CPT, and then lookup the evidence on the discrete parents.
% The resulting table can easily be converted to a potential.

CPT = CPD.table;
odom = domain(~isemptycell(evidence(domain)));
vals = cat(1, evidence{odom});
map = find_equiv_posns(odom, domain);
index = mk_multi_index(length(domain), map, vals);
T = CPT(index{:});


function y = end( x, k, n )

%Disciplined convex/geometric programming information for END:
%   The use of END as an array subscript (e.g., X(:,end)) is identical
%   with CVX variables as it is for numeric vectors and arrays.

sz = size( x );
nz = length( sz );
if k > nz,
    y = 1;
elseif k < n || nz <= n,
    y = sz( k );
else
    y = prod( sz( k : end ) );
end

% Copyright 2005-2014 CVX Research, Inc.
% See the file LICENSE.txt for full copyright information.
% The command 'cvx_where' will show where this file is located.


function f = fliplr( f )
%FLIPLR   Flip/reverse a SEPARABLEAPPROX in the x-direction.
%   G = FLIPLR( F ) returns a SEPARABLEAPPROX G with the same domain as F but reversed;
%   that is, G(x,y) = F(a+b-x,y), where the domain is [a, b, c, d].
%
% See also FLIPUD.

% Copyright 2017 by The University of Oxford and The Chebfun Developers.
% See http://www.chebfun.org/ for Chebfun information.

% Empty check:
if ( isempty( f ) )
    return
end

% Flip the row slices:
f.rows = flipud( f.rows );

end


function p36_p_set ( b, m1, m2, r11, r12, r21, r22, seed )

%*****************************************************************************80
%
%% P36_P_SET sets parameters for problem 36.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    16 October 2004
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, real B, M1, M2, R11, R12, R21, R22, problem parameters.
%
%    Input, integer SEED, a seed for the random number generator.
%
  p36_p_val ( 'SET', b, m1, m2, r11, r12, r21, r22, seed );

  return
end


%% Copyright (C) 2014, 2016 Colin B. Macdonald
%%
%% This file is part of OctSymPy.
%%
%% OctSymPy 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 software 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 software; see the file COPYING.
%% If not, see <http://www.gnu.org/licenses/>.

%% -*- texinfo -*-
%% @defmethod  @@sym private_disp_name (@var{x}, @var{name})
%% A string appropriate for representing the name of this sym.
%%
%% Private method: this is not the method you are looking for.
%%
%% @end defmethod

function s = private_disp_name(x, input_name)

  s = input_name;
  % subclasses might do something more interesting, but they should
  % be careful to ensure empty input_name gives empty s.

end

%!test
%! syms x
%! s = private_disp_name(x, 'x');
%! assert (strcmp (s, 'x'))


function binsize_str = binsizelabel(binsize)
    binsize_str = '';
    if binsize == 1.0/1440
        binsize_str = 'per minute';
    elseif binsize == 1.0/24
        binsize_str = 'per hour';
    elseif binsize == 1.0
        binsize_str = 'per day';
    elseif binsize == 7.0
        binsize_str = 'per week';
    elseif binsize >= 28 & binsize <=31
        binsize_str = 'per month';
    elseif binsize >= 365 & binsize <= 366
        binsize_str = 'per year';
    end

    %disp(sprintf('binsize label',binsize_str));
end


function varargout = process_cohere1n( varargin )
% PROCESS_COHERE1N: Compute the coherence between all the pairs of signals, in one file.
%
% USAGE:   OutputFiles = process_cohere1n('Run', sProcess, sInputA)
%                        process_cohere1n('Test')

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2012-2020; Hossein Shahabi, 2019-2020

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
    % Description the process
    sProcess.Comment     = 'Coherence NxN [Deprecated]';
    sProcess.Category    = 'Custom';
    sProcess.SubGroup    = 'Connectivity';
    sProcess.Index       = 656;
    sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/Connectivity';
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'data',     'results',  'matrix'};
    sProcess.OutputTypes = {'timefreq', 'timefreq', 'timefreq'};
    sProcess.nInputs     = 1;
    sProcess.nMinFiles   = 1;

    % === CONNECT INPUT
    sProcess = process_corr1n('DefineConnectOptions', sProcess, 1);
    % === REMOVE EVOKED REPONSE
    sProcess.options.removeevoked.Comment = 'Remove evoked response from each trial';
    sProcess.options.removeevoked.Type    = 'checkbox';
    sProcess.options.removeevoked.Value   = 0;
    sProcess.options.removeevoked.Group   = 'input';
    % === COHERENCE METHOD
    sProcess.options.cohmeasure.Comment = {...
        ['Magnitude-squared Coherence
' ...
        '|C|^2 = |Gxy|^2/(Gxx*Gyy)'], ...
        ['Imaginary Coherence (2019)
' ...
        'IC    = |imag(C)|'], ...
        ['Lagged Coherence (2019)
' ...
        'LC    = |imag(C)|/sqrt(1-real(C)^2)'], ...
        [' Imaginary Coherence (before 2019)
' ...
        ' IC    = imag(C)^2 / (1-real(C)^2) ']; ...
        'mscohere', 'icohere2019', 'lcohere2019', 'icohere'};
    sProcess.options.cohmeasure.Type    = 'radio_label';
    sProcess.options.cohmeasure.Value   = 'mscohere';
    % === Overlap
    sProcess.options.overlap.Comment = 'Overlap for PSD estimation:';
    sProcess.options.overlap.Type    = 'value';
    sProcess.options.overlap.Value   = {50, '%', []};
    % === MAX FREQUENCY RESOLUTION
    sProcess.options.maxfreqres.Comment = 'Maximum frequency resolution:';
    sProcess.options.maxfreqres.Type    = 'value';
    sProcess.options.maxfreqres.Value   = {2,'Hz',2};
    % === HIGHEST FREQUENCY OF INTEREST
    sProcess.options.maxfreq.Comment = 'Highest frequency of interest:';
    sProcess.options.maxfreq.Type    = 'value';
    sProcess.options.maxfreq.Value   = {60,'Hz',2};
    % === OUTPUT MODE
    sProcess.options.outputmode.Comment = {'Save individual results (one file per input file)', 'Concatenate input files before processing (one file)', 'Save average connectivity matrix (one file)'};
    sProcess.options.outputmode.Type    = 'radio';
    sProcess.options.outputmode.Value   = 1;
    sProcess.options.outputmode.Group   = 'output';
end

%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess) %#ok<DEFNU>
    Comment = sProcess.Comment;
end

%% ===== RUN =====
function OutputFiles = Run(sProcess, sInputA) %#ok<DEFNU>
    % Input options
    OPTIONS = process_corr1n('GetConnectOptions', sProcess, sInputA);
    if isempty(OPTIONS)
        OutputFiles = {};
        return
    end

    % Metric options
    OPTIONS.Method = 'cohere';
    OPTIONS.RemoveEvoked  = sProcess.options.removeevoked.Value;
    OPTIONS.MaxFreqRes    = sProcess.options.maxfreqres.Value{1};
    OPTIONS.MaxFreq       = sProcess.options.maxfreq.Value{1};
    OPTIONS.CohOverlap    = 0.50;  % First pre-define the overlap
    OPTIONS.pThresh       = 0.05;
    OPTIONS.CohMeasure    = sProcess.options.cohmeasure.Value;

    % Change the overlap if it is specified
    if isfield(sProcess.options, 'overlap') && isfield(sProcess.options.overlap, 'Value') && ...
       iscell(sProcess.options.overlap.Value) && ~isempty(sProcess.options.overlap.Value) && ~isempty(sProcess.options.overlap.Value{1})
        OPTIONS.CohOverlap = sProcess.options.overlap.Value{1}/100 ;
    end

    % Compute metric
    OutputFiles = bst_connectivity({sInputA.FileName}, [], OPTIONS);
end

%% ===== TEST FUNCTION =====
function Test() %#ok<DEFNU>
    % Start a new report
    bst_report('Start');
    % Get test datasets
    sFile = process_simulate_ar('Test');
    % Loop on frequency resolutions
    for freq = [1 2 3 5 10 20]
        % Coherence process
        sTmp = bst_process('CallProcess', 'process_cohere1n', sFile, [], ...
            'timewindow',   [], ...    % All the time in input
            'cohmeasure',   1, ...     % 1=Magnitude-squared, 2=Imaginary
            'overlap',      3, ...     % 50%
            'maxfreqres',   freq, ...  % VARIES
            'maxfreq',      [], ...    % No maximum frequency
            'pThresh',      0.05, ...  % p-value thrshold
            ... 'isfreqbands',  0, ...     % No frequency bands
            ... 'freqbands',    [], ...
            'outputmode',   1);        % Save individual results (one file per input file)
        % Snapshot: spectrum
        bst_process('CallProcess', 'process_snapshot', sTmp, [], ...
            'target',       11, ...  % Connectivity matrix (image)
            'modality',     1, 'orient', 1, 'time', 0, 'contact_time', [-40, 110], 'contact_nimage', 16, ...
            'Comment',      [sTmp.Comment, ': ' sTmp.Comment]);
    end
    % Save and display report
    ReportFile = bst_report('Save', sTmp);
    bst_report('Open', ReportFile);
end


function [ screen ] = tapas_BLT_initScreen(params)
% tapas_BLT_initScreen initilaizes the screen for the BLT experiment

% Here we call some default settings for setting up Psychtoolbox
PsychDefaultSetup(2);

% Psychtoolbox has some issues in Mac which only allows us to run this
% by skipping some default tests, we might want to change that for windows
Screen('Preference', 'SkipSyncTests', 1);

% Get the screen numbers
screens = Screen('Screens');

% Draw to the external screen if avaliable
screen.number = max(screens);

% Define black and white
screen.white = WhiteIndex(screen.number);
screen.black = BlackIndex(screen.number);
screen.grey = screen.white / 2;
screen.inc = screen.white - screen.grey;

% Open an on screen window
switch params.expMode
    case 'debug'
        rectDebug = [0 0 1300 900];
        [screen.window, screen.windowRect] = PsychImaging('OpenWindow', screen.number, screen.black, rectDebug);
    case {'task', 'train_4', 'train_6'}
        [screen.window, screen.windowRect] = PsychImaging('OpenWindow', screen.number, screen.black);
end

% Get the size of the on screen window
[screen.xpixels, screen.ypixels] = Screen('WindowSize', screen.window);

% Query the frame duration
screen.ifi = Screen('GetFlipInterval', screen.window);

% Get the centre coordinate of the window
[screen.xCenter, screen.yCenter] = RectCenter(screen.windowRect);

% Set the screen size for display
screen.fit = [screen.xCenter/2.5, screen.yCenter/3, 1.6*screen.xCenter, 1.6*screen.yCenter];

% Set up alpha-blending for smooth (anti-aliased) lines
Screen('BlendFunction', screen.window, 'GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA');

% Adjust the text size as specified
Screen('TextSize', screen.window, params.txt.size);

end


function sae = saesetup(size)
    for u = 2 : numel(size)
        sae.ae{u-1} = nnsetup([size(u-1) size(u) size(u-1)]);
    end
end


function axesSDG = InitAxesSDG(handles)

if ~exist('handles','var')
    handles = [];
end

if isempty(handles)
    hAxes = axes();
else
    hAxes = handles.axesSDG;
end

default_colors = [...
    0.2  0.6  0.1;
    1.0  0.5  0.0;
    1.0  0.0  1.0;
    0.5  0.5  1.0;
    0.0  1.0  1.0;
    1.0  0.0  0.0;
    0.2  0.3  0.1;
    0.8  0.6  0.6;
    0.5  1.0  0.5;
    0.5  1.0  1.0;
    0.0  0.0  0.0;
    0.2  0.2  0.2;
    0.4  0.4  0.4;
    0.6  0.6  0.6;
    0.8  0.8  0.8 ...
];

p = [getAppDir(), 'SDGcolors.csv'];
try
    rgb01 = csvread(p) / 255;
    if size(rgb01, 1) < 6
        rgb01 = default_colors;
        fprintf('SDGcolors.csv is not an appropriate table of more than 6 RGB values. Using default colors.\n')
    else
        fprintf('Loaded %s colors from SDGcolors.csv.\n', num2str(size(rgb01, 1)))
    end
catch  % File not found
    rgb01 = default_colors;
    fprintf('Failed to load SDGcolors.csv. Using default colors.\n')
end

axesSDG = struct(...
                 'handles', struct(...
                                   'axes',hAxes, ...
                                   'SD', [], ...
                                   'ch', [] ...
                                  ), ...
                 'iCh', [], ...
                 'iSrcDet', [], ...
                 'SDPairColors', rgb01...
               );


function calcselective_search( config)
ssconfig = config.selective_search;

images = dir(config.imageLocation);
images = regexpi({images.name}, '.*jpg|.*jpeg|.*png|.*bmp', 'match');
images = [images{:}];
if(~exist([config.outputLocation '/selective_search'], 'dir'))
	mkdir(config.outputLocation,'/selective_search')
end

for i=1:length(images)
        imname = char(images(i));

        fprintf('Calculating Selective Search Object Proposals for %s\n', imname);

        impath = fullfile(config.imageLocation, imname);
        im=imread(impath);

        proposals=calcselective_searchForIm(im,ssconfig);
        saveFile=[imname '.mat'];
        save([config.outputLocation '/selective_search/' saveFile], 'proposals');
end


function y = gt(X,Y)
%GT (overloaded)

if isa(X,'blkvar')
    X = sdpvar(X);
end

if isa(Y,'blkvar')
    Y = sdpvar(Y);
end

warning('YALMIP:strict','Strict inequalities are not supported. A non-strict has been added instead'')');

try
    y = constraint(X,'>=',Y);
catch
    error(lasterr)
end


function seed = hammersley_seed_get ( )

%*****************************************************************************80
%
%% HAMMERSLEY_SEED_GET gets the seed vector of the leaped Hammersley subsequence.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    29 January 2005
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Output, integer SEED(1:DIM_NUM), the Hammersley seed vector.
%
  global hammersley_BASE
  global hammersley_LEAP
  global hammersley_DIM_NUM
  global hammersley_SEED
  global hammersley_STEP

  dim_num = hammersley_DIM_NUM;

  if ( ~halham_dim_num_check ( dim_num ) )
    fprintf ( 1, '\n' );
    fprintf ( 1, 'HAMMERSLEY_SEED_GET - Fatal error!\n' );
    error ( 'HAMMERSLEY_SEED_GET - Fatal error!' );
  end

  if ( length ( hammersley_SEED ) ~= dim_num )

    hammersley_SEED = [];

    for i = 1 : dim_num
      hammersley_SEED(i) = 0;
    end

  end

  seed(1:dim_num) = hammersley_SEED(1:dim_num);

  return
end


function file_saveMatlab(file, dat, mrk, mnt, varargin)
%FILE_SAVEMATLAB - Save EEG data structures in Matlab format
%
% Synopsis:
%   file_saveMatlab(FILE, DAT, MRK, MNT, <OPT>)
%
% Arguments:
%   FILE:   CHAR    name of data file
%   DAT:    STRUCT  structure of continuous or epoched signals
%   MRK:    STRUCT  marker structure
%   MNT:    STRUCT  electrode montage structure
%
%   OPT: PROPLIST - Structure or property/value list of optional properties:
%   'Folder' - CHAR: Path to save the file. Default is the global variable BTB.MatDir
%                  unless FILE is an absolute path in which case it is ''.
%   'Channelwise' - BOOL: If true, signals are saved channelwise. This is an advantage
%                  for big files, because it allows to load selected
%                  channels. (default 1)
%   'Format' - CHAR: 'double', 'float', 'int16', or 'auto' (default).
%             In 'auto' mode, the function tries to find a lossless conversion
%             of the signals to INT16 (see property '.ResolutionList').
%             If this is possible '.format' is set to 'INT16', otherwise it is
%             set to 'DOUBLE'.
%   'Resolution' - CHAR|INT : Resolution of signals, when saving in format INT16.
%                 (Signals are divided by this factor before saving.) The resolution
%                 maybe selected for each channel individually, or globally for all
%                 channels. In the 'auto' mode, the function tries to find for each
%                 channel a lossless conversion to INT16 (see property
%                 'ResolutionList'). For all other channels the resolution producing
%                 least information loss is chosen (under the resolutions that avoid
%                 clipping). Possible values:
%                 'auto' (default),
%                 numerical scalar, or
%                 numerical vector of length 'number of channels'
%                 (i.e., length(DAT.clab)).
%   'ResolutionList' - DOUBLE: Vector of numerical values. These values are tested as
%                      resolutions to see whether lossless conversion to INT16 is
%                      possible. Default [1 0.5 0.1].
%   'Accuracy' - DOUBLE: used to define "losslessness" (default 10e-10)
%   'AddChannels' - BOOL: (true or false) Adds the channels in DAT to the
%                   existing MAT file (default 0)
%   'Vars': Additional variables that should be stored. 'opt.Vars' must be a
%           cell array of variable names, e.g., {'hdr', 'patient_info'}
%   'SaveParam': additonal parameters to be passes to the Matlab save function,
%           default {'-v7'}
%
%Description:
%   Saves data, marker, montage and (if requested) additional variables in
%   BTB format in a single matlab file.
%
% See also: file_loadBV file_loadMatlab

% Author(s): Benjamin Blankertz, Feb 2005

global BTB

props = {'Folder'            BTB.MatDir   'CHAR'
         'Channelwise'       1            '!BOOL'
         'Format'            'auto'       '!CHAR(double float int16 auto)'
         'Resolution'        'auto'       '!CHAR(auto)|!DOUBLE[1]|!DOUBLE[-]'
         'ResolutionList'   [1 0.5 0.1]   '!DOUBLE[-]'
         'Accuracy'          10e-10       '!DOUBLE[1]'
         'AddChannels'       0            '!BOOL'
         'Vars'              {}           'CHAR|CELL{CHAR}'
         'SaveParam'         {'-v7'}      'CELL'
        };

misc_checkType(dat,'STRUCT(x fs clab)');
misc_checkType(mrk,'STRUCT(time y)');
misc_checkType(mnt,'STRUCT(x y clab)');
opt= opt_proplistToStruct(varargin{:});
[opt, isdefault]= opt_setDefaults(opt, props);
opt_checkProplist(opt, props);

if ischar(opt.Vars),
  opt.Vars= {opt.Vars};
end
if fileutil_isAbsolutePath(file),
  opt.Folder = '';
end

nChans= length(dat.clab);
fullname= fullfile(opt.Folder, file);
if opt.AddChannels && ~exist([fullname '.mat'], 'file')
  warning('File does not exist: ignoring option ''AddChannels''.');
  opt.AddChannels= 0;
end

if opt.AddChannels,
  if ~opt.Channelwise,
    warning('AddChannels requested: forcing channelwise mode');
    opt.Channelwise= 1;
  end
  nfo_old= file_loadMatlab(fullname, 'Vars','nfo');
  if ~isdefault.format && opt.Format~=nfo_old.format,
    warning(sprintf('format mismatch: using %s', nfo_old.format));
  end
  opt.format= nfo_old.format;
end

if any(strcmpi(opt.Format, {'INT16','AUTO'}))
  %% Look for resolutions producing lossless conversion to INT16.
  if strcmpi(opt.Resolution, 'auto'),
    opt.Resolution= NaN*ones(1, nChans);
    for cc= 1:nChans,
      rr= 0;
      res_found= 0;
      while ~res_found && rr<length(opt.ResolutionList),
        rr= rr+1;
        X= dat.x(:,cc,:) / opt.ResolutionList(rr);
        X= X(:);
        if all( abs(X-round(X)) < opt.Accuracy ) && ...
              all(X>=-32768-opt.Accuracy) && all(X<=32767+opt.Accuracy),
          opt.Resolution(cc)= opt.ResolutionList(rr);
          res_found= 1;
        end
      end
      clear X;
    end
  end

  %% Expand global resolution.
  if length(opt.Resolution)==1,
    opt.Resolution= opt.Resolution*ones(1,nChans);
  end

  %% Check format of resolution.
  if ~all(isnumeric(opt.Resolution)) || length(opt.Resolution)~=nChans,
    error('property resolution has invalid format');
  end

  %% If for all channels lossless conversions were found, auto-select
  %% format INT16.
  if strcmpi(opt.Format, 'auto'),
    if all(~isnan(opt.Resolution)),
      opt.Format= 'INT16';
    else
      opt.Format= 'DOUBLE';
    end
  end
end

%% Check format of property format.
if ~ismember(upper(opt.Format), {'INT16','FLOAT','DOUBLE'},'legacy'),
  error('unknown format');
end

%% Select resolution for lossy conversion to INT16.
if strcmpi(opt.Format, 'INT16'),
  iChoose= find(isnan(opt.Resolution));
  for cc= 1:length(iChoose),
    ci= iChoose(cc);
    dat_ch= dat.x(:,ci,:);
    opt.Resolution(ci)= 1.000001*max(abs(dat_ch(:)))'/32767;
  end
end

%% Gather some summary information into structure 'nfo'.
nfo= struct_copyFields(dat,{'fs','clab'});
nfo.T= size(dat.x,1);
nfo.nEpochs= size(dat.x,3);
nfo.length= size(dat.x,1)*size(dat.x,3) / dat.fs;
nfo.format= opt.Format;
nfo.resolution= opt.Resolution;
nfo.file= fullname;
if isfield(mrk, 'time')
  nfo.nEvents= length(mrk.time);
else
  nfo.nEvents= 0;
end
if isfield(mrk, 'y'),
  nfo.nClasses= size(mrk.y,1);
else
  nfo.nClasses= 0;
end
if isfield(mrk, 'className')
  nfo.className= mrk.className;
else
  nfo.className= {};
end

%% if adding channels is requested, merge the nfo structures
if opt.AddChannels,
  nfo.clab= cat(2, nfo_old.clab, nfo.clab);
  nfo.resolution= cat(2, nfo_old.resolution, nfo.resolution);
end

%% Create directory if necessary
[filepath, filename]= fileparts(fullname);
if ~exist(filepath, 'dir'),
  [parentdir, newdir]=fileparts(filepath);
  [status,msg]= mkdir(parentdir, newdir);
  if status~=1,
    error(msg);
  end
  if isunix,
    unix(sprintf('chmod a-rwx,ug+rwx %s', filepath));
  end
end

saveParamNoVer= opt.SaveParam;
iVer= strmatch('-v', saveParamNoVer);
saveParamNoVer(iVer)= [];

if opt.AddChannels,
  %% update the nfo structure
  save(fullname, '-APPEND', 'nfo', saveParamNoVer{:});
  chan_offset= length(nfo_old.clab);
else
  save(fullname, 'mrk', 'mnt', 'nfo', opt.SaveParam{:});
  chan_offset= 0;
end

dat.file= fullname;
if opt.Channelwise,
  rhs= 'dat.x(:,cc,:)';
  switch(upper(opt.Format)),
   case 'INT16',
    evalstr= ['int16(round(' rhs '/opt.Resolution(cc)));'];
    dat.resolution= opt.Resolution;
   case 'FLOAT',
    evalstr= ['float(' rhs ');'];
   case 'DOUBLE',
    evalstr= [rhs ';'];
  end
  for cc= 1:nChans,
    varname= ['ch' int2str(chan_offset+cc)];
    eval([varname '= ' evalstr]);
    save(fullname, '-APPEND', varname, saveParamNoVer{:});
    clear(varname);
  end
  dat= rmfield(dat, 'x');
  %% the following field updates are needed for add_channels=1
  dat.clab= nfo.clab;
  if ~ismember(upper(opt.Format), {'FLOAT','DOUBLE'},'legacy'),
    dat.resolution= nfo.resolution;
  end
  save(fullname, '-APPEND', 'dat', saveParamNoVer{:});
else
  switch(upper(opt.Format)),
   case 'INT16',
    for cc= 1:nChans,
      dat.x(:,cc,:)= round( dat.x(:,cc,:) / opt.Resolution(cc) );
    end
    dat.x= int16(dat.x);
    dat.resolution= opt.Resolution;
   case 'FLOAT',
    dat.x= float(dat.x);
   case 'DOUBLE',
    %% nothing to do
  end
  save(fullname, '-APPEND', 'dat', saveParamNoVer{:});
end

%% Save additional variables, as requested.
if ~isempty(opt.Vars),
  vars= struct;
  for vv= 1:length(opt.Vars),
    vars.(opt.Vars{vv})= evalin('caller', opt.Vars{vv});
  end
  save(fullname, '-APPEND', '-STRUCT', 'vars', saveParamNoVer{:});
end


function varargout=convfft(varargin)
%
% For calling details please see v_convfft.m
%
% This dummy routine is included for backward compatibility only
% and will be removed in a future release of voicebox. Please use
% v_convfft.m in future and/or update with v_voicebox_update.m
%
%      Copyright (C) Mike Brookes 2018
%      Version: $Id: convfft.m 10863 2018-09-21 15:39:23Z dmb $
%
if nargout
    varargout=cell(1,nargout);
    [varargout{:}]=v_convfft(varargin{:});
else
    v_convfft(varargin{:});
end


function xyz = skel2xyz(skel, channels)

% SKEL2XYZ Compute XYZ values given skeleton structure and channels.
% FORMAT
% DESC Computes X, Y, Z coordinates given a BVH or acclaim skeleton
% structure and an associated set of channels.
% ARG skel : a skeleton for the bvh file.
% ARG channels : the channels for the bvh file.
% RETURN xyz : the point cloud positions for the skeleton.
%
% COPYRIGHT : Neil D. Lawrence, 2006
%
% SEEALSO : acclaim2xyz, bvh2xyz

% MOCAP

fname = str2func([skel.type '2xyz']);
xyz = fname(skel, channels);


function h = errorpatch(x,y,l,u,varargin)

% make inputs uniform
x = x(:);
y = y(:);
if numel(l) == 1,
  l = repmat(l,size(x));
else
  l = l(:);
end
if ~isnumeric(u),
  varargin = [{u},varargin];
  u = l;
elseif nargin < 4,
  u = l;
elseif numel(u) == 1,
  u = repmat(u,size(x));
else
  u = u(:);
end

% sort by x
[x,order] = sort(x);
y = y(order);
l = l(order);
u = u(order);

h = patch([x;flipud(x)],[y-l;flipud(y+u)],[.7,.7,.7],...
  'EdgeColor','none',varargin{:});


%% Run the statistics for a version of the VEP
saveFile = 'dataStatistics.mat';

%% Setup the directories and titles
setupDir(1) = struct('inDir', [], 'outDir', [], 'title', [], 'fieldPath', []);
setupDir(1).inDir = 'N:\ARLAnalysis\VEP\VEPRobust_1Hz_Post_Median_Unfiltered';
setupDir(1).outDir = 'N:\ARLAnalysis\VEP\VEPRobust_1Hz_Post_Median_Unfiltered_Report';
setupDir(1).title = 'VEP robust 1Hz';
setupDir(1).fieldPath = {'etc', 'noiseDetection', 'reference', 'noisyStatistics'};
%% Get the directory list
for k = 1:length(setupDir)
    inList = dir(setupDir(k).inDir);
    inNames = {inList(:).name};
    inTypes = [inList(:).isdir];
    inNames = inNames(~inTypes);

    %% Take only the .set files
    validNames = true(size(inNames));
    for j = 1:length(inNames)
        ext = inNames{j}((end-3):end);
        if ~strcmpi(ext, '.set')
            validNames(j) = false;
        else
            inNames{j} = [setupDir(k).inDir filesep inNames{j}];
        end
    end
    %% Consolidate the results in a single structure for comparative analysis
    collectionStats = createCollectionStatistics(setupDir(k).title, ...
                                          inNames, setupDir(k).fieldPath);
    %% Save the statistics in the specified file
    save([setupDir(k).outDir filesep saveFile], 'collectionStats', '-v7.3');

    %% Display the reference statistics
    showNoisyStatistics(collectionStats);
end


function [kern, noise, ivmInfo] = ivmDeconstruct(model)

% IVMDECONSTRUCT break IVM in pieces for saving.
% FORMAT
% DESC takes an IVM model structure and breaks it into component
% parts for saving.
% ARG model : the model that needs to be saved.
% RETURN kern : the kernel component of the IVM model.
% RETURN noise : the noise component of the IVM model.
% RETURN ivmInfo : a structure containing the other information
% from the IVM: what the active set is, what the inactive set is
% and what the site parameters are.
%
% SEEALSO : ivmReconstruct
%
% COPYRIGHT : Neil D. Lawrence, 2005

% IVM

kern = rmfield(model.kern, 'Kstore');
kern = rmfield(kern, 'diagK');
noise = model.noise;
ivmInfo.I = model.I;
ivmInfo.J = model.J;
ivmInfo.m = model.m;
ivmInfo.beta = model.beta;
ivmInfo.type = 'ivm';


% 此函数非本人编写
% 参考自：https://www.cnblogs.com/GarfieldEr007/p/5598861.html
function [ dest ] = drawRect( src, pt, wSize,  lineSize, color )
%简介：
% %将图像画上有颜色的框图，如果输入是灰度图，先转换为彩色图像，再画框图
% 图像矩阵
% 行向量方向  是  y
% 列向量方向  是  x
%----------------------------------------------------------------------
%输入：
% src：        原始图像，可以为灰度图，可为彩色图
% pt：         左上角坐标   [x1, y1]
% wSize：   框的大小      [wx, wy]
% lineSize： 线的宽度
% color：     线的颜色      [r,  g,  b]
%----------------------------------------------------------------------
%输出：
% dest：           画好了的图像
%----------------------------------------------------------------------

%flag=1: 有缺口的框
%flag=2: 无缺口的框
flag = 1;

%判断输入参数个数
if nargin < 5
    color = [255 255 0];
end

if nargin < 4
    lineSize = 1;
end

if nargin < 3
    disp('输入参数不够 !!!');
    return;
end

%判断框的边界问题
[yA, xA, z] = size(src);
x1 = pt(1);
y1 = pt(2);
wx = wSize(1);
wy = wSize(2);
if  x1>xA || ...
        y1>yA||...
        (x1+wx)>xA||...
        (y1+wy)>yA

    disp('画的框将超过图像 !!!');
    return;
end

%如果是单通道的灰度图，转成3通道的图像
if 1==z
    dest(:, : ,1) = src;
    dest(:, : ,2) = src;
    dest(:, : ,3) = src;
else
    dest = src;
end

%开始画框图
for c = 1 : 3                 %3个通道，r，g，b分别画
    for dl = 1 : lineSize   %线的宽度，线条是向外面扩展的
        d = dl - 1;
        if  1==flag %有缺口的框
            try
                dest(  y1-d ,            x1:(x1+wx) ,  c  ) =  color(c); %上方线条
                dest(  y1+wy+d ,     x1:(x1+wx) , c  ) =  color(c); %下方线条
                dest(  y1:(y1+wy) ,   x1-d ,           c  ) =  color(c); %左方线条
                dest(  y1:(y1+wy) ,   x1+wx+d ,    c  ) =  color(c); %左方线条
            catch
                continue
            end
        elseif 2==flag %无缺口的框
            dest(  y1-d ,            (x1-d):(x1+wx+d) ,  c  ) =  color(c); %上方线条
            dest(  y1+wy+d ,    (x1-d):(x1+wx+d) ,  c  ) =  color(c); %下方线条
            dest(  (y1-d):(y1+wy+d) ,   x1-d ,           c  ) =  color(c); %左方线条
            dest(  (y1-d):(y1+wy+d) ,   x1+wx+d ,    c  ) =  color(c); %左方线条
        end
    end
end %主循环尾

end %函数尾


function check_installation
% check whether all binaries are working properly

% check nfft
if ~check_nfft

  rmpath([mtex_path filesep 'extern' filesep 'nfft_openMP'])
  addpath([mtex_path filesep 'extern' filesep 'nfft'])

  hline()
  if check_nfft

    disp(' ');
    disp('  MTEX: Error running NFFT with openmp - I''m going to use it without openmp!');

  else

    disp(' ');
    disp('  MTEX: Error running NFFT!');
    disp('   ');
    disp('  The original error message was:');
    e = lasterror;
    disp(['  ' e.message]);

    disp(' ');
    disp(' I did not get NFFT working. This restricts the functionality of MTEX.')
    disp(' To overcome this restriction you may need to compile the NFFT your own.');
    if ismac
      disp(' Please have a look at https://mtex-toolbox.github.io/installation for possible workarounds')
    else
      disp(' Please have a look at https://github.com/mtex-toolbox/mtex/blob/develop/extern/nfft_openMP/readme.md');
    end

  end
  hline()
end

% check mex
check_mex;

end

% -------- check nfft

function e = check_nfft

e = 0;
try
  % check NFSFT
  f = @(x) x.theta;
  sF = S2FunHarmonic.quadrature(f,'bandwidth',12);
  assert(abs(f(xvector) - sF.eval(xvector))<1e-5);

  % check NFSOFT
  %ori = orientation.rand(100,crystalSymmetry);
  %odf = calcDensity(ori,'Fourier','bandwidth',12);
  %odf.eval(ori);
  e = 1;
end
end

% ----------- check mex files ---------------------------
function check_mex

% check for mex files
if fast_check_mex, return;end

hline('-');
disp('Checking mex files failed!');

disp('> Trying now to recompile mex files.');
opwd = pwd;
cd(fullfile(mtex_path,'mex'));
mex_install(mtex_path,false);
cd(opwd);

if fast_check_mex
  disp('> Hurray - everythink works!')
else
  hline()
  disp('  MTEX: Couldn''t get the mex files working!');
  disp('   ');
  disp('  The original error message was:');
  e = lasterror;
  disp(['  ' e.message]);

  disp(' ');
  disp(' Please have a look at https://mtex-toolbox.github.io/installation')
  disp(' or ask in the forum https://groups.google.com/forum/#!forum/mtexmail for help.');

  hline()
end
hline('-')
end

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

function e = fast_check_mex

e = false;
mexpath = [mtex_path filesep 'mex'];

% check for existence of the files
mex = {'quaternion_*','S1Grid_*','S2Grid_*','SO3Grid_*','insidepoly_*','representationbased*'};
for k=1:numel(mex)
  cfile = dir(fullfile(mexpath, [mex{k} '.c']));
  for c=1:numel(cfile)
    [~,cname] = fileparts(cfile(c).name);
    mexfile = fullfile(mexpath, [cname,'.' mexext]);
    if ~exist(mexfile,'file')
       warning(['missing mex-file ' [cname,'.' mexext]])
      return
    end
  end
end

try
  S3G = equispacedSO3Grid(crystalSymmetry,specimenSymmetry,'points',100);
  dot_outer(S3G,quaternion.id,'epsilon',pi/4);

  SO3F = SO3FunHarmonic(rand(1000,1));
  SO3F.eval(rotation.rand(10));

  e = true;
catch
  e = false;
end

end

function hline(st)

if nargin < 1, st = '*'; end
fprintf('\n');
disp(repmat(st,1,80));

end


% set_dbm_centers - See (Montavon & Muller, 2012)
% Copyright (C) 2011 KyungHyun Cho, Tapani Raiko, Alexander Ilin
%
% 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 2
% 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, write to the Free Software
% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
%
function [D] = set_dbm_centers (D)

    n_layers = length(D.structure.layers);

    D.centering.use = 1;
    D.centering.centers = cell(n_layers, 1);
    for l = 1:n_layers
        D.centering.centers{l} = sigmoid(D.biases{l});
    end


%islvector - Test if parameter is a (pseudo) logical vector satisfying an optional list of tests.
%
%  USAGE
%
%    test = islvector(x,test1,test2,...)
%
%    x              parameter to test
%    test1...       optional list of additional tests (see examples below)
%
%  EXAMPLES
%
%    % Test if x is a logical vector
%    islvector(x)
%
%    % Special test: test if x is a logical vector of length 3
%    islvector(x,'#3')
%
%  NOTE
%
%    To be considered logical, the vector should contain only values 0 and 1, but it
%    does not need to actually be of class 'logical' (class(x) could be e.g. 'double').
%
%  SEE ALSO
%
%    See also islscalar, islmatrix, isdmatrix, isdvector, isdscalar, isimatrix, isiscalar,
%    isstring.
%

% Copyright (C) 2010-2011 by Michaël Zugaro
%
% 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.

function test = islvector(x,varargin)

% Check number of parameters
if nargin < 1,
  error('Incorrect number of parameters (type ''help islvector'' for details).');
end

% Test: logical, vector
test = islogical(x) & isvector(x);

% Optional tests
for i = 1:length(varargin),
	try
		if varargin{i}(1) == '#',
			if length(x) ~= str2num(varargin{i}(2:end)), test = false; return; end
		end
	catch err
		error(['Incorrect test ''' varargin{i} ''' (type ''help islvector'' for details).']);
	end
end

function test = islogical(x)

test = builtin('islogical',x) | all(x(:)==0|x(:)==1);


function [proj,geo,angles]=loadNikonProjections(filepath,geo,angles,varargin)
%[proj,angles]=loadNikonProjections(filepath,geo,angles,varargin)
%   loads Nikon uCT machine projections
%
%   loadNikonData(filepath,geo,angles) Loads a dataset given its FILEPATH,
%      a geometry GEO and angles ANGLES (loaded from readXtekctGeometry())
%
%   loadNikonData(filepath,geo,angles, OPT,VAL,...) uses options and values.
%      These are options in case you don't want to load the entire
%      dataset, but only particular sets of projections.
%      The possible options in OPT are:
%           'sampling': type of sampling. default 'equidistant' Can be:
%                  'equidistant': equidistantly sample the entire set
%                       of angles. 'num_angles' should be set for partial
%                       loading of the data.
%                  'step': sample the entire set of projections every
%                         'sampling_step' angles.
%                  'continous': Load the first 'num_angles' amount of
%                             angles only.
%
%           'num_angles': Number of total angles to load. Default all of
%                    them. Useful for 'equidistant' and 'continous' loading
%
%           'sampling_step': step to load when loading projections. Default
%           1. Useful for 'step' loading.
%

% developed by A. Biguri and W. Sun 06.07.2020

%% Parse inputs.

[angles_to_load,index]=parse_inputs(geo,angles,varargin);

% make sure its path
if filepath(end)~='\' && filepath(end)~='/'
   filepath=[filepath '/'];
end
%% get filename
% assuming TIF and 4 digits.
firstfile = dir([filepath,'/*.tif']); %
firstfile = firstfile(1).name;
filename = firstfile(1:end-8);

fprintf("Dataset in: %s \n", filepath);

%% load images
%proj=[];
l = length(angles_to_load);
proj = zeros(geo.nDetector(2),geo.nDetector(1),l,'single');
for ii=1:length(angles_to_load)
    if(~mod(ii,50))
      fprintf("Loading: %d / %d \n", ii, length(angles_to_load));
    end
     proj(:,:,ii)=single(imread([filepath,filename,num2str(index(ii),'%04d'),'.tif']));
end
%% Beer lambert
if any(proj(:))>single(geo.whitelevel)
    warning('Changing the Whitelevel value as projection data has higher value than specified in the file');
    geo.whitelevel=max(proj(:)+1);
end
proj=-log(proj/single(geo.whitelevel));
geo=rmfield(geo,'whitelevel');
%%
angles=angles_to_load;
end

function [angles,indices]=parse_inputs(geo,angles,argin)
opts=     {'sampling','num_angles','sampling_step'};
defaults=ones(length(opts),1);
% Check inputs
nVarargs = length(argin);
if mod(nVarargs,2)
    error('Invalid number of inputs')
end

% check if option has been passed as input
for ii=1:2:nVarargs
    ind=find(ismember(opts,lower(argin{ii})));
    if ~isempty(ind)
        defaults(ind)=0;
    else
        error(['Optional parameter "' argin{ii} '" does not exist' ]);
    end
end

for ii=1:length(opts)
    opt=opts{ii};
    default=defaults(ii);
    % if one option is not default, then extract value from input
    if default==0
        ind=double.empty(0,1);jj=1;
        while isempty(ind)
            ind=find(isequal(opt,lower(argin{jj})));
            jj=jj+1;
        end
        if isempty(ind)
            error(['Optional parameter "' argin{jj} '" does not exist' ]);
        end
        val=argin{jj};
    end

    switch opt
        case 'sampling'
            if default
                sampling='equidistant';
            else
                if strcmpi(val,'equidistant') || strcmpi(val,'step')|| strcmpi(val,'continuous')
                    sampling=val;
                else
                   error('sampling type not understood, must be  equidistant, step or continuous');
                end
            end
        case 'num_angles'
            if default
                num_angles=length(angles);
            else
                num_angles=val;
            end
        case 'sampling_step'
            if default
               sampling_step=1;
            else
               sampling_step=val;
            end
        otherwise
            error(['Invalid input name:', num2str(opt),'\n No such option']);
    end
end
% now lets build the angles

if strcmpi(sampling,'equidistant')
   sampling_step=round(length(angles)/num_angles);
   indices=1:sampling_step:length(angles);
   angles=angles(indices);
   return;
end
if strcmpi(sampling,'continuous')
   indices=1:num_angles;
   angles=angles(indices);
   return;
end
if strcmpi(sampling,'step')
   indices=1:sampling_step:length(angles);
   angles=angles(indices);
   return;
end
end


%ROBOT	robot object constructor
%
%	ROBOT
%	ROBOT(robot)		create a copy of an existing ROBOT object
%	ROBOT(LINK, ...)	create from a cell array of LINK objects
%	ROBOT(DH, ...)		create from legacy DYN matrix
%	ROBOT(DYN, ...)		create from legacy DYN matrix
%
%	optional trailing arguments are:
%		Name			robot type or name
%		Manufacturer		who built it
%		Comment			general comment
%
%  If the legacy matrix forms are used the default name is the workspace
% variable that held the data.

% Ryan Steindl based on Robotics Toolbox for MATLAB (v6 and v9)
%
% Copyright (C) 1993-2011, by Peter I. Corke
%
% This file is part of The Robotics Toolbox for MATLAB (RTB).
%
% RTB is free software: you can redistribute it and/or modify
% it under the terms of the GNU Lesser General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% RTB 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 Lesser General Public License for more details.
%
% You should have received a copy of the GNU Leser General Public License
% along with RTB.  If not, see <http://www.gnu.org/licenses/>.
%
% http://www.petercorke.com
function r = SerialLink(L, varargin)
	r.name = 'noname';
	r.manufacturer = '';
	r.comment = '';
	r.links = [];
	r.n = 0;
	r.mdh = 0;
	r.gravity = [0; 0; 9.81];
	r.base = eye(4,4);
	r.tool = eye(4,4);
	r.handle = [];  % graphics handles
	r.q = [];   % current joint angles

	r.lineopt = {'Color', 'black', 'Linewidth', 4};
	r.shadowopt = {'Color', 0.7*[1 1 1], 'Linewidth', 3};
	r.plotopt = {};
	r = class(r, 'SerialLink');

	if nargin == 0
	%zero argument constructor
		return;

	else
		if isa(L, 'SerialLink')
			if length(L) == 1
				% clone the passed robot
				for j=1:L.n
					newlinks(j) = L.links(j);
				end
				r.links = newlinks;
			else
				% compound the robots in the vector
				r = L(1);
				for k=2:length(L)
					r = r * L(k);
				end
			end
		elseif isa(L,'Link')
			r.links = L; %attach the links

		elseif isa(L, 'double')
			% legacy matrix
			dh_dyn = L;
			clear L
			for j=1:numrows(dh_dyn)
				L(j) =        Link();
				L(j).theta =  dh_dyn(j,1);
				L(j).d =      dh_dyn(j,2);
				L(j).a =      dh_dyn(j,3);
				L(j).alpha =  dh_dyn(j,4);

				if numcols(dh_dyn) > 4
					L(j).sigma = dh_dyn(j,5);
				end
			end
			r.links = L;
		else
			error('unknown type passed to SerialLink');
		end
		r.n = length(r.links);
	end
	%process the rest of the arguments in key, value pairs
	opt.name = 'robot';
	opt.comment = [];
	opt.manufacturer = [];
	opt.base = [];
	opt.tool = [];
	opt.offset = [];
	opt.qlim = [];
	opt.plotopt = [];

	[opt,out] = tb_optparse(opt, varargin);
	if ~isempty(out)
		error( sprintf('unknown option <%s>', out{1}));
	end

	% copy the properties to robot object
            p = fieldnames(r);
            for i=1:length(p)
                if isfield(opt, p{i}) && ~isempty(getfield(opt, p{i}))

					r = setfield(r, p{i}, getfield(opt, p{i}));

				end
            end

	% set the robot object mdh status flag
	mdh = [r.links.mdh];
	if all(mdh == 0)
		r.mdh = mdh(1);
	elseif all (mdh == 1)
		r.mdh = mdh(1);
	else
		error('SerialLink has mixed D&H links conventions');
	end
	r = class(r, 'SerialLink');

endfunction


function value = s_is_alpha ( s )

%*****************************************************************************80
%
%% S_IS_ALPHA returns TRUE if the string contains only alphabetic characters.
%
%  Discussion:
%
%    Here, alphabetic characters are 'A' through 'Z' and 'a' through 'z'.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    02 May 2004
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, string S, the string to be checked.
%
%    Output, logical VALUE, is TRUE if the string contains only
%    alphabetic characters.
%
  for i = 1 : length ( s )

    if ( ~ch_is_alpha ( s(i) ) )
      value = 0;
      return
    end

  end

  value = 1;

  return
end


function doy = convert_to_doy (epoch)
    doy = mydatedoy(epoch);
end

%!test
%! epoch = 1;
%! doy = mydatedoy(epoch);
%! doy2 = convert_to_doy(epoch);
%! myassert(doy2, doy)


function S=afni_niml_readsimple(fn,full)
% Reads an AFNI NIML file and returns a 'simple' struct
%
% S=AFNI_NIML_READSIMPLE(FN) reads the AFNI NIML ASCII file FN, and returns
% a 'simple' struct S with the relevant data fields. (This is unlike
% the function AFNI_NIML_READ, which returns all data fields and resembles
% the original file content of FN better).
%
% If the file is present, S will contain the following fields:
%  .data           PxN data for P nodes and N columns (values per node).
%  .node_indices   Px1 indices of P nodes that data refers to (base 0)
%  .history        String with history information
%  .stats          1xN cell with strings of the statistics of the data
%                  (for example, Z or T score).
%  .labels         1xN cell with strings of the labels of the data columns
%  .dset_type      String with the data set type
%
% S=AFNI_NIML_READSIMPLE(FN,true) returns a struct S but with a
% full data matrix (rather than sparse) for all nodes; node indices not
% defined in the file are set to zero. S does not contain a field
% .node_indices.
%
% Please note that this function is *VERY EXPERIMENTAL*, and has only been
% tested with functional data NIML files.
%
% NNO Jan 2010 <n.oosterhof@bangor.ac.uk>

D=afni_niml_read(fn);

if iscell(D)
    if numel(D)==1
        D=D{1};
    else
        error('Cell with multiple elements is not supported');
    end
end

if ~isstruct(D) || ~isfield(D,'dset_type')
    error('Unrecognized input');
end

S=struct();
S.dset_type=D.dset_type;

nodecount=numel(D.nodes);
for k=1:nodecount
    Nk=D.nodes{k};
    if ~isfield(Nk,'name')
        error('Missing name in node element %d',k);
    end

    switch(Nk.name)
        case 'INDEX_LIST'
            S.node_indices=Nk.data;

        case 'SPARSE_DATA'
            S.data=Nk.data;

        case 'AFNI_atr'
            if ~isfield(Nk,'atr_name')
                error('Field %s in node %d has missing atr_name\n', ...
                            Nk.name, D);
            end

            switch Nk.atr_name
                case 'HISTORY_NOTE'
                    S.history=get_string_cell_data(Nk.data);

                case 'COLMS_STATSYM'
                    S.stats=get_string_cell_data(Nk.data);

                case 'COLMS_LABS'
                    S.labels=get_string_cell_data(Nk.data);

            end

        otherwise
            S.misc.(Nk.name)=Nk.data;
    end
end

if nargin>1 && full && isfield(S,'node_indices')
    data=zeros(max(S.node_indices)+1,size(S.data,2));
    data(S.node_indices+1,:)=S.data;
    S.data=data;
    S=rmfield(S,'node_indices');
end

function labels=get_string_cell_data(data)
    while iscell(data) && numel(data)==1 && ~ischar(data)
        data=data{1};
    end

    labels=split_string(data,';');
    if numel(labels{end})==0        % as above
        labels=labels(1:(end-1));
    end


function h = spm_orthviews_showposition()
% :Usage:
% ::
%
%    handles = spm_orthviews_showposition;
%
% plots x, y, and z coordinates on SPM orthviews figure
%
% bring context structure variable into this script.
% contains handles, etc.
%
% ..
%    tor wager, august 2006
%    updated: june 2007
% ..

    global st

    % get current position in mm
    pos = round(spm_orthviews('Pos'));  % x y z

    for i = 1:length(st.vols)

        if ~isempty(st.vols{i}) && ~isempty(st.vols{i}.ax) && ~isempty(st.vols{i}.ax{1})

            axish(1) = st.vols{i}.ax{1}.ax;
            axish(2) = st.vols{i}.ax{2}.ax;
            axish(3) = st.vols{i}.ax{3}.ax;

            % axial
            h(1) = put_text(axish(1),['z = ' num2str(pos(3))]);

            % coronal
            h(2) = put_text(axish(2),['y = ' num2str(pos(2))]);

            % saggital
            h(3) = put_text(axish(3),['x = ' num2str(pos(1))]);

        end

        % %
        % % % Get figure and axis handles
        % % fh = findobj('Tag','Graphics');
        % % ch = get(fh,'Children');
        % % for i= 1:length(ch),mytype = get(ch(i),'Type'); wh(i)=strcmp(mytype,'axes'); end
        % %
        % % if ~exist('wh','var')
        % %     disp('spm_orthviews_showposition: No axis handles found. Exiting.');
        % %     return
        % % end
        % %
        % % axish = ch(find(wh));
        % %
        % % if isempty(axish), return, end

        % % % get which axis is which
        % % for i = 1:length(axish)
        % % poss(i,:) = get(axish(i),'Position');
        % % end
        % %
        % % % get rid of extra axes we may have created in the 4th quadrant
        % % other_axes = find(any(poss(:,1) > .45 & poss(:,2) < .2,2));
        % % axish(other_axes) = [];
        % % poss(other_axes,:) = [];
        % %
        % % % sort into order:  axial, coronal, saggital
        % % ssum = sum(poss(:,1:2),2);
        % % [ssum,ind] = sort(ssum);
        % % axish = axish(ind);
        % %
        % %
        % % % axial
        % % h(1) = put_text(axish(1),['z = ' num2str(pos(3))]);
        % %
        % % % coronal
        % % h(2) = put_text(axish(2),['y = ' num2str(pos(2))]);
        % %
        % % % saggital
        % % h(3) = put_text(axish(3),['x = ' num2str(pos(1))]);

    end  % end loop thru vols

    return

function h = put_text(axish,str)

    axes(axish)

    % remove old text
    oldh = findobj(gca,'Type','text');
    delete(oldh)

    xdelta = diff(get(axish,'XLim'));
    ydelta = diff(get(axish,'YLim'));

    % add new text
    xpos = xdelta .* .42;
    ypos = ydelta .* .1;
    h1 = text(xpos - xdelta * .01,ypos + ydelta * .005,str,'Color','k','FontSize',24, 'FontWeight', 'b');
    h = text(xpos,ypos,str,'Color','g','FontSize',24, 'FontWeight', 'demi');

    return


function [fval,optimState,fsd] = funlogger(fun,u,optimState,state,varargin)
%FUNLOGGER Call objective function and do some bookkeeping.
%   [~,OPTIMSTATE] = FUNLOGGER(FUN,U,OPTIMSTATE,'init') starts logging
%   function FUN with starting point U and optimization struct OPTIMSTATE.
%
%   [~,OPTIMSTATE] = FUNLOGGER(FUN,U,OPTIMSTATE,'init',NMAX) stores up to
%   NMAX function values (default NMAX=1e4).
%
%   [~,OPTIMSTATE] = FUNLOGGER(FUN,U,OPTIMSTATE,'init',NMAX,1) also stores
%   heteroskedastic noise (second output argument) from the logged function.
%
%   [FVAL,OPTIMSTATE] = FUNLOGGER(FUN,U,OPTIMSTATE,'iter') evaluates function
%   FUN at U with optimization struct OPTIMSTATE, return function value FVAL.
%   FUN must take a vector input and returns a scalar value and, optionally,
%   the (estimated) SD of the returned value (if heteroskedastic noise
%   handling is on).
%
%   [FVAL,OPTIMSTATE] = FUNLOGGER(FUN,U,OPTIMSTATE,'single') as 'iter' but
%   does not store function values in the cache.
%
%   [~,OPTIMSTATE] = FUNLOGGER(FUN,U,OPTIMSTATE,'done') finalizes stored
%   function values.

%   Luigi Acerbi 2017

fval = [];
fsd = [];

switch lower(state)
    case 'init' % Start new function logging session

        nvars = numel(u);

        % Number of stored function values
        if nargin > 4; nmax = varargin{1}; else; nmax = []; end
        if isempty(nmax); nmax = 1e4; end

        % Heteroscedastic noise provided as 2nd output argument of FUN?
        if nargin > 5; henoise_flag = varargin{2}; else; henoise_flag = []; end
        if isempty(henoise_flag); henoise_flag = false; end

        optimState.funccount = 0;

        % Passing empty or new OPTIMSTATE
        if ~isfield(optimState,'X') || isempty(optimState.X)
            optimState.X = NaN(nmax,nvars);
            optimState.U = NaN(nmax,nvars);
            optimState.Y = NaN(nmax,1);
            if henoise_flag; optimState.S = NaN(nmax,1); end
            optimState.Xn = 0;                    % Last filled entry
            optimState.Xmax = 0;                  % Maximum entry index

        else % Receiving previous evaluations (e.g. from previous run)

            % Find last evaluated point
            optimState.Xmax = find(~isnan(optimState.Y),1,'last');
            if ~isfield(optimState,'Xn') || isempty(optimState.Xn)
                optimState.Xn = optimState.Xmax;
            end

            % Current cache is smaller than previous one, keep most recent
            % evaluated points
            if optimState.Xmax > nmax
                optimState.X = circshift(optimState.X,-optimState.Xn);
                optimState.X = optimState.X(end-nmax+1:end,:);
                optimState.Y = circshift(optimState.Y,-optimState.Xn);
                optimState.Y = optimState.Y(end-nmax+1:nmax,:);
                if henoise_flag
                    optimState.S = circshift(optimState.S,-optimState.Xn);
                    optimState.S = optimState.S(end-nmax+1:nmax,:);
                end
                optimState.Xn = 0;
                optimState.Xmax = nmax;
            else
                offset = nmax - size(optimState.X,1);
                optimState.X = [optimState.X; NaN(offset,nvars)];
                optimState.Y = [optimState.Y; NaN(offset,1)];
                if henoise_flag
                    optimState.S = [optimState.S; NaN(offset,1)];
                end
            end
            optimState.U = gridunits(optimState.X,optimState);
        end
        optimState.funevaltime = NaN(nmax,1);
        optimState.totalfunevaltime = 0;

    case {'iter','single'} % Evaluate function (and store output for 'iter')

        x = origunits(u,optimState);    % Convert back to original space
        % Heteroscedastic noise?
        henoise_flag = isfield(optimState,'S');

        try
            t_fun = tic;
            if henoise_flag
                [fval,fsd] = fun(x);
            else
                fval = fun(x);
            end
            t = toc(t_fun);

            % Check returned function value
            if ~isscalar(fval) || ~isfinite(fval) || ~isreal(fval)
                error(['The returned function value must be a finite real-valued scalar (returned value: ' mat2str(fval) ').']);
            end

            % Check returned function SD
            if henoise_flag && (~isscalar(fsd) || ~isfinite(fsd) || ~isreal(fsd) || fsd <= 0.0)
                error(['The returned estimated SD (second function output) must be a finite, non-negative real-valued scalar (returned SD: ' mat2str(fsd) ').']);
            end

        catch fun_error
            warning('bads:funError', ['Error in executing the logged function ''' func2str(fun) ''' with input: ' mat2str(x)]);
            rethrow(fun_error);
        end

        % Update function records
        optimState.funccount = optimState.funccount + 1;
        if strcmpi(state, 'iter')
            optimState.Xn = max(1,mod(optimState.Xn+1, size(optimState.X,1)));
            optimState.Xmax = min(optimState.Xmax+1, size(optimState.X,1));
            optimState.X(optimState.Xn,:) = x;
            optimState.U(optimState.Xn,:) = u;
            optimState.Y(optimState.Xn) = fval;
            if henoise_flag
                optimState.S(optimState.Xn) = fsd;
            end
            optimState.funevaltime(optimState.Xn) = t;
        end
        optimState.totalfunevaltime = optimState.totalfunevaltime + t;

    case 'done' % Finalize stored table

        henoise_flag = isfield(optimState,'S');

        if optimState.Xmax < size(optimState.X,1)
            optimState.X = optimState.X(1:optimState.Xmax,:);
            optimState.Y = optimState.Y(1:optimState.Xmax);
            if henoise_flag; optimState.S = optimState.S(1:optimState.Xmax); end
            optimState.funevaltime = optimState.funevaltime(1:optimState.Xmax);
        else
            optimState.X = circshift(optimState.X,-optimState.Xn);
            optimState.Y = circshift(optimState.Y,-optimState.Xn);
            if henoise_flag; optimState.S = circshift(optimState.S,-optimState.Xn); end
            optimState.funevaltime = circshift(optimState.funevaltime,-optimState.Xn);
        end
        optimState = rmfield(optimState,'U');

    otherwise
        error('Unknown FUNLOGGER action.');
end

end


function rgb = white

% This returns a predefined color as [red green blue] values
%   red               = [255   0   0]/255;
%   green             = [  0 192   0]/255;
%   blue              = [  0   0 255]/255;
%   magenta           = [255 255   0]/255;
%   cyan              = [  0 255 255]/255;
%   yellow            = [255 255   0]/255;
%   white             = [255 255 255]/255;
%   black             = [  0   0   0]/255;
%
%   skull             = [140  85  85]/255
%   cortex            = [255 213 119]/255;
%   cortex_light      = [199 194 169]/255;
%   cortex_dark       = [100  97  85]/255;
%   skin              = [249 223 192]/255;
%   skin_light        = [249 223 192]/255;
%   skin_medium_light = [225 194 158]/255;
%   skin_medium       = [188 142 106]/255;
%   skin_medium_dark  = [155 102	65]/255;
%   skin_dark         = [ 91  71  61]/255;

rgb = [255 255 255]/255;


%% SATISFY_SCRIPT is a script to call SATISFY_FUN.
%
%  Discussion:
%
%    The BATCH command runs scripts, not functions.  So we have to write
%    this short script if we want to work with BATCH!
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    23 May 2012
%
%  Author:
%
%    John Burkardt
%
  fprintf ( 1, '\n' );
  fprintf ( 1, 'SATISFY_SCRIPT:\n' );
  fprintf ( 1, '  Call SATISFY_FUN for 0/1 variable assignments that satisfy a formula.\n' );

  tic
  solutions = satisfy_fun ( );
  toc


function A = double(T)
%DOUBLE Convert a sptenmat to a sparse matrix.
%
%   A = double(T) converts T stored as a SPTENMAT to a sparse matrix.
%
%   See also SPTENMAT.
%
%MATLAB Tensor Toolbox.
%Copyright 2012, Sandia Corporation.

% This is the MATLAB Tensor Toolbox by T. Kolda, B. Bader, and others.
% http://www.sandia.gov/~tgkolda/TensorToolbox.
% Copyright (2012) Sandia Corporation. Under the terms of Contract
% DE-AC04-94AL85000, there is a non-exclusive license for use of this
% work by or on behalf of the U.S. Government. Export of this data may
% require a license from the United States Government.
% The full license terms can be found in the file LICENSE.txt

m = prod(T.tsize(T.rdims));
n = prod(T.tsize(T.cdims));
if isempty(T.subs)
    A = sparse(m,n);
else
    A = sparse(T.subs(:,1), T.subs(:,2), T.vals, m, n);
end


function [inputMatrix, coords] = cropMatrix(inputMatrix,varargin)
	% Crops a matrix either by removing rows or adding NaNs to where data was previously.
	% Biafra Ahanonu
	% 2014.01.23 [16:06:01]
	% inputs
		% inputMatrix - a [m n p] matrix of any class type
	% outputs
		% inputMatrix - cropped or NaN'd matrix, same name to reduce memory usage

	[inputMatrix, coords] = ciapkg.image.cropMatrix(inputMatrix,'passArgs', varargin);
end