classdef DagNN < matlab.mixin.Copyable
%DagNN Directed acyclic graph neural network
%   DagNN is a CNN wrapper alternative to SimpleNN. It is object
%   oriented and allows constructing networks with a directed acyclic
%   graph (DAG) topology. It is therefore far more flexible, although
%   a little more complex and slightly slower for small CNNs.
%
%   A DAG object contains the following data members:
%
%   - `layers`: The network layers.
%   - `vars`: The network variables.
%   - `params`: The network parameters.
%   - `meta`: Additional information relative to the CNN (e.g. input
%     image format specification).
%
%   There are additional transient data members:
%
%   `mode`:: `normal`
%      This flag can either be `normal` or `test`. In the latter case,
%      certain blocks switch to a test mode suitable for validation or
%      evaluation as opposed to training. For instance, dropout
%      becomes a pass-through block in `test` mode.
%
%   `accumulateParamDers`:: `false`
%      If this flag is set to `true`, then the derivatives of the
%      network parameters are accumulated rather than rewritten the
%      next time the derivatives are computed.
%
%   `conserveMemory`:: `true`
%      If this flag is set to `true`, the DagNN will discard
%      intermediate variable values as soon as they are not needed
%      anymore in the calculations. This is particularly important to
%      save memory on GPUs.
%
%   `device`:: `cpu`
%      This flag tells whether the DagNN resides in CPU or GPU
%      memory. Use the `DagNN.move()` function to move the DagNN
%      between devices.
%
%   The DagNN is copyable handle, i.e. allows to create a deep copy using
%   `copy` operator `deep_copy = copy(dagnet);`. In all cases the deep copy
%   is located in CPU memory (i.e. is transfered from GPU before copy).
%   Remark: As a side effect the original network is being reset (all
%   variables are cleared) and only the network structure and parameters
%   are copied.
%
%   See Also: matlab.mixin.Copyable

% Copyright (C) 2015-2016 Karel Lenc and Andrea Vedaldi.
% All rights reserved.
%
% This file is part of the VLFeat library and is made available under
% the terms of the BSD license (see the COPYING file).

  properties
    layers
    vars
    params
    meta
  end

  properties (Transient)
    mode = 'normal'
    accumulateParamDers = false
    conserveMemory = true
  end

  properties (Transient, SetAccess = private, GetAccess = public)
    device = 'cpu' ;
  end

  properties (Transient, Access = {?dagnn.DagNN, ?dagnn.Layer}, Hidden = true)
    numPendingVarRefs
    numPendingParamRefs
    computingDerivative = false
    executionOrder
  end

  properties (Transient, Access = private, Hidden = true)
    modifed = false
    varNames = struct()
    paramNames = struct()
    layerNames = struct()
    layerIndexes = {}
  end

  methods
    function obj = DagNN()
    %DAGNN  Initialize an empty DaG
    %   OBJ = DAGNN() initializes an empty DaG.
    %
    %   See Also addLayer(), loadobj(), saveobj().
      obj.vars = struct(...
        'name', {}, ...
        'value', {}, ...
        'der', {}, ...
        'fanin', {}, ...
        'fanout', {}, ...
        'precious', {}) ;
      obj.params = struct(...
        'name', {}, ...
        'value', {}, ...
        'der', {}, ...
        'fanout', {}, ...
        'trainMethod', {}, ...
        'learningRate', {}, ...
        'weightDecay', {}) ;
      obj.layers = struct(...
        'name', {}, ...
        'inputs', {}, ...
        'outputs', {}, ...
        'params', {}, ...
        'inputIndexes', {}, ...
        'outputIndexes', {}, ...
        'paramIndexes', {}, ...
        'forwardTime', {[]}, ...
        'backwardTime', {[]}, ...
        'block', {}) ;
    end

    function set.mode(obj, mode)
      switch lower(mode)
        case {'normal', 'train'}
          obj.mode = 'normal' ;
        case {'test'}
          obj.mode = 'test' ;
      end
    end

    % Manage the DagNN
    reset(obj)
    move(obj, direction)
    s = saveobj(obj)

    % Manipualte the DagNN
    addLayer(obj, name, block, inputs, outputs, params, varargin)
    removeLayer(obj, name)
    setLayerInputs(obj, leyer, inputs)
    setLayerOutput(obj, layer, outputs)
    setLayerParams(obj, layer, params)
    renameVar(obj, oldName, newName, varargin)
    rebuild(obj)

    % Process data with the DagNN
    initParams(obj)
    eval(obj, inputs, derOutputs)

    % Get information about the DagNN
    varSizes = getVarSizes(obj, inputSizes)

    % ---------------------------------------------------------------------
    %                                                           Access data
    % ---------------------------------------------------------------------

    function inputs = getInputs(obj)
    %GETINPUTS Get the names of the input variables
    %   INPUTS = GETINPUTS(obj) returns a cell array containing the name
    %   of the input variables of the DaG obj, i.e. the sources of the
    %   DaG (excluding the network parameters, which can also be
    %   considered sources).
      v = find([obj.vars.fanin]==0) ;
      inputs = {obj.vars(v).name} ;
    end

    function outputs = getOutputs(obj)
    %GETOUTPUTS Get the names of the output variables
    %    OUTPUT = GETOUTPUTS(obj) returns a cell array containing the name
    %    of the output variables of the DaG obj, i.e. the sinks of the
    %    DaG.
      v = find([obj.vars.fanout]==0) ;
      outputs = {obj.vars(v).name} ;
    end

    function l = getLayerIndex(obj, name)
    %GETLAYERINDEX Get the index of a layer
    %   INDEX = GETLAYERINDEX(obj, NAME) returns the index of the layer
    %   NAME. NAME can also be a cell array of strings. If no layer
    %   with such a name is found, the value NaN is returned for the
    %   index.
    %
    %   Layers can then be accessed as the `obj.layers(INDEX)`
    %   property of the DaG.
    %
    %   Indexes are stable unless the DaG is modified (e.g. by adding
    %   or removing layers); hence they can be cached for faster
    %   variable access.
    %
    %   See Also getParamIndex(), getVarIndex().
      if iscell(name)
        l = zeros(1, numel(name)) ;
        for k = 1:numel(name)
          l(k) = obj.getLayerIndex(name{k}) ;
        end
      else
        if isfield(obj.layerNames, name)
          l = obj.layerNames.(name) ;
        else
          l = NaN ;
        end
      end
    end

    function v = getVarIndex(obj, name)
    %GETVARINDEX Get the index of a variable
    %   INDEX = GETVARINDEX(obj, NAME) obtains the index of the variable
    %   with the specified NAME. NAME can also be a cell array of
    %   strings. If no variable with such a name is found, the value
    %   NaN is returned for the index.
    %
    %   Variables can then be accessed as the `obj.vars(INDEX)`
    %   property of the DaG.
    %
    %   Indexes are stable unless the DaG is modified (e.g. by adding
    %   or removing layers); hence they can be cached for faster
    %   variable access.
    %
    %   See Also getParamIndex(), getLayerIndex().
      if iscell(name)
        v = zeros(1, numel(name)) ;
        for k = 1:numel(name)
          v(k) = obj.getVarIndex(name{k}) ;
        end
      else
        if isfield(obj.varNames, name)
          v = obj.varNames.(name) ;
        else
          v = NaN ;
        end
      end
    end

    function p = getParamIndex(obj, name)
    %GETPARAMINDEX Get the index of a parameter
    %   INDEX = GETPARAMINDEX(obj, NAME) obtains the index of the
    %   parameter with the specified NAME. NAME can also be a cell
    %   array of strings. If no parameter with such a name is found,
    %   the value NaN is returned for the index.
    %
    %   Parameters can then be accessed as the `obj.params(INDEX)`
    %   property of the DaG.
    %
    %   Indexes are stable unless the DaG is modified (e.g. by adding
    %   or removing layers); hence they can be cached for faster
    %   parameter access.
    %
    %   See Also getVarIndex(), getLayerIndex().
      if iscell(name)
        p = zeros(1, numel(name)) ;
        for k = 1:numel(name)
          p(k) = obj.getParamIndex(name{k}) ;
        end
      else
        if isfield(obj.paramNames, name)
          p = obj.paramNames.(name) ;
        else
          p = NaN ;
        end
      end
    end

    function layer = getLayer(obj, layerName)
    %GETLAYER Get a copy of a layer definition
    %   LAYER = GETLAYER(obj, NAME) returns a copy of the layer definition
    %   structure with the specified NAME. NAME can also be a cell array
    %   of strings or an array of indexes. If no parameter with a
    %   specified name or index exists, an error is thrown.
    %
    %   See Also getLayerIndex().
      if isnumeric(layerName)
        idxs = layerName;
        if any(idxs > numel(obj.layers) || idxs < 0)
          error('Invalid layer indexes.');
        end
      else
        if ischar(layerName), layerName = {layerName}; end;
        idxs = obj.getLayerIndex(layerName);
        if any(isnan(idxs))
          error('Invalid layer name `%s`', ...
            strjoin(layerName(isnan(idxs)), ', '));
        end
      end
      layer = obj.layers(idxs);
    end

    function var = getVar(obj, varName)
    %GETVAR Get a copy of a layer definition
    %   VAR = GETVAR(obj, NAME) returns a copy of the network variable
    %   with the specified NAME. NAME can also be a cell array
    %   of strings or an array of indexes. If no variable with a
    %   specified name or index exists, an error is thrown.
    %
    %   See Also getVarIndex().
      if isnumeric(varName)
        idxs = varName;
        if any(idxs > numel(obj.vars) || idxs < 0)
          error('Invalid var indexes.');
        end
      else
        if ischar(varName), varName = {varName}; end;
        idxs = obj.getVarIndex(varName);
        if any(isnan(idxs))
          error('Invalid variable name `%s`', ...
            strjoin(varName(isnan(idxs)), ', '));
        end
      end
      var = obj.vars(idxs);
    end

    function param = getParam(obj, paramName)
    %GETPARAM Get a copy of a layer parameter
    %   PARAM = GETPARAM(obj, NAME) returns a copy of the network parameter
    %   with the specified NAME. NAME can also be a cell array
    %   of strings or an array of indexes. If no parameter with a
    %   specified name or index exists, an error is thrown.
    %
    %   See Also getParamIndex().
      if isnumeric(paramName)
        idxs = paramName;
        if any(idxs > numel(obj.params) || idxs < 0)
          error('Invalid param indexes.');
        end
      else
        if ischar(paramName), paramName = {paramName}; end;
        idxs = obj.getParamIndex(paramName);
        if any(isnan(idxs))
          error('Invalid param name `%s`', ...
            strjoin(paramName(isnan(idxs)), ', '));
        end
      end
      param = obj.params(idxs);
    end

    function order = getLayerExecutionOrder(obj)
    %GETLAYEREXECUTIONORDER Get the order in which layers are evaluated
    %   ORDER = GETLAYEREXECUTIONORDER(obj) returns a vector with
    %   the indexes of the layers in the order in which they are
    %   executed. This needs not to be the trivial order 1,2,...,L
    %   as it depends on the graph topology.
      order = obj.executionOrder ;
    end
  end

  methods (Static)
    obj = fromSimpleNN(net, varargin)
    obj = loadobj(s)
  end

  methods (Access = {?dagnn.DagNN, ?dagnn.Layer})
    function v = addVar(obj, name)
    %ADDVAR  Add a variable to the DaG
    %   V = ADDVAR(obj, NAME) adds a varialbe with the specified
    %   NAME to the DaG. This is an internal function; variables
    %   are automatically added when adding layers to the network.
      v = obj.getVarIndex(name) ;
      if ~isnan(v), return ; end
      v = numel(obj.vars) + 1 ;
      obj.vars(v) = struct(...
        'name', {name}, ...
        'value', {[]}, ...
        'der', {[]}, ...
        'fanin', {0}, ...
        'fanout', {0}, ...
        'precious', {false}) ;
      obj.varNames.(name) = v ;
    end

    function p = addParam(obj, name)
    %ADDPARAM  Add a parameter to the DaG
    %   V = ADDPARAM(obj, NAME) adds a parameter with the specified NAME
    %   to the DaG. This is an internal function; parameters are
    %   automatically added when adding layers to the network.
      p = obj.getParamIndex(name) ;
      if ~isnan(p), return ; end
      p = numel(obj.params) + 1 ;
      obj.params(p) = struct(...
        'name', {name}, ...
        'value', {[]}, ...
        'der', {[]}, ...
        'fanout', {0}, ...
        'trainMethod', {'gradient'}, ...
        'learningRate', {1}, ...
        'weightDecay', {1}) ;
      obj.paramNames.(name) = p ;
    end
  end

  methods (Access = protected)
    function cp = copyElement(obj)
      % Create a deep copy of the network
      cp = dagnn.DagNN.loadobj(obj.saveobj());
    end
  end
end


function [flag] = certify(mesh)
%CERTIFY error checking for JIGSAW mesh objects.

%-----------------------------------------------------------
%   Darren Engwirda
%   github.com/dengwirda/jigsaw-matlab
%   07-Aug-2019
%   darren.engwirda@columbia.edu
%-----------------------------------------------------------
%

    np = +0 ; flag = -1;

    if (~isstruct(mesh))
        error('certify:incorrectInputClass', ...
            'Incorrect input class.') ;
    end

    if (inspect(mesh,'point'))
        if (~isempty (mesh.point.coord))
        if (isnumeric(mesh.point.coord))
%----------------------------------------- check MESH coords
        np = size(mesh.point.coord,1) ;

        if (ndims(mesh.point.coord) ~= 2)
        error('certify:incorrectDimensions', ...
            'Invalid POINT.COORD dimensions.') ;
        end
        if ( size(mesh.point.coord,2)< 3)
        error('certify:incorrectDimensions', ...
            'Invalid POINT.COORD dimensions.') ;
        end

        if (any(isinf(mesh.point.coord)))
        error('certify:invalidMeshPosition', ...
            'Invalid POINT.COORD values.') ;
        end
        if (any(isnan(mesh.point.coord)))
        error('certify:invalidMeshPosition', ...
            'Invalid POINT.COORD values.') ;
        end

        if (isfield(mesh,'mshID'))
        if (strcmpi(mesh.mshID,'euclidean-grid'))
        error('certify:incompatiblemshID', ...
            'Incompatible msh-ID flag.') ;
        end
        if (strcmpi(mesh.mshID,'ellipsoid-grid'))
        error('certify:incompatiblemshID', ...
            'Incompatible msh-ID flag.') ;
        end
        end

        elseif(iscell(mesh.point.coord))
%----------------------------------------- check GRID coords
        if (~isvector(mesh.point.coord))
        error('certify:incorrectDimensions', ...
            'Invalid POINT.COORD dimensions.') ;
        end

        for ii = +1:length(mesh.point.coord)

        if (~isvector(mesh.point.coord{ii}))
        error('certify:incorrectDimensions', ...
            'Invalid POINT.COORD dimensions.') ;
        end
        if (any(isinf(mesh.point.coord{ii})))
        error('certify:invalidMeshPosition', ...
            'Invalid POINT.COORD values.') ;
        end
        if (any(isnan(mesh.point.coord{ii})))
        error('certify:invalidMeshPosition', ...
            'Invalid POINT.COORD values.') ;
        end

        end

        if (isfield(mesh,'mshID'))
        if (strcmpi(mesh.mshID,'euclidean-mesh'))
        error('certify:incompatiblemshID', ...
            'Incompatible msh-ID flag.') ;
        end
        if (strcmpi(mesh.mshID,'ellipsoid-mesh'))
        error('certify:incompatiblemshID', ...
            'Incompatible msh-ID flag.') ;
        end
        end

        else
%----------------------------------------- wrong POINT class
        error('certify:incorrectInputClass', ...
            'Invalid POINT.COORD type.') ;

        end
        end
    end

    if (inspect(mesh,'radii'))
%----------------------------------------- check RADII value
        if (~isempty  (mesh.radii))
        if (~isnumeric(mesh.radii))
        error('certify:incorrectInputClass', ...
            'Invalid RADII class.') ;
        end
        if (~isvector(mesh.radii))
        error('certify:incorrectDimensions', ...
            'Invalid RADII dimensions.') ;
        end
        if (length(mesh.radii) ~= +1 && ...
            length(mesh.radii) ~= +3 )
        error('certify:incorrectDimensions', ...
            'Invalid RADII dimensions.') ;
        end
        if (any(isinf(mesh.radii)))
        error('certify:invalidRadiiEntries', ...
            'Invalid RADII entries.') ;
        end
        if (any(isnan(mesh.radii)))
        error('certify:invalidRadiiEntries', ...
            'Invalid RADII entries.') ;
        end
        end
    end

    if (inspect(mesh,'value'))
%----------------------------------------- check VALUE value
        if (~isempty  (mesh.value))
        if (isnumeric(mesh.point.coord))
%----------------------------------------- for MESH obj kind

        if (ndims(mesh.value) ~= 2)
        error('certify:incorrectDimensions', ...
            'Invalid VALUE dimensions.') ;
        end
        if ( size(mesh.value,1) ~= np)
        error('certify:incorrectDimensions', ...
            'Invalid VALUE dimensions.') ;
        end

        if (any(isinf(mesh.value)))
        error('certify:invalidValueEntries', ...
            'Invalid VALUE entries.') ;
        end
        if (any(isnan(mesh.value)))
        error('certify:invalidValueEntries', ...
            'Invalid VALUE entries.') ;
        end

        elseif(iscell(mesh.point.coord))
%----------------------------------------- for GRID obj kind

        if (length(mesh.point.coord) ~= ...
            ndims (mesh.value))
        error('certify:incorrectDimensions', ...
            'Invalid VALUE dimensions.') ;
        end

        if (length(mesh.point.coord) == 2)

        if (isvector(mesh.value))
        if (length(mesh.point.coord{2}) ...
           *length(mesh.point.coord{1}) ...
            ~= numel(mesh.value))
        error('certify:incorrectDimensions', ...
            'Invalid VALUE dimensions.') ;
        end
        else
        if (length(mesh.point.coord{2}) ...
            ~= size(mesh.value,1) || ...
            length(mesh.point.coord{1}) ...
            ~= size(mesh.value,2) )
        error('certify:incorrectDimensions', ...
            'Invalid VALUE dimensions.') ;
        end
        end

        end

        if (length(mesh.point.coord) == 3)

        if (isvector(mesh.value))
        if (length(mesh.point.coord{2}) ...
           *length(mesh.point.coord{1}) ...
           *length(mesh.point.coord{3}) ...
            ~= numel(mesh.value))
        error('certify:incorrectDimensions', ...
            'Invalid VALUE dimensions.') ;
        end
        else
        if (length(mesh.point.coord{2}) ...
            ~= size(mesh.value,1) || ...
            length(mesh.point.coord{1}) ...
            ~= size(mesh.value,2) || ...
            length(mesh.point.coord{3}) ...
            ~= size(mesh.value,3) )
        error('certify:incorrectDimensions', ...
            'Invalid VALUE dimensions.') ;
        end
        end

        end

        if (any(isinf(mesh.value)))
        error('certify:invalidValueEntries', ...
            'Invalid VALUE entries.') ;
        end
        if (any(isnan(mesh.value)))
        error('certify:invalidValueEntries', ...
            'Invalid VALUE entries.') ;
        end

        else
%----------------------------------------- wrong VALUE class
        error('certify:incorrectInputClass', ...
            'Invalid VALUE class.') ;

        end
        end
    end

    if (inspect(mesh,'slope'))
%----------------------------------------- check SLOPE value
        if (~isempty  (mesh.slope))
        if (isnumeric(mesh.point.coord))
%----------------------------------------- for MESH obj kind

        if (ndims(mesh.slope) ~= 2)
        error('certify:incorrectDimensions', ...
            'Invalid SLOPE dimensions.') ;
        end
        if ( size(mesh.slope,1) ~= np)
        error('certify:incorrectDimensions', ...
            'Invalid SLOPE dimensions.') ;
        end

        if (any(isinf(mesh.slope)))
        error('certify:invalidValueEntries', ...
            'Invalid SLOPE entries.') ;
        end
        if (any(isnan(mesh.slope)))
        error('certify:invalidValueEntries', ...
            'Invalid SLOPE entries.') ;
        end

        elseif(iscell(mesh.point.coord))
%----------------------------------------- for GRID obj kind

        if (length(mesh.point.coord) ~= ...
            ndims (mesh.slope))
        error('certify:incorrectDimensions', ...
            'Invalid SLOPE dimensions.') ;
        end

        if (length(mesh.point.coord) == 2)

        if (isvector(mesh.slope))
        if (length(mesh.point.coord{2}) ...
           *length(mesh.point.coord{1}) ...
            ~= numel(mesh.slope))
        error('certify:incorrectDimensions', ...
            'Invalid SLOPE dimensions.') ;
        end
        else
        if (length(mesh.point.coord{2}) ...
            ~= size(mesh.slope,1) || ...
            length(mesh.point.coord{1}) ...
            ~= size(mesh.slope,2) )
        error('certify:incorrectDimensions', ...
            'Invalid SLOPE dimensions.') ;
        end
        end

        end

        if (length(mesh.point.coord) == 3)

        if (isvector(mesh.slope))
        if (length(mesh.point.coord{2}) ...
           *length(mesh.point.coord{1}) ...
           *length(mesh.point.coord{3}) ...
            ~= numel(mesh.slope))
        error('certify:incorrectDimensions', ...
            'Invalid SLOPE dimensions.') ;
        end
        else
        if (length(mesh.point.coord{2}) ...
            ~= size(mesh.slope,1) || ...
            length(mesh.point.coord{1}) ...
            ~= size(mesh.slope,2) || ...
            length(mesh.point.coord{3}) ...
            ~= size(mesh.slope,3) )
        error('certify:incorrectDimensions', ...
            'Invalid SLOPE dimensions.') ;
        end
        end

        end

        if (any(isinf(mesh.slope)))
        error('certify:invalidValueEntries', ...
            'Invalid SLOPE entries.') ;
        end
        if (any(isnan(mesh.slope)))
        error('certify:invalidValueEntries', ...
            'Invalid SLOPE entries.') ;
        end

        else
%----------------------------------------- wrong VALUE class
        error('certify:incorrectInputClass', ...
            'Invalid SLOPE class.') ;

        end
        end
    end

    if (inspect(mesh,'edge2'))
%----------------------------------------- check EDGE2 index
        if (~isempty  (mesh.edge2.index))
        if (~isnumeric(mesh.edge2.index))
        error('certify:incorrectInputClass', ...
            'Invalid EDGE2.INDEX type.') ;
        end
        if (ndims(mesh.edge2.index) ~= 2)
        error('certify:incorrectDimensions', ...
            'Invalid EDGE2.INDEX dimensions.') ;
        end
        if (size(mesh.edge2.index,2)~= 3)
        error('certify:incorrectDimensions', ...
            'Invalid EDGE2.INDEX dimensions.') ;
        end
        if (any(isinf(mesh.edge2.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid EDGE2.INDEX indexing.') ;
        end
        if (any(isnan(mesh.edge2.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid EDGE2.INDEX indexing.') ;
        end
        if (min(min( ...
        mesh.edge2.index(:,1:2))) < +1 || ...
            max(max( ...
        mesh.edge2.index(:,1:2))) > np  )
        error('certify:invalidMeshIndexing', ...
            'Invalid EDGE2.INDEX indexing.') ;
        end
        end
    end

    if (inspect(mesh,'tria3'))
%----------------------------------------- check TRIA3 index
        if (~isempty  (mesh.tria3.index))
        if (~isnumeric(mesh.tria3.index))
        error('certify:incorrectInputClass', ...
            'Invalid TRIA3.INDEX type.') ;
        end
        if (ndims(mesh.tria3.index) ~= 2)
        error('certify:incorrectDimensions', ...
            'Invalid TRIA3.INDEX dimensions.') ;
        end
        if (size(mesh.tria3.index,2)~= 4)
        error('certify:incorrectDimensions', ...
            'Invalid TRIA3.INDEX dimensions.') ;
        end
        if (any(isinf(mesh.tria3.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid TRIA3.INDEX indexing.') ;
        end
        if (any(isnan(mesh.tria3.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid TRIA3.INDEX indexing.') ;
        end
        if (min(min( ...
        mesh.tria3.index(:,1:3))) < +1 || ...
            max(max( ...
        mesh.tria3.index(:,1:3))) > np  )
        error('certify:invalidMeshIndexing', ...
            'Invalid TRIA3.INDEX indexing.') ;
        end
        end
    end

    if (inspect(mesh,'quad4'))
%----------------------------------------- check QUAD4 index
        if (~isempty  (mesh.quad4.index))
        if (~isnumeric(mesh.quad4.index))
        error('certify:incorrectInputClass', ...
            'Invalid QUAD4.INDEX type.') ;
        end
        if (ndims(mesh.quad4.index) ~= 2)
        error('certify:incorrectDimensions', ...
            'Invalid QUAD4.INDEX dimensions.') ;
        end
        if (size(mesh.quad4.index,2)~= 5)
        error('certify:incorrectDimensions', ...
            'Invalid QUAD4.INDEX dimensions.') ;
        end
        if (any(isinf(mesh.quad4.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid QUAD4.INDEX indexing.') ;
        end
        if (any(isnan(mesh.quad4.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid QUAD4.INDEX indexing.') ;
        end
        if (min(min( ...
        mesh.quad4.index(:,1:4))) < +1 || ...
            max(max( ...
        mesh.quad4.index(:,1:4))) > np  )
        error('certify:invalidMeshIndexing', ...
            'Invalid QUAD4.INDEX indexing.') ;
        end
        end
    end

    if (inspect(mesh,'tria4'))
%----------------------------------------- check TRIA4 index
        if (~isempty  (mesh.tria4.index))
        if (~isnumeric(mesh.tria4.index))
        error('certify:incorrectInputClass', ...
            'Invalid TRIA4.INDEX type.') ;
        end
        if (ndims(mesh.tria4.index) ~= 2)
        error('certify:incorrectDimensions', ...
            'Invalid TRIA4.INDEX dimensions.') ;
        end
        if (size(mesh.tria4.index,2)~= 5)
        error('certify:incorrectDimensions', ...
            'Invalid TRIA4.INDEX dimensions.') ;
        end
        if (any(isinf(mesh.tria4.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid TRIA4.INDEX indexing.') ;
        end
        if (any(isnan(mesh.tria4.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid TRIA4.INDEX indexing.') ;
        end
        if (min(min( ...
        mesh.tria4.index(:,1:4))) < +1 || ...
            max(max( ...
        mesh.tria4.index(:,1:4))) > np  )
        error('certify:invalidMeshIndexing', ...
            'Invalid TRIA4.INDEX indexing.') ;
        end
        end
    end

    if (inspect(mesh,'hexa8'))
%----------------------------------------- check HEXA8 index
        if (~isempty  (mesh.hexa8.index))
        if (~isnumeric(mesh.hexa8.index))
        error('certify:incorrectInputClass', ...
            'Invalid HEXA8.INDEX type.') ;
        end
        if (ndims(mesh.hexa8.index) ~= 2)
        error('certify:incorrectDimensions', ...
            'Invalid HEXA8.INDEX dimensions.') ;
        end
        if (size(mesh.hexa8.index,2)~= 9)
        error('certify:incorrectDimensions', ...
            'Invalid HEXA8.INDEX dimensions.') ;
        end
        if (any(isinf(mesh.hexa8.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid HEXA8.INDEX indexing.') ;
        end
        if (any(isnan(mesh.hexa8.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid HEXA8.INDEX indexing.') ;
        end
        if (min(min( ...
        mesh.hexa8.index(:,1:8))) < +1 || ...
            max(max( ...
        mesh.hexa8.index(:,1:8))) > np  )
        error('certify:invalidMeshIndexing', ...
            'Invalid HEXA8.INDEX indexing.') ;
        end
        end
    end

    if (inspect(mesh,'wedg6'))
%----------------------------------------- check WEDG6 index
        if (~isempty  (mesh.wedg6.index))
        if (~isnumeric(mesh.wedg6.index))
        error('certify:incorrectInputClass', ...
            'Invalid WEDG6.INDEX type.') ;
        end
        if (ndims(mesh.wedg6.index) ~= 2)
        error('certify:incorrectDimensions', ...
            'Invalid WEDG6.INDEX dimensions.') ;
        end
        if (size(mesh.wedg6.index,2)~= 7)
        error('certify:incorrectDimensions', ...
            'Invalid WEDG6.INDEX dimensions.') ;
        end
        if (any(isinf(mesh.wedg6.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid WEDG6.INDEX indexing.') ;
        end
        if (any(isnan(mesh.wedg6.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid WEDG6.INDEX indexing.') ;
        end
        if (min(min( ...
        mesh.wedg6.index(:,1:6))) < +1 || ...
            max(max( ...
        mesh.wedg6.index(:,1:6))) > np  )
        error('certify:invalidMeshIndexing', ...
            'Invalid WEDG6.INDEX indexing.') ;
        end
        end
    end

    if (inspect(mesh,'pyra5'))
%----------------------------------------- check PYRA5 index
        if (~isempty  (mesh.pyra5.index))
        if (~isnumeric(mesh.pyra5.index))
        error('certify:incorrectInputClass', ...
            'Invalid PYRA5.INDEX type.') ;
        end
        if (ndims(mesh.pyra5.index) ~= 2)
        error('certify:incorrectDimensions', ...
            'Invalid PYRA5.INDEX dimensions.') ;
        end
        if (size(mesh.pyra5.index,2)~= 6)
        error('certify:incorrectDimensions', ...
            'Invalid PYRA5.INDEX dimensions.') ;
        end
        if (any(isinf(mesh.pyra5.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid PYRA5.INDEX indexing.') ;
        end
        if (any(isnan(mesh.pyra5.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid PYRA5.INDEX indexing.') ;
        end
        if (min(min( ...
        mesh.pyra5.index(:,1:5))) < +1 || ...
            max(max( ...
        mesh.pyra5.index(:,1:5))) > np  )
        error('certify:invalidMeshIndexing', ...
            'Invalid PYRA5.INDEX indexing.') ;
        end
        end
    end

    if (inspect(mesh,'bound'))
%----------------------------------------- check BOUND index
        if (~isempty  (mesh.bound.index))
        if (~isnumeric(mesh.bound.index))
        error('certify:incorrectInputClass', ...
            'Invalid BOUND.INDEX type.') ;
        end
        if (ndims(mesh.bound.index) ~= 2)
        error('certify:incorrectDimensions', ...
            'Invalid BOUND.INDEX dimensions.') ;
        end
        if (size(mesh.bound.index,2)~= 3)
        error('certify:incorrectDimensions', ...
            'Invalid BOUND.INDEX dimensions.') ;
        end
        if (any(isinf(mesh.bound.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid BOUND.INDEX indexing.') ;
        end
        if (any(isnan(mesh.bound.index)))
        error('certify:invalidMeshIndexing', ...
            'Invalid BOUND.INDEX indexing.') ;
        end
        if (min(min( ...
            mesh.bound.index(:,2:2))) < +1 )
        error('certify:invalidMeshIndexing', ...
            'Invalid BOUND.INDEX indexing.') ;
        end
        end
    end

%----------------------------------------- ok if we get here
    flag = +1 ;

end


function [model,entriesNotAdded] = addFoodRxns2HM(model,foodRxns)
% Takes a set of food reactions to a whole-body metabolic model
%
% USAGE
%
%   Format:    [model,entriesNotAdded] = addFoodRxns2HM(model,foodRxns)
%   Example 1: [model,entriesNotAdded] = addFoodRxns2HM(model,'All Food')
%   Example 2: [model,entriesNotAdded] = addFoodRxns2HM(model,{'Food_EX_AvocadoSalad[d]','Food_EX_BakerPotatoes[d]'})

% INPUTS
%
%   model:     a whole-body metabolic model
%   foodRxns:  a set of instructions to tell the function which food
%               reactions should be added to the model. Valid foodRxns
%               entries are as follows:
%                   - 'All' : adds all food reactions in the vmh database
%                   including food category reactions
%                   - 'All Food': adds all food reactions in the vmh
%                   database but does not include food category reactions
%                   - 'All Cat': adds all food category reactions to the
%                   model
%                   - A cellular array containing a list of food
%                   reactions to include in the model.
%
% OUTPUTS
%
%   model:          returns an updated model with the desired food reactions
%   entriesNotAdded:    a list of reactions that were not in the vmh database
%                       and therefore were not added to the model
%
% AUTHORS
%   Bronson R. Weston 2022

% Set up foodItems array
load('fdTable.mat')
load('fdCategoriesTable.mat')
if ischar(foodRxns)
    if strcmp(foodRxns,'All')
        foodRxns=[fdTable.Properties.VariableNames(2:end),fdCategoriesTable.Properties.VariableNames(2:end)];
        foodRxns=strcat('Food_EX_',foodRxns);
        foodRxns=strcat(foodRxns,'[d]');
    elseif strcmp(foodRxns,'All Food')
        foodRxns=fdTable.Properties.VariableNames(2:end);
        foodRxns=strcat('Food_EX_',foodRxns);
        foodRxns=strcat(foodRxns,'[d]');
    elseif strcmp(foodRxns,'All Cat')
        foodRxns=fdCategoriesTable.Properties.VariableNames(2:end);
        foodRxns=strcat('Food_EX_',foodRxns);
        foodRxns=strcat(foodRxns,'[d]');
    else
        foodRxns={foodRxns};
    end
else
    foodRxns=foodRxns;
end
%Combine fdTable and fdCategoriesTable and make necessary adjustements
fdCategoriesTable.Var1=[];
foodTable=[fdTable,fdCategoriesTable];
pro_Dindex=find(contains(foodTable.Var1,'pro_D')); %for now, remove pro_D from table.
foodTable(pro_Dindex,:)=[];
EnergyIndex=find(contains(foodTable.Var1,'Energy in Kcal'));
foodTable.Var1(EnergyIndex)={'Energy_in_Kcal'};

% Remove food items that already have reactions present in model
[~,~,ib] = intersect(model.rxns,foodRxns);
foodRxns(ib)= [];

%Add 'Energy_in_Kcal[d]' metabolite if not present
if ~any(strcmp(model.mets,'Energy_in_Kcal[d]'))
    model=addMetabolite(model, 'Energy_in_Kcal[d]');
    model = addMultipleReactions(model, {'EX_DietEnergy'}, {'Energy_in_Kcal[d]'}, [-1], 'lb', 0, 'ub', 100000);
end

% Identify food reactions to add
augFoodRxns=regexprep(foodRxns,'Food_EX_','');
augFoodRxns=regexprep(augFoodRxns,'\[d\]','');

[~,ia,ib] = intersect(foodTable.Properties.VariableNames,augFoodRxns);
entriesNotAdded=foodRxns(setdiff(1:length(foodRxns),ib));
foodRxns=foodRxns(ib);

%Add all food category reactions
foodMetabolites= foodTable.Var1.';
foodMetabolites= strcat(foodMetabolites,'[d]');
sMatrix=-1*table2array(foodTable(:,ia));
model = addMultipleReactions(model, foodRxns, foodMetabolites, sMatrix, 'lb', zeros(1,length(foodRxns)), 'ub', zeros(1,length(foodRxns)));
end


function GCO_SetLabelOrder(Handle,Order)
% GCO_SetLabelOrder   Set label order for Expansion/Swap moves.
%   GCO_SetLabelOrder(Handle,Order) tells Expansion/Swap to select labels
%   in a specific order when Order contains integers from 1..NumLabels.
%   By default, Expansion/Swap use a consistent, prescribed order of labels
%   in until convergence.
%   Example:
%      GCO_SetLabelOrder(Handle,5:10); % only operate on labels 5..10
%      GCO_SetLabelOrder(Handle,randperm(NumLabels)); % random label order
%

GCO_LoadLib();
gco_matlab('gco_setlabelorder',Handle,int32(Order));
end


function [value, varname] = in_matlab_var(varname, vartype)
% IN_MATLAB_VAR: Reads the contents of a Matlab variable in base workspace
%
% USAGE:  value = in_matlab_var(varname, vartype)
%         value = in_matlab_var(varname, 'numeric')
%         value = in_matlab_var(varname)
%         value = in_matlab_var()        : Ask the variable name to the user

% @=============================================================================
% 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, 2009-2015

value = [];
% Variable type not specified
if (nargin < 2) || isempty(vartype)
    vartype = [];
end
% Ask user the name of the destination variable
if (nargin < 1) || isempty(varname)
    % Get the list of all the base workspace variables (structures only)
    allVars = evalin('base', 'whos');
    if isempty(allVars)
        bst_error('No variables in the base workspace.', 'Import from Matlab', 0);
        return;
    end
    % If variable type not specified, show all variables
    if isempty(vartype)
        listVar = {allVars.name};
    elseif isequal(vartype, 'numeric')
        iVar = find(ismember({allVars.class}, {'uint8','int8','uint16','int16','uint32','int32','uint64','int64','single','double'}));
        listVar = {allVars(iVar).name};
    else
        iVar = find(strcmpi({allVars.class}, vartype));
        listVar = {allVars(iVar).name};
    end
    % Check again that there are variables availables
    if isempty(listVar)
        bst_error('No valid variables in the base workspace.', 'Import from Matlab', 0);
        return;
    end
    % Show question
    varname = java_dialog('combo', 'Select a workspace variable:

', 'Import from Matlab', [], listVar);
    if isempty(varname)
        return
    end
end
% Get variable value
try
    value = evalin('base', varname);
catch
    bst_error(['Variable "' varname '" does not exist in Matlab workspace.'], 'Import from workspace', 0);
    value = [];
end


function nfgCompareProjectomeToGold(phantomDir, projType, bWriteBundledProj, bDoTrueError, bDoArclength)
%Compare projectome with gold standard.
%
%   nfgCompareProjectomeToGold(phantomDir, projType, bWriteBundledProj)
%
% This loads the gold standard pathways and bundle info.  For each bundle,
% the corresponding pathways are found in the projectome solution and the
% volume of the gold standard and the projectome estimate bundles are
% calculated.
%
% Input the projectome type is a cell array and can be any or all of
%   ('stt', 'stt_bm', 'ctr_bm', 'trk', or 'gold').
%
% NOTES:

if ieNotDefined('bWriteBundledProj'); bWriteBundledProj=0; end
if isempty(projType) || ~iscellstr(projType)
    error('Must provide cell array of projectome types to compare!');
end

% Directories
fiberDir = nfgGetName('fiberDir',phantomDir);
% Input Files
volExFile = nfgGetName('volExFile',phantomDir);
goldPDBFile = nfgGetName('goldPDBFile',phantomDir);
goldInfoFile = nfgGetName('goldInfoFile',phantomDir);
projFile={};
for pp=1:length(projType)
    projFile{pp} = nfgGetName([projType{pp},'PDBFile'],phantomDir); %#ok<AGROW>
end
% Output
bundleOutFile = fullfile(fiberDir,'gold');

% Validate existence of directories
if ~isdir(fiberDir)
    error('Must provide a valid path to an NFG simulation phantom!');
end

% What types of comparisons should we do?
if ieNotDefined('bDoTrueError'); bDoTrueError = 0; end
if ieNotDefined('bDoArclength'); bDoArclength = 1; end
if ieNotDefined('bDoFiberCount'); bDoFiberCount = 0; end

% Get bundle ID and radius of each gold pathway from info file
gi = load(goldInfoFile);
g_bundleID = zeros(1,length(gi.strand_info));
g_radius = zeros(1,length(gi.strand_info));
for ii=1:length(gi.strand_info)
    g_bundleID(ii) = gi.strand_info(ii).bundleID+1;
    g_radius(ii) = gi.strand_info(ii).radius;
end

% XXX For testing
%g_bundleID(g_bundleID>30) = -1;

% Set the distance threshold to be equal to the diameter of the gold fiber
% bundle + the voxel resolution.  Ideally we would like the distance to be the radius of the gold
% plus the radius of the projectome, but we don't know the radii for all
% projectome estimates
vol = niftiRead(volExFile);
dThresh = max(vol.pixdim) + 2*max(g_radius);

% Compare Gold to the Projectome
disp('Importing gold projectome ...');
fgG = mtrImportFibers(goldPDBFile);
% Prepare output file
fg = dtiNewFiberGroup();
fg.fibers = fgG.fibers;
fgAllNonSel = dtiNewFiberGroup();
iAllNonSelGroup = [];
iGroupStat = zeros(1,length(fgG.fibers));
iBundleStat = g_bundleID;
% Radii to search through to compare projectomes
vR = 0.005:0.0005:0.07;
% For common summary info
fpCount = zeros(1,length(projFile));
tpCount = zeros(1,length(projFile));
% For arclength calculations
alE = zeros(length(projFile),max(g_bundleID));
fvol = zeros(length(projFile),max(g_bundleID));
fvolG = zeros(1,max(g_bundleID));
% For trueerror calculations
teE = zeros(length(projFile),max(g_bundleID));
teW = zeros(1,max(g_bundleID));
% For normalized fiber count comparison
%nfc = zeros(length(projFile),max(g_bundleID));
%nfcG = nfgGetNormalizeFiberCount(g_bundleID,g_bundleID);
numBundlesGold = sum(unique(g_bundleID)>0);
numFibersGold = sum(g_bundleID>0);
vID = {};
for pp=1:length(projFile)
    % Get the projectome to test
    disp(' ');disp(['Importing test projectome ' projFile{pp} ' ...']);
    fgProj = mtrImportFibers(projFile{pp});
    % Match the projectome fibers to bundles in the gold set
    [fibersSel, fibersNonSel, iBundleSel] = nfgMatchProjectome(fgG,g_bundleID,fgProj,dThresh);
    % False positives
    fpCount(pp) = length(fibersNonSel) / (length(fibersNonSel) + length(fibersSel)) * 100;
    % True positives
    tpCount(pp) = sum(unique(iBundleSel)>0) / numBundlesGold * 100;

    % Calculate the volume estimate of the test fibers compared with gold
    if bDoTrueError
        [teE(pp,:), teW] = nfgCompareAllVolumeTrueError(fgG,g_bundleID,g_radius,fibersSel,iBundleSel,phantomDir);
    end
    if bDoArclength
        %[alE(pp,:)] = nfgCompareAllVolumeArclength(fgG,g_bundleID,g_radius,vR,fibersSel,fibersNonSel,iBundleSel);
        bApplyHagmannW = 0;
        if strcmp(projType{pp},'trk')
            [fvol(pp,:), fvolG, fvolNon] = nfgCompareAllVolumeArclength(fgG,g_bundleID,g_radius,vR,fibersSel,fibersNonSel,iBundleSel,bApplyHagmannW);
        else
            [fvol(pp,:), fvolG, fvolNon] = nfgCompareAllVolumeArclength(fgG,g_bundleID,g_radius,vR,fibersSel,fibersNonSel,iBundleSel,0);
        end
    end
    if bDoFiberCount
        vID{pp} = iBundleSel; %#ok<AGROW>
        %nfc(pp,:) = nfgGetNormalizeFiberCount(iBundleSel,g_bundleID);
    end

    % Preparing for output pdb
    nSel = length(fibersSel);
    nNonSel = length(fibersNonSel);
    fg.fibers(end+1:end+nSel) = fibersSel;
    fg.fibers(end+1:end+nNonSel) = fibersNonSel;
    iGroupStat(end+1:end+nSel) = pp;
    iGroupStat(end+1:end+nNonSel) = pp;
    %fgAllNonSel.fibers(end+1:end+nNonSel) = fibersNonSel;
    %iAllNonSelGroup(end+1:end_nSel) = pp;
    iBundleStat(end+1:end+nSel) = iBundleSel;
    iBundleStat(end+1:end+nNonSel) = -1;
    [foo, projName] = fileparts(projFile{pp});
    bundleOutFile = [bundleOutFile '-' projName]; %#ok<AGROW>
end

[nfc, nfcG] = nfgGetNormalizeFiberCount(vID,g_bundleID);

% Plotting and Reporting
summary_info = {};

if bDoTrueError
    % First plot the unweighted trueerror volume error
    figure;
    bar(teE','group');
    xlabel('Bundle ID');
    ylabel('Within Bundle Volume Error (% compared to bundle volume)');
    title('Unnormalized trueError Estimation');
    legend(projType);

    % Second plot trueerror volume error normalized by total volume
    figure;
    teNE = teE.*repmat(teW/sum(teW),size(teE,1),1);
    bar(teNE','group');
    xlabel('Bundle ID');
    ylabel('Within Bundle Volume Error (% compared to total volume)');
    title('Normalized trueError Estimation');
    legend(projType);
end

if bDoFiberCount
    % Do plots by sorting NFC Gold
    [foo, iSort] = sort(nfcG,'descend');
    % First plot the normalized fiber counts
    figure;
    bar([nfcG(iSort)*numFibersGold; nfc(:,iSort)*numFibersGold]','group');
    xlabel('Bundle ID');
    ylabel('Number of fibers compared to smallest');
    title('Normalized Fiber Count');
    legend(['Gold', projType(:)']);

    % Second plot the normalized fiber count error
    figure;
    nfcE = abs(repmat(nfcG*numFibersGold,size(nfc,1),1)-nfc*numFibersGold);
    bar(nfcE(:,iSort)','group');
    xlabel('Bundle ID');
    ylabel('Difference betweeen NFC and NFC Gold');
    title('Normalized Fiber Count Error');
    legend(projType);
    strE = 'NFC Error divided by number of bundles: ';
    for pp=1:length(projType)
        strE = [strE num2str(sum(nfcE(pp,:))/numBundlesGold) ' (' projType{pp} ') ']; %#ok<AGROW>
    end
    summary_info{end+1} = strE;
end

if bDoArclength
    % Do plots by sorting NFC Gold
    [foo, iSort] = sort(fvolG,'descend');
    % First plot the normalized fiber counts
    figure;
    bar([fvolG(iSort)*100; fvol(:,iSort)*100]','group');
    xlabel('Bundle ID');
    ylabel('Bundle Volume Percent of Total');
    title('Normalized Fiber Volume');
    legend(['Gold', projType(:)']);

    % Second plot the normalized fiber count error
%     figure;
     fvolE = abs(repmat(fvolG,size(fvol,1),1)-fvol)*100;
%     bar(nfcE(:,iSort)','group');
%     xlabel('Bundle ID');
%     ylabel('Difference betweeen NFC and NFC Gold');
%     title('Normalized Fiber Count Error');
%     legend(projType);
    strE = 'Max/Sum Volume Error Percentage: ';
    for pp=1:length(projType)
        strE = [strE num2str(max(fvolE(pp,:))) '/' num2str(sum(fvolE(pp,:))) ' (' projType{pp} ') ']; %#ok<AGROW>
    end
    summary_info{end+1} = strE;
end

% False positive report
strFPs = 'False Positives: ';
for pp=1:length(projFile)
    strFPs = [strFPs num2str(fpCount(pp)) '% (' projType{pp} ') ']; %#ok<AGROW>
end
summary_info{end+1} = strFPs;
% True positive report
strTPs = 'True Positives: ';
for pp=1:length(projFile)
    strTPs = [strTPs num2str(tpCount(pp)) '% (' projType{pp} ') ']; %#ok<AGROW>
end
summary_info{end+1} = strTPs;

% Write out full report
disp(' '); disp('Summary Info');
for ii=1:length(summary_info)
    disp(summary_info{ii});
end

% Write out pathway file that combines bundled gold and bundled projectome
if bWriteBundledProj
    fg = dtiClearQuenchStats(fg);
    fg = dtiCreateQuenchStats(fg,'Bundle ID',iBundleStat);
    fg = dtiCreateQuenchStats(fg,'Group',iGroupStat);
    % Need to put the extension on the file
    bundleOutFile = [bundleOutFile '.pdb'];
    mtrExportFibers(fg,bundleOutFile);
end

return;

% Color wheel
% vC = {'b','g','r','c','m','y','k'};

%
%     % Calculate normalized true error volume error and store mean
%     teNE = teE.*teW/sum(teW);
%     pE1W(pp) = mean(teNE(teE<100 & teE>0));
%     % Store unnormalized volume error as well
%     pE1(pp) = mean(teE(teE<100 & teE>0));
%
%     % Plotting and Reporting Error
%     percentE = sum(abs(E),2)/sum(volG)*100;
%     %figure(100);
%     %plot(vR,percentE,vC{pp}); hold on;
%     figure(100);
%     teNE = teE.*teW/sum(teW);
%     plot(teNE,vC{pp}); hold on;
%     pE1W(pp) = mean(teNE(teE<100 & teE>0));
%     figure(101);
%     plot(teE,vC{pp}); hold on;
%     pE1(pp) = mean(teE(teE<100 & teE>0));
%     [minE, minI] = min(percentE);
%     disp(['Minimum within bundle error of ' num2str(minE) '% found with radius ' num2str(vR(minI))]);
%     percentENonBundle = volPNonBundle * vR(minI)^2 / sum(volG) * 100;
%
%     %disp([projName ' non-bundle error is ' num2str(percentENonBundle) '%']);
%     pE2(pp) = 100*length(fibersNonSel)/(length(fibersNonSel)+length(fibersSel));
%     %disp([projName ' non-bundle error is ' num2str(100*length(fibersNonSel)/(length(fibersNonSel)+length(fibersSel))) '%.']);
%


function [ y, ierror ] = y_check_bahai ( y )

%*****************************************************************************80
%
%% Y_CHECK_BAHAI checks a Bahai year.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    06 March 2013
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input/output, integer Y, the year, which must not be 0.
%
%    Output, integer IERROR, is 0 if Y is legal, and 1 otherwise.
%
  if ( y <= 0 )
    ierror = 1;
  else
    ierror = 0;
  end

  return
end


function [idx, new_mpc, new_xgd, new_sd] = addstorage(storage, mpc, xgd, sd)
%ADDWIND   Adds storage units and corresponding xGen/StorageData to existing data.
%
%   [IDX, NEW_MPC] = ADDSTORAGE(STORAGE, MPC)
%   [IDX, NEW_MPC, NEW_XGD, NEW_SD] = ADDSTORAGE(STORAGE, MPC)
%   [IDX, NEW_MPC, NEW_XGD, NEW_SD] = ADDSTORAGE(STORAGE, MPC, XGD)
%   [IDX, NEW_MPC, NEW_XGD, NEW_SD] = ADDSTORAGE(STORAGE, MPC, XGD, SD)
%
%   Given a StorageUnitData structure, or the name of a file containing such
%   a structure, this function adds the specified storage generators to an
%   existing MATPOWER case, xGenData struct and StorageData struct.
%
%   Inputs:
%       STORAGE : a StorageUnitData struct or the name of an M-file
%                   or MAT-file that returns one, with the following fields
%           .gen     : rows to be appended to the GEN matrix from MPC
%           .gencost : (optional) rows to be added to the GENCOST matrix
%                      from MPC, default is zero cost
%           .xgd_table : xGenData table struct or filename providing data for
%                        the storage units being added. See LOADSTORAGEDATA
%                        for more information on the xGenData table format.
%           .sd_table : StorageData table struct or filename providing data
%                       for the storage units being added. See LOADSTORAGEDATA
%                       for more information on the StorageData table format.
%       MPC : MATPOWER case struct to which storage generators will be added
%       XGD : (optional) xGenData struct corresponding to the generators
%             already in MPC, to which the new xGenData for the storage
%             units will be added.
%       SD : (optional) StorageData struct corresponding to the generators
%            already in MPC, to which the new StorageData for the storage
%            units will be added.
%
%   Output:
%       IDX : Generator indices of newly added storage units.
%       NEW_MPC : MPC with storage units appended to MPC.GEN and MPC.GENCOST
%                 MPC.GENFUEL (= 'ess').
%       NEW_XGD : XGD with xGenData for new storage units appended.
%       NEW_SD  : SD with StorageData for new storage units appended.
%
%   See also LOADSTORAGEDATA, LOADXGENDATA.

%   MOST
%   Copyright (c) 2013-2016, Power Systems Engineering Research Center (PSERC)
%   by Ray Zimmerman, PSERC Cornell
%
%   This file is part of MOST.
%   Covered by the 3-clause BSD License (see LICENSE file for details).
%   See https://github.com/MATPOWER/most for more info.

%% define named indices into data matrices
[PW_LINEAR, POLYNOMIAL, MODEL, STARTUP, SHUTDOWN, NCOST, COST] = idx_cost;

%% define fuel type for storage
STORAGE_FUEL = 'ess';

%% input arg handling
if nargin < 4
    sd = [];
    if nargin < 3
        xgd = [];
    end
end
if ischar(storage)
    infile = sprintf(' in file: ''%s''', storage);
    storage = loadgenericdata(storage, 'struct', 'gen', 'storage');
else
    infile = '';
end

%% add to MPC
ns = size(storage.gen, 1);          %% number of storage units being added
if isfield(storage, 'gencost')
    storage_gencost = storage.gencost;
else        %% use zero cost by default
    storage_gencost = repmat([POLYNOMIAL 0 0 2 0 0], ns, 1);
end
[new_mpc, idx] = addgen2mpc(mpc, storage.gen, storage_gencost, STORAGE_FUEL);

%% handle xGenData and StorageData
if nargout > 2      %% output NEW_XGD, NEW_SD requested
    %% xGenData
    if isfield(storage, 'xgd_table')
        storage_xgd = loadxgendata(storage.xgd_table, storage.gen);
    else
        error('addstorage: missing XGD_TABLE field in STORAGE');
    end

    if isempty(xgd)     %% no input XGD provided
        new_xgd = storage_xgd;
    else                %% input XGD provided
        %% append rows of every field in xgd
        new_xgd = xgd;
        fields = fieldnames(xgd);
        for f = 1:length(fields)
            ff = fields{f};
            %% dims of wind_xgd fields already checked by loadxgendata
            if size(xgd.(ff), 1) ~= size(mpc.gen, 1)
                error('addstorage: # of rows in XGD.%s (%d) does not match MPC.GEN (%d)', ...
                    ff, size(xgd.(ff), 1), size(mpc.gen, 1));
            end
            new_xgd.(ff) = [xgd.(ff); storage_xgd.(ff)];
        end
    end

    %% StorageData
    if isfield(storage, 'sd_table')
        storage_sd = loadstoragedata(storage.sd_table, storage.gen);
    else
        error('addstorage: missing SD_TABLE field in STORAGE');
    end

    storage_sd.UnitIdx = idx;       %% add UnitIdx field

    if isempty(sd)     %% no input SD provided
        new_sd = storage_sd;
    else                %% input SD provided
        %% find number of storage units in original mpc
        ns0 = length(find(strcmp(mpc.genfuel, STORAGE_FUEL)));
        new_sd = sd;
        fields = fieldnames(sd);
        for f = 1:length(fields)    %% append rows of every field in sd
            ff = fields{f};
            add_ns  = size(storage_sd.(ff), 1);
            orig_ns = size(        sd.(ff), 1);
            if orig_ns ~= 1 || add_ns ~= 1 || sd.(ff) ~= storage_sd.(ff)
                %% at least one of them is not 1-d or else they don't match
                if orig_ns == 1     %% original is 1-d
                    %% expand it
                    sd.(ff) = ones(ns0, 1) * sd.(ff);
                end
                if add_ns == 1      %% new is 1-d
                    %% expand it
                    storage_sd.(ff) = ones(ns, 1) * storage_sd.(ff);
                end
                %% concatenate them
                new_sd.(ff) = [sd.(ff); storage_sd.(ff)];
            % else (both are 1-d and they match)
            %   do nothing, just use old sd.(ff)
            end
        end
    end
end


function dat = read_egis_data(filename, hdr, begtrial, endtrial, chanindx);

% READ_EGIS_DATA reads the data from an EGI EGIS format file
%
% Use as
%   dat = read_egis_data(filename, hdr, begtrial, endtrial, chanindx);
% where
%   filename       name of the input file
%   hdr            header structure, see READ_HEADER
%   begtrial       first trial to read, mutually exclusive with begsample+endsample
%   endtrial       last trial to read,  mutually exclusive with begsample+endsample
%   chanindx       list with channel indices to read
%
% This function returns a 3-D matrix of size Nchans*Nsamples*Ntrials.
% Note that EGIS session files are defined as always being epoched.
% For session files the trials are organized with the members of each cell grouped
% together.  For average files the "trials" (subjects) are organized with the cells
% also grouped together (e.g., "cell1sub1, cell1sub2, ...).
%_______________________________________________________________________
%
%
% Modified from EGI's EGI Toolbox with permission 2007-06-28 Joseph Dien

% This file is part of FieldTrip, see http://www.ru.nl/neuroimaging/fieldtrip
% for the documentation and details.
%
%    FieldTrip 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.
%
%    FieldTrip 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 FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id: read_egis_data.m 3063 2011-03-07 05:02:52Z josdie $

fh=fopen([filename],'r');
if fh==-1
    error('wrong filename')
end
fclose(fh);

[fhdr,chdr,ename,cnames,fcom,ftext] = read_egis_header(filename);
fh=fopen([filename],'r');
fhdr(1)=fread(fh,1,'int32'); %BytOrd
[str,maxsize,cEndian]=computer;
if fhdr(1)==16909060
    if cEndian == 'B'
        endian = 'ieee-be';
    elseif cEndian == 'L'
        endian = 'ieee-le';
    end;
elseif fhdr(1)==67305985
    if cEndian == 'B'
        endian = 'ieee-le';
    elseif cEndian == 'L'
        endian = 'ieee-be';
    end;
else
    error('This is not an EGIS average file.');
end;

if fhdr(2) == -1
    fileType = 'ave';
elseif fhdr(2) == 3
    fileType = 'ses';
else
    error('This is not an EGIS file.');
end;

dat=zeros(hdr.nChans,hdr.nSamples,endtrial-begtrial+1);

%read to end of file header
status=fseek(fh,(130+(2*fhdr(18))+(4*fhdr(19))),'bof');
status=fseek(fh,2,'cof');
for loop=1:fhdr(18)
  temp=fread(fh,80,'uchar');
  theName=strtok(temp);
  theName=strtok(theName,char(0));
  cnames{loop}=deblank(char(theName))';
  status=fseek(fh,fhdr(24+(loop-1))-80,'cof');
end
fcom=fread(fh,fhdr(20),'uchar');
ftext=fread(fh,fhdr(21),'uchar');
fpad=fread(fh,fhdr(22),'uchar');
status=fseek(fh,-2,'cof');

%read to start of desired data
fseek(fh, ((begtrial-1)*hdr.nChans*hdr.nSamples*2), 'cof');

for segment=1:(endtrial-begtrial+1)
    dat(:,:,segment) = fread(fh, [hdr.nChans, hdr.nSamples],'int16',endian);
end
dat=dat(chanindx, :,:);

if fileType == 'ave'
    dat=dat/fhdr(12); %convert to microvolts
elseif fileType == 'ses'
    dat=dat/5; %convert to microvolts (EGIS sess files created by NetStation use a 5 bins per microvolt scaling)
end;

fclose(fh);


function [boxesOut uniqueIdx] = BoxRemoveDuplicates(boxesIn)
% function boxOut = BoxRemoveDuplicates(boxIn)
%
% Removes duplicate boxes. Leaves the boxes in the same order
% Keeps the first box of each kind.
%
% boxesIn:          N x 4 array containing boxes
%
% boxexOut:         M x 4 array of boxes witout duplicates. M <= N
% uniqueIdx:        Indices of retained boxes from boxesIn
%
%     Jasper Uijlings - 2013

[dummy uniqueIdx] = unique(boxesIn, 'rows', 'first');
uniqueIdx = sort(uniqueIdx);
boxesOut = boxesIn(uniqueIdx,:);


function img_list = batch_imread(batch)
% -------------------------------------------------------------------------
%   Description:
%       read a batch of images
%
%   Input:
%       - batch : array of ID to fetch
%
%   Output:
%       - img_list: batch of images
%
%   Citation:
%       Deep Laplacian Pyramid Networks for Fast and Accurate Super-Resolution
%       Wei-Sheng Lai, Jia-Bin Huang, Narendra Ahuja, and Ming-Hsuan Yang
%       IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2017
%
%   Contact:
%       Wei-Sheng Lai
%       wlai24@ucmerced.edu
%       University of California, Merced
% -------------------------------------------------------------------------

    img_list = cell(length(batch), 1);

    for i = 1:length(batch)
         img = imread(batch{i});

         if( size(img, 3) > 1 )
             img = rgb2ycbcr(img);
             img = img(:, :, 1);
         end

         img_list{i} = im2single(img);
    end

end


function [row,header] = genTablePCP(pcp,name)

assert(length(pcp)==11)
header = sprintf(' Torso & Upper & Lower & Upper & Fore- & Head  & Total %s\n       & Leg   & Leg&  & Arm   & arm   &       &       %s\n','\\','\\');

row = sprintf('%s& %1.1f  & %1.1f  & %1.1f  & %1.1f  & %1.1f  & %1.1f & %1.1f %s\n',name,pcp(10),(pcp(2)+pcp(3))/2,(pcp(1)+pcp(4))/2,(pcp(6)+pcp(7))/2,(pcp(5)+pcp(8))/2,pcp(9),pcp(end),'\\');

fprintf('\n%s %s',blanks(length(name)),header);
fprintf('%s\n',row);
end


function options = pswarmset(varargin)
%PSWARMSET  Create or alter the options for Optimization with PSwarm
%
% options = pswarmset('param1',value1,'param2',value2,...) creates an
% PSWARM options structure with the parameters 'param' set to their
% corresponding values in 'value'. Parameters not specified will be set to
% the PSWARM default.
%
% options = pswarmset(oldopts,'param1',value1,...) creates a copy of the old
% options 'oldopts' and then fills in (or writes over) the parameters
% specified by 'param' and 'value'.
%
% options = pswarmset() creates an options structure with all fields set to
% PSWARMSET defaults.
%
% pswarmset() prints a list of all possible fields and their function.

%   Copyright (C) 2012 Jonathan Currie (I2C2)

% Print out possible values of properties.
if ((nargin == 0) && (nargout == 0))
    printfields();
    return
end

%Names and Defaults
Names = {'swarm_size';'vectorized';'mu';'nu';'iweight';'fweight';'delta';'idelta';'ddelta'};
Defaults = {42;0;0.5;0.5;0.9;0.4;Inf;2.0;0.5};

%Enter and check user args
try
    options = opticheckset(Names,Defaults,@checkfield,varargin{:});
catch ME
    throw(ME);
end

function checkfield(field,value)
%Check a field contains correct data type
switch lower(field)
    %Scalar double
    case {'mu','nu','iweight','fweight','delta','idelta','ddelta'}
        err = opticheckval.checkScalarDbl(value,field);
    %Scalar 0/1
    case 'vectorized'
        err = opticheckval.checkScalar01(value,field);
    %Non-zero scalar double
    case 'swarm_size'
        err = opticheckval.checkScalarIntGrtZ(value,field);

    otherwise
        err = sprintf('Unrecognized parameter name ''%s''.', field);
end
if(~isempty(err)), throw(err); end

function printfields()
%Print out fields with defaults
fprintf('        swarm_size: [ Swarm Size {42} ] \n');
fprintf('        vectorized: [ Objective function is vectorized {0}, 1 ] \n');
fprintf('                mu: [ Cognitial Parameter {0.5} ] \n');
fprintf('                nu: [ Social Parameter {0.5} ] \n');
fprintf('           iweight: [ Initial Weight {0.9} ] \n');
fprintf('           fweight: [ Final Weight {0.4} ] \n');
fprintf('             delta: [ Initial Delta {Inf} ] \n');
fprintf('            idelta: [ Increase Delta {2.0} ] \n');
fprintf('            ddelta: [ Decrease Delta {0.5} ] \n');
fprintf('\n');


function [cum_ret, cumprod_ret, daily_ret, ra_ret, run_time]...
    = manager(strategy_name, dataset_name, varargins, opts)
% Manager:
% This program universally calls different OLPS strategies and datasets.
%
% function [cum_ret, cumprod_ret, daily_ret, ra_ret, run_time]...
%          = manager(strategy_name, dataset_name, varargins, opts)
%
% cum_ret: a number representing the final cumulative wealth.
% cumprod_ret: cumulative return until each trading period
% daily_ret: individual returns for each trading period
% ra_ret:  result analysis, output risk-adjusted return and other
% run_time: computational time for a algorithm on a dataset
%
% strategy_name: the string of a strategy.
% dataset_name: the string of a dataset.
% varargins: parameter array, depends on the algorithms
% opts: a variable to control the behavior of back testing. Described as
% follows.
%
% Option parameters:
%     opts.quiet_mode = 0/1;    display debug info[Y/N];
%     opts.display_interval = 500; display info time interval
%     opts.log_record = 0/1;    record the .log file[N/Y]
%     opts.mat_record = 0/1;    record the .mat file[N/Y]
%     opts.analyze_mode = 0/1;  analyze the algorithm? [N/Y]
%
% Example: [cum_ret, cumprod_ret, daily_ret, ra_ret, run_time] ...
%          = manager('market', 'nyse_o', {0}, opts);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% This file is part of OLPS: http://OLPS.stevenhoi.org/
% Original authors: Bin LI, Steven C.H. Hoi
% Contributors: Doyen Sahoo
% Change log:
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Loading the Dataset stringed dataset_name
if (~opts.quiet_mode)
    fprintf(1, 'Running strategy %s on dataset %s\nLoading dataset %s.\n', strategy_name, dataset_name, dataset_name);
end
load(sprintf('../Data/%s.mat', dataset_name));
if (~opts.quiet_mode)
    fprintf(1, 'Finish loading dataset %s\nThe size of the dataset is %dx%d.\n', dataset_name, size(data, 1), size(data, 2));
end

%% Logging stage, format the log file name
if (opts.log_mode)
    dt = datestr(now, 'yyyy-mmdd-HH-MM-SS');
    file_name = ['../Log/Log/' strategy_name '-' dataset_name '-' dt '.txt'];
    fid=fopen(file_name, 'wt');

    fprintf(fid, 'Running strategy %s on dataset %s.\n', strategy_name, dataset_name);
else
    fid = 1;  % use 1 to ensure no leak
end

start_time = datestr(now, 'yyyy-mmdd-HH-MM-SS-FFF');

if (opts.log_mode)
    fprintf(fid, 'Start Time: %s.\n', start_time);
end
if (~opts.quiet_mode)
    fprintf(1, 'Start Time: %s.\n', start_time);
end

%%%%%%%%%%%%%%Calling Strategy Stage%%%%%%%%%%%%%%%%%%%
fprintf(1, '----Begin %s on %s-----\n', strategy_name, dataset_name);
start_watch = tic;

strategy_fun = [strategy_name '(fid, data, varargins, opts)'];
[cum_ret, cumprod_ret, daily_ret, daily_portfolio] = eval(strategy_fun);

tElapse = toc(start_watch);
fprintf(1, '----End %s on %s-----\n', strategy_name, dataset_name);

isOctave = exist('OCTAVE_VERSION', 'builtin') ~= 0;
if (isOctave)
    fflush(stdout); % force output in Octave
end

%%%%%%%%%%%%%%Logging Stage%%%%%%%%%%%%%%%%%%%%%%
stop_time = datestr(now, 'yyyy-mmdd-HH-MM-SS-FFF');

if (opts.log_mode)
    fprintf(fid, 'Stop Time: %s.\n', stop_time);
    fprintf(fid, 'Elapse time(s): %f.\n', tElapse);
end

if (~opts.quiet_mode)
    fprintf(1, 'Stop Time: %s.\n', stop_time);
    fprintf(1, 'Elapse time(s): %f.\n', tElapse);
end

%%%%%%%%%%%%%%Analysis Stage%%%%%%%%%%%%%%%%%%%%%%
% Analyze the results, output to the log file and screen
ra_ret = [];
run_time = tElapse;
if (opts.analyze_mode)
    [ra_ret] = ra_result_analyze(fid, data, cum_ret, cumprod_ret, daily_ret, opts);
end

%% Save the result variables to a .mat file
if (opts.mat_mode)
    mat_dt = datestr(now, 'yyyy-mmdd-HH-MM');
    mat_name = ['../Log/Mat/' strategy_name '-' dataset_name '-' mat_dt '.mat'];
    save(mat_name,  'cum_ret', 'cumprod_ret', 'daily_ret', 'ra_ret', 'run_time', 'daily_portfolio');
end

% Clear the log file
if (opts.log_mode)
    fclose(fid);
end

%% Email notivation, currently disabled.
% %% Send the results to email: libin.yz@gmail.com
% if (opts.log_mode) && (opts.mat_mode)
%     msg_rcv = ['libin.yz@gmail.com'];
%     msg_head = ['Results from MATLAB:' strategy_name '-' dataset_name '-' dt ];
%     msg_body = ['cum_ret' num2str(cum_ret) '    ' 'run_time' num2str(run_time)];
%     msg_attach =[file_name];
% %    sendmail(msg_rcv, msg_head, msg_body,{msg_attach})
% end

end
%%%%%%%%%%%%%%End%%%%%%%%%%%%%%%%%%%%%%


% Script demo_load_fileformats - to be excluded
% Shows versatile file format loading capabilities of MrImage.load
%
% demo_load_fileformats
%
%
% See also

% Author:   Saskia Bollmann & Lars Kasper
% Created:  2017-02-14
% Copyright (C) 2017 Institute for Biomedical Engineering
% University of Zurich and ETH Zurich
%
% This file is part of the TAPAS UniQC Toolbox, which is released
% under the terms of the GNU General Public License (GPL), version 3.
% You can redistribute it and/or modify it under the terms of the GPL
% (either version 3 or, at your option, any later version).
% For further details, see the file COPYING or
% <http://www.gnu.org/licenses/>.

pathExamples    = tapas_uniqc_get_path('data');

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 1. Load different types of nifti
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% a) load data from fileName and updates both name and parameters.save of
% nifti files, header is read to update MrImage.parameters
fileName = fullfile(pathExamples, 'nifti', 'rest', 'fmri_short.nii');

Y1 = MrImage(fileName);
disp(Y1);
disp(Y1.dimInfo);

%% b) load data from fileName with different load options

% selectedVolumes: Note that selectedVolumes is specific to loading nifti,
% cpx and par/rec files and refers to the 4rd dimension of the loaded data;
% the advantage is it avoids loading the full data and then selecting a subset.
Y2 = MrImage(fileName, 'selectedVolumes', 3:2:9);
disp(Y2.dimInfo);

% updateProperties: Note that per default the name of the MrImage object is
% set to the file. For more information see MrDataNd.read_single_file. In
% the example here, the save parameters are set to the load path. Per
% default, a new path depending on the pwd is created to prevent accidental
% overwrite. Be careful with this option!
Y3 = MrImage(fileName, 'updateProperties', 'save');
disp(Y3.parameters.save);

%% c) load data from fileName with additinal dimInfo information

% default: The dimInfo information is gathered from the header (same as Y1,
% nothing to do here).
Y4 = MrImage(fileName);

% fileName_dimInfo.mat: An additional dimInfo.mat file exisits.
Y4.dimInfo.units = {'this', 'and', 'that', 'too'};
Y4.save;

% load file, dimInfo is automatically added
Y5 = MrImage(Y4.get_filename);
disp(Y5.dimInfo);

% compare to only loading the nifti file
[fp, fn] = fileparts(Y4.get_filename);
delete(fullfile(fp, [fn, '_dimInfo.mat']));
Y6 = MrImage(Y4.get_filename);
disp(Y6.dimInfo);

% restore correct dimInfo via dimInfo argument
Y7 = MrImage(Y4.get_filename, 'dimInfo', Y5.dimInfo);

disp(Y7.dimInfo);

% or, alternatively, via prop/val pair
Y8 = MrImage(Y4.get_filename, 'units', Y4.dimInfo.units);
disp(Y8.dimInfo)

%% d) load filename from data with additional affine geometry
Y9 = MrImage(fileName, 'shear', [0.2 -0.1 0]);
disp(Y9.geometry);

%% 2. Load multiple files in folder
% a) load multiple .nii files in folder with filenames containing additional
% dimension information but no additinal _dimInfo.mat files
fileNameSplitSubset = fullfile(pathExamples, 'nifti', 'split', 'subset');
YSplitSubset = MrImage(fileNameSplitSubset);
disp(YSplitSubset.dimInfo);

% b) load multiple nifti files in folder without the filenames containing
% dimension information
fileNameSplitRes = fullfile(pathExamples, 'nifti', 'split', 'split_residual_images');
YSplitRes = MrImage(fileNameSplitRes);
disp(YSplitRes.dimInfo);

% c) load multiple nifti files in folder with filenames containing
% additional dimension information and select
fileNameSplitFull = fullfile(pathExamples, 'nifti', 'split', 'subset');
select.TE = 2;
select.t = 3;
YSplitSelect = MrImage(fileNameSplitFull, 'select', select);

% d) load multiple nifti files in folder with filenames containing
% additional dimension information and _dimInfo file
fileNameSplitDimInfo = fullfile(pathExamples, 'nifti', 'split', 'with_dim_info');
YSplitDimInfo = MrImage(fileNameSplitDimInfo);

% e) load multiple nifti files in folder with filenames containing
% additional dimension information and select which is not a dimension of
% the files
selectError.TE = 2;
selectError.t = 7;
selectError.doesNotExist = 3;
YSplitError = MrImage(fileNameSplitFull, 'select', selectError);

% f) select for MrSeries
YMrSeries = MrSeries(fileName, 'select', {'t', 4:2:8});
disp(YMrSeries.data.dimInfo);


function x = p13_root ( i )

%*****************************************************************************80
%
%% P13_ROOT returns a root for problem 13.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    07 May 2011
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, integer I, the index of the requested root.
%
%    Output, real X, the value of the root.
%
  if ( i == 1 )
    x = 100.0;
  else
    fprintf ( 1, '\n' );
    fprintf ( 1, 'P13_ROOT - Fatal error!\n' );
    fprintf ( 1, '  Illegal root index = %d\n', i );
    error ( 'P13_ROOT - Fatal error!' );
  end

  return
end