function [modelJoint] = createMultipleSpeciesModel(models, varargin)
% Based on the implementation from *Klitgord and Segre 2010, PMID 21124952*.
% The present implementation has been used in *PMID 23022739*, *PMID 25841013*,
% *PMID 25901891*, *PMID 27893703*.
%
% Joins one or more COBRA models with or without another COBRA model
% representing the host. The created setup when a host is entered is
% depicted schematically in Figures 1 and 2 in *PMID 27893703*.
%
% Creates a common space u (lumen) through which all cells can feed and exchange metabolites,
% and separate extracellular spaces for all joined models.
% If a host model is entered, a separate compartment `b` (body fluids) which has no
% connection to extracellular space is created for the host. Metabolites can be transported from
% the lumen to `e`, from `e` to the cytosol and from the cytosol to `b`,
% but not from body fluids to the lumen.
%
% USAGE:
%
%    [modelJoint] = createMultipleSpeciesModel(models, varargin)
%
% INPUTS:
%    models:            cell array of COBRA models(at least one).
%                       Format
%
%                         * models{1,1} = model 1
%                         * models{2,1} = model 2...
%
% OPTIONAL INPUTS:
%    nameTagsModels:    cell array of tags for reaction/metabolite abbreviation
%                       corresponding to each model.
%                       Format
%                         * nameTagsModels{1,1} = 'name tag 1'
%                         * nameTagsModels{2,1} = 'name tag 2'...
%    modelHost:         COBRA model for host
%    nameTagHost:       string of tag for reaction/metabolite abbreviation of host model
%    mergeGenesFlag:    If true, the gene associations in both models are
%                       included in the joined model. If false, empty fields are created
%                       instead (default:false). Note: merging genes is time-consuming
%                       and may crash certain models.
%    remCyclesFlag:     If true, the function will attempt to remove futile
%                       cycles that appear after merging the models (default: false).
%
% OUTPUT:
%    modelJoint:        model structure for joint model
%
% .. Authors:
%       - Ines Thiele and Almut Heinken, 2011-2018
%       - Almut Heinken, 07.02.2018-included option whether or not genes are
%         merged
%       - Almut Heinken, 21.02.2018-fixed compatibility issue with reconstructions
%         from BIGG Models database that have _e instead of [e] as compartment IDs
%       - Almut Heinken, 06.03.2018-changed to parameter-input pairs
%       - Laurent Heirendt, 16/3/2018 - backward compatibility
%       - Almut Heinken, 15.01.2019-fixed compatibility issue with reconstructions
%         from KBase database that have [e0] instead of [e] as compartment IDs
%       - Almut Heinken, 01/2020-remove futile cycles in multi-species
%       AGORA models
%       - Almut Heinken, 07/2022-made futile cycles removal optional
%
% NOTE:
%    This function assumes, that exchange reactions are identified by
%    containing 'EX' in the reaction name and that no other reactions do have this property!

oldOptionalOrder = {'nameTagsModels', 'modelHost', 'nameTagHost', 'mergeGenesFlag'};

% ensure backward compatibility
if numel(varargin) > 0 && ~ischar(varargin{1})
    tempargin = cell(0);
    for i = 1:numel(varargin)
        if ~isempty(varargin{i})
            tempargin{end + 1} = oldOptionalOrder{i};
            tempargin{end + 1} = varargin{i};
        end
    end
    varargin = tempargin;
end

% Define default input parameters if not specified
parser = inputParser();
parser.addRequired('models', @iscell);
parser.addParameter('nameTagsModels', {}, @iscell);
parser.addParameter('modelHost', {}, @isstruct);
parser.addParameter('nameTagHost', '', @(x) ischar(x) || iscell(x))
parser.addParameter('mergeGenesFlag', false, @(x) isnumeric(x) || islogical(x))
parser.addParameter('remCyclesFlag', false, @(x) isnumeric(x) || islogical(x))

parser.parse(models, varargin{:});

models = parser.Results.models;
nameTagsModels = parser.Results.nameTagsModels;
modelHost = parser.Results.modelHost;
nameTagHost = parser.Results.nameTagHost;
mergeGenesFlag = parser.Results.mergeGenesFlag;
remCyclesFlag = parser.Results.remCyclesFlag;

if isempty(models)
   error('Please enter at least one model!')
end
% prepare the model structures and assign name tags for each model structure if not provided
modelNumber = size(models, 1);

if isempty(nameTagsModels)
    % assign default name tags for microbes
    for i = 1:modelNumber
        nameTagsModels{i, 1} = strcat('model', num2str(i), '_');
    end
else
    if size(nameTagsModels, 1) ~= modelNumber
        error('Number of name tags and joint models needs to be identical!')
    end
end

if isempty(nameTagHost)
    % assign default name tag for host
    nameTagHost = 'Host_';
end

%% ensure compatibility with reconstructions from BIGG Models database
for i = 1:modelNumber
    model = models{i, 1};
    metIndices =~cellfun(@isempty, regexp(model.mets, '_e$'));
    model.mets(metIndices) = strrep(model.mets(metIndices), '_e', '[e]');
    models{i, 1} = model;
end
if ~isempty(modelHost)
metIndices =~cellfun(@isempty, regexp(modelHost.mets, '_e$'));
modelHost.mets(metIndices) = strrep(modelHost.mets(metIndices), '_e', '[e]');
end

%% Ensure compatibility with reconstructions from KBase database
for i = 1:modelNumber
    model = models{i, 1};
    metIndices =~cellfun(@isempty, regexp(model.mets, '\[e0\]$'));
    model.mets(metIndices) = strrep(model.mets(metIndices), '[e0]', '[e]');
    % need workaround for biomass metabolite, otherwise the resulting joint
    % model will be unable to carry biomass flux
    if ~isempty(find(ismember(model.mets, 'cpd11416[c0]')))
        model = addDemandReaction(model, 'cpd11416[c0]');
    end
    models{i, 1} = model;
end
if ~isempty(modelHost)
metIndices =~cellfun(@isempty, regexp(modelHost.mets, '\[e0\]$'));
modelHost.mets(metIndices) = strrep(modelHost.mets(metIndices), '[e0]', '[e]');
end
%% Remove futile cycles that may appear after joining (optional)

if remCyclesFlag
    database = loadVMHDatabase;

    unionRxns={};
    for i = 1:modelNumber
        model=models{i, 1};
        unionRxns=vertcat(unionRxns,model.rxns);
    end
    unionRxns=unique(unionRxns);
    for i = 1:modelNumber
        model=models{i, 1};
        biomassReaction=model.rxns(find(strncmp(model.rxns, 'bio', 3)));
        model = removeFutileCycles(model, biomassReaction, database,unionRxns);
        models{i, 1} = model;
    end
end
%% define some variables
eTag = 'u';
exTag = 'e';
% find exchange reactions for models, but leaves demand and sink reactions
% do this for all models to be added, remove exchange reactions from host while leaving demand and sink reactions
% First, find the minimal number of fields common to all models.
presentinallModels = fieldnames(models{1});
missingFields = {};
for i = 2:modelNumber
    cfields = fieldnames(models{i});
    missingFields = union(missingFields, setxor(cfields, presentinallModels));
    presentinallModels = intersect(presentinallModels, cfields);
end
fprintf('The following fields are missing in several models, they will not be merged:\n');
disp(missingFields);
models = restrictModelsToFields(models, presentinallModels);

modelStorage = cell(modelNumber, 1);
for i = 1:modelNumber
    % a new model each turn
    model = models{i, 1};
    % find exchange reactions and external metabolites
    exmod = model.rxns(strmatch('EX', model.rxns));
    % remove all previously defined exchange reactions
    model = removeRxns(model, exmod);
    % make sure the exchange reactions and changed model are saved under correct name
    modelStorage{i, 1} = model;
end

if ~isempty(modelHost)
    %% with a host
    exmod = modelHost.rxns(strmatch('EX', modelHost.rxns));

    % modelHost = removeRxns(modelHost,ExRH);
    % ExRH = modelHost.rxns(selExcH);
    % ExRH(strmatch('sink',modelHost.rxns(selExcH)))=[];
    % ExRH(strmatch('DM',modelHost.rxns(selExcH)))=[];

    % create a new extracellular space for host
    % find all metabolites in e
    relMetIndex = cellfun(@(x) ~isempty(strfind(x, '[e]')), modelHost.mets);
    relMets = modelHost.mets(relMetIndex);
    relRxns = findRxnsFromMets(modelHost, relMets);  % These are all reactions which are relevant
    rxnIndices = ismember(modelHost.rxns, relRxns);
    Stoich = modelHost.S(:, rxnIndices);
    changedMets = regexprep(modelHost.mets, '\[e\]', '\[b\]');
    modelHost = addMultipleMetabolites(modelHost, setdiff(changedMets, modelHost.mets));
    modelHost = addMultipleReactions(modelHost, strcat(modelHost.rxns(rxnIndices), 'b'), changedMets, Stoich, 'lb', modelHost.lb(rxnIndices), ...
                                    'ub', modelHost.ub(rxnIndices), 'c', modelHost.c(rxnIndices), 'subSystems', repmat({'Host Exchange'}, numel(relRxns), 1));

    % remove exchange reactions from host while leaving demand and sink reactions
    modelHost = removeRxns(modelHost, exmod);

    %% create intercellular space
    % will need to find all extracellular metabolites and duplicate reactions
    % if a host model was input, create the shared compartment for the microbes
    model = modelStorage{1, 1};
    nameTag = nameTagsModels{1, 1};
    [modelJoint, MexGJoint] = createInterSpace(model, nameTag, eTag, exTag);

    % if more than one microbe was input
    if modelNumber > 1
        for i = 1:modelNumber
            model = modelStorage{i, 1};
            nameTag = nameTagsModels{i, 1};
            [model, MexG] = createInterSpace(model, nameTag, eTag, exTag);
            % make sure the changed model is saved under correct name
            modelStorage{i, 1} = model;
            MexGJoint = union(MexG, MexGJoint);
        end
     end

    [modelHost,MexGHost] = createInterSpace(modelHost, nameTagHost, eTag, exTag);
    MexGJoint = union(MexGJoint, MexGHost);

    %% merge the models
    % if more than one microbe was input
    if modelNumber > 1
        for i = 2:modelNumber
            model = modelStorage{i, 1};
            [modelJoint] = mergeTwoModels(modelJoint, model, 1, mergeGenesFlag);
        end
    end
    [modelJoint] = mergeTwoModels(modelJoint,modelHost, 1, mergeGenesFlag);

    modelJoint = addExchangeRxn(modelJoint, unique(MexGJoint));

else
    %% without a host
     % create the shared compartment for the microbes
    model = modelStorage{1, 1};
    nameTag = nameTagsModels{1, 1};
    [modelJoint, MexGJoint] = createInterSpace(model, nameTag, eTag, exTag);
     if modelNumber > 1
        for i = 2:modelNumber
            model = modelStorage{i, 1};
            nameTag = nameTagsModels{i, 1};
            [model, MexG] = createInterSpace(model, nameTag, eTag, exTag);
            modelStorage{i, 1}=model;
            MexGJoint = union(MexG, MexGJoint);
        end
     end

     if modelNumber > 1
        for i = 2:modelNumber
            model = modelStorage{i, 1};
            [modelJoint] = mergeTwoModels(modelJoint, model, 1, mergeGenesFlag);
        end
     end

    modelJoint = addExchangeRxn(modelJoint, unique(MexGJoint));
end
end

%%
function [modelNew,MexG] = createInterSpace(model, nameTag, eTag, exTag)
% create intercellular space
% will need to find all extracellular metabolites and duplicate reactions using them
modelNew = model;
% add name tag to all metabolites and reactions in model
modelNew.mets = strcat(nameTag, model.mets);
modelNew.rxns = strcat(nameTag, model.rxns);
% Get the relevant metabolites
relMetsIndex = cellfun(@(x) ~isempty(strfind(x,'biomass[c]')) || ~isempty(strfind(x,['[', exTag, ']'])),modelNew.mets);
relMets = modelNew.mets(relMetsIndex);
% Define the names of the interspace metabolites
MexG = regexprep(strrep(relMets,nameTag,''),strcat('\[', exTag, '\]'), strcat('\[', eTag, '\]'));
varinput = {};
% Add metNames and metFormulas, if present in the original model
if isfield(modelNew,'metNames')
    varinput{end+1} = 'metNames';
    varinput{end+1} = modelNew.metNames(relMetsIndex);
end
if isfield(modelNew,'metFormulas')
    varinput{end+1} = 'metFormulas';
    varinput{end+1} = modelNew.metFormulas(relMetsIndex);
end
% Add all new Metabolites
modelNew = addMultipleMetabolites(modelNew, MexG, varinput{:});

nExchange = numel(relMets);
% Set the exchanger Stoichiometries (met[e] -> met[u])
stoich = [-speye(nExchange);speye(nExchange)];
% Set the names of the exchangers
rxnNames =  strcat(nameTag, 'IEX_', MexG, 'tr');
% Set the bounds
lbs = repmat(-1000,nExchange,1);
ubs = repmat(1000,nExchange,1);
% Set the subSystem
subSystems = repmat({'Transport, intercellular'},nExchange,1);
% Add all Reactions in one go.
modelNew = addMultipleReactions(modelNew,rxnNames,[relMets;MexG],stoich,'lb',lbs,'ub',ubs,'subSystems',subSystems);

end


% this script is to add metabolite identifiers (#174) that will be used for Met Atlas web-portal.

% load data
metIDs = importTsvFile('metabolites.tsv');

% get compartment id for non-HMR mets that will be used later
compID = cellfun(@(x) regexprep(x, '^.+_(\w)$', '$1'), metIDs.mets, 'UniformOutput', false);
matche2NonHMR = regexp(metIDs.mets, '_\w$', 'match');
emptymask = cellfun('isempty', matche2NonHMR);
compID(emptymask) = {''};    % empty HMR met ids

% generate MA met ids from HMR ids only with "MA-" prefix
metMAID = cellfun(@(x) regexprep(x, '^m(\d\d\d\d\d\w)$', 'MA-M$1'), metIDs.mets, 'UniformOutput', false);

% check consistency between HMR ids and empty elemnets of non-HMR ids
matche2HMR = regexp(metMAID, '^MA-M\d\d\d\d\d\w$', 'match');
ind2HMR = ~cellfun('isempty', matche2HMR);
check = find(ind2HMR ~= emptymask);
metIDs.mets(check)
%    {'temp001c'}
%    {'temp001s'}
% these two mets are from HMR but in non-standard format, will be treated as
% non-HMR ids
compID(check) = {'c';'s'};

% kepp HMR ids in metMAID and clean others
ind_MAID = startsWith(metMAID, 'MA-M');
metMAID(~ind_MAID) = {''};
metMAIDNoComp = cellfun(@(x) regexprep(x, '^(.+)\w$', '$1'), metMAID, 'UniformOutput', false);

% get index of nonHMR ids from compID and remove their comppart id
nonHMRidInd = ~cellfun('isempty', compID);
nonHMRidNoComp = cellfun(@(x) regexprep(x, '^(.+)_\w$', '$1'), metIDs.mets(nonHMRidInd), 'UniformOutput', false);

% get unique list of nonHMR ids
[uniqueID, ia, ic] = unique(nonHMRidNoComp);
%isequal(nonHMRidNoComp, uniqueID(ic))
%isequal(uniqueID, nonHMRidNoComp(ia))

% prepare standard format MA ids for nonHMR mets
idsToAdd = cell(10000,1);
for i=1:10000
    idsToAdd{i} = strcat('MA-M',sprintf('%05d', i));
end
idsToAdd = setdiff(idsToAdd, metMAIDNoComp);     % exclude existing ones
idsToAdd = idsToAdd(1:length(uniqueID));

% get ids to fill in
idsToFill = idsToAdd(ic);

% append compartment id to idsToFill
idsToFill = strcat(idsToFill,compID(nonHMRidInd));

% complete MA met ids
metMAID(nonHMRidInd) = idsToFill;

% format check
all(cell2mat(regexp(metMAID, '^MA-M\d\d\d\d\d\w$')))
% unique check
isequal(length(metMAID), length(sort(metMAID)))
% empty check
B = cellfun('isempty', metMAID);
all(B(:) == 0)

% add new field and output
metIDs.metMAID = metMAID;
exportTsvFile(metIDs,'metabolites.tsv');


function saveash5(data, path_to_file, options)

% save the variable stored in data in a h5 file named path_to_file (includes the
% path to the directory). The file is saved with the same class as data.
% options.groupname: the name of the group (default: /mov)
% options.append: append to existing file if it exists (default: True)

if ~exist('path_to_file','var'); path_to_file = 'data.h5'; end
[pathstr, name, ~] = fileparts(path_to_file);
path_to_file = fullfile(pathstr,[name,'.h5']); % make sure file has an h5 extension
data_type = class(data);

sizY = size(data);
nd = ndims(data)-1;

defoptions.groupname = '/mov';
defoptions.append = true;

if ~exist('options','var'); options = defoptions; end
if ~isfield(options,'groupname'); options.groupname = defoptions.groupname; end
if ~isfield(options,'groupname'); options.append = defoptions.append; end

if ~exist(path_to_file,'file') || ~options.append
    if ~options.append && exist('path_to_file','file')
        warning('Rewriting existing file')
    end
    h5create(path_to_file,options.groupname,[sizY(1:nd),Inf],'Chunksize',[sizY(1:nd),100],'Datatype',data_type);
    start_point = ones(1,nd+1);
else
    fprintf('Appending to existing file \n')
    info = h5info(path_to_file);
    options.groupname = ['/',info.Datasets.Name];
    start_point = [ones(1,nd),info.Datasets.Dataspace.Size(end)+1];
end

h5write(path_to_file,options.groupname,data,start_point,size(data));


function varargout = sltakeval(A)
%SLTAKEVAL Extracts the values in an array/cell array to output
%
% $ Syntax $
%   -[O1, O2, ..., On] = sltakeval(A)
%
% $ Description $
%   -[O1, O2, ..., On] = sltakeval(A) takes the values in the array
%    or cell array A and assigns them to output variables.
%    The number of elements in A should be equal to the number of
%    output arguments.
%
% $ History $
%   - Created by Dahua Lin, on Sep 1st, 2006
%

n = numel(A);
if nargout ~= n
    error('sltoolbox:sizmismatch', ...
        'The number of elements in A is not equal to the number of outputs');
end

if n > 0
    if iscell(A)
        if isequal(size(A), [1, n])
            varargout = A;
        else
            varargout = reshape(A, 1, n);
        end
    else
        varargout = cell(1, n);
        for i = 1 : n
            varargout{i} = A(i);
        end
    end
end


function [hRect,api] = imrectLite( hParent, pos, lims, ar, varargin )
% A 'lite' version of imrect [OBSOLETE: use imrectRot].
%
% There are significant problems using imrect in a larger GUI, mostly
% due to the fact that imrect is such a heavy object.  This creates a
% simple, fast, and in some ways nicer version of imrect.
%
% The implementation uses two graphics object: a rectangle and a patch.
% The patch is just there to catch mouse clicks in case the rectangle does
% not have a face color.  Any valid property of the rectangle may be
% altered (such as 'FaceColor', 'Curvature', ... ), EXCEPT for its
% 'ButtonDownFcn', 'DeleteFcn' and 'Position' properties.  To change the
% 'Position' of the rectangle, use the output structure 'api' (described
% below).
%
% USAGE
%  [hRect,api] = imrectLite( [hParent],[pos],[lims],[ar],[varargin] )
%
% INPUTS
%  hParent    - [gca] object parent, typically an axes object (gca)
%  pos        - [] initial pos vector [x y w h] or [] or [x0 y0]
%  lims       - [] 2x2 matrix of [xMin xMax; yMin yMax]
%  ar         - [0] aspect ratio (if positive w/h is constrained to ar)
%  varargin   - [] parameters to RECTANGLE graphics object
%
% OUTPUTS
%  hRect      - handle to rectangle, use to alter color etc.
%  api        - interface allowing access to created object
%  .getPos()       - get position - returns 4 elt pos
%  .setPos(pos)    - set position (while respecting constraints)
%  .setAr(ar)      - set aspect ratio to fixed value
%  .setPosLock(b)  - if lock set (b==true), rectangle cannot change
%  .setSizLock(b)  - if lock set (b==true), rectangle cannot change size
%  .setPosChnCb(f) - whenever pos changes (even slightly), calls f(pos)
%  .setPosSetCb(f) - whenever pos finished changing, calls f(pos)
%  .uistack(...)   - calls 'uistack( [hRect hPatch], ... )', see uistack
%
% EXAMPLE
%  figure(1), imshow cameraman.tif
%  rectProp = {'EdgeColor','g','LineWidth',4,'Curvature',[.1 .1]};
%  lims = [get(gca,'xLim'); get(gca,'yLim')];
%  [h,api] = imrectLite( gca, [], lims, [],  rectProp{:} );
%  api.setPosChnCb( @(pos) disp(['        ' num2str(pos)]) );
%  api.setPosSetCb( @(pos) disp(['FINAL = ' num2str(pos)]) );
%
% See also IMRECT, RECTANGLE, PATCH
%
% Piotr's Image&Video Toolbox      Version 2.35
% Copyright 2012 Piotr Dollar.  [pdollar-at-caltech.edu]
% Please email me if you find bugs, or have suggestions or questions!
% Licensed under the Simplified BSD License [see external/bsd.txt]

% default arguments / globals
if( nargin<1 || isempty(hParent) ); hParent=gca; end
if( nargin<2 ); pos=[]; end
if( nargin<3 ); lims=[]; end
if( nargin<4 || isempty(ar) ); ar=0; end
[posChnCb,posSetCb]=deal([]);
posLock=false; sizLock=false; minSiz=1;

% get figure and axes handles
hAx = ancestor(hParent,'axes'); assert(~isempty(hAx));
hFig = ancestor(hAx,'figure'); assert(~isempty(hFig));
set( hFig, 'CurrentAxes', hAx );

% create rectangle as well as patch objects
if(isempty(pos)); vis='off'; else vis='on'; end
hPatch = patch('FaceColor','none','EdgeColor','none');
hRect = rectangle(varargin{:},'Visible',vis);
set([hRect hPatch],'ButtonDownFcn',{@btnDwn,0},'DeleteFcn',@deleteFcn);

% set / get position
if(length(pos)==4), setPos(pos); else
  btnDwn([],[],-1); waitfor(hFig,'WindowButtonUpFcn','');
end

% create api
api = struct('getPos',@getPos, 'setPos',@setPos, ...
  'setPosChnCb',@setPosChnCb, 'setPosSetCb',@setPosSetCb, ...
  'setPosLock',@setPosLock, 'setSizLock',@setSizLock, ...
  'setAr',@setAr, 'uistack',@uistack1 );

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

  function setPos( posNew, varargin )
    if(isempty(hRect) || isempty(hPatch)); return; end
    % constrain position to fall within lims
    pos = constrainPos( posNew, varargin{:} );
    % compute rectangle vertices
    xs = pos([1 1 1 1]); xs(2:3)=xs(2:3)+pos(3);
    ys = pos([2 2 2 2]); ys(3:4)=ys(3:4)+pos(4);
    vert = [xs' ys' [1;1;1;1]]; face=1:4;
    % draw objects
    set(hPatch,'Faces',face,'Vertices',vert);
    set(hRect,'Position',pos);
  end

  function pos = constrainPos( pos, anchor, pos0 )
    % constrain position to fall within lims
    if( ~isempty(lims) )
      posStr=pos(1:2);  posEnd=pos(1:2)+pos(3:4);
      posStr = min( max(posStr,lims(1:2)), lims(3:4)-minSiz );
      posEnd = max( min(posEnd,lims(3:4)), posStr+minSiz );
      pos = [posStr posEnd-posStr];
    end

    % now constrain for aspect ratio
    if( ar<=0 ); return; end;
    w=pos(3); h=pos(4);
    if( nargin<3 )
      w = min( w, h*ar );
      h = min( h, w/ar );
      pos(3:4) = [w h];
    else
      if( isempty(lims) )
        if( anchor(1)==0 ); w=h*ar; else h=w/ar; end;
      else
        mid = pos(1:2) + pos(3:4)/2;
        lr='MRL'; lr=lr(sign(anchor(1)-pos(1))+2);
        tb='MBT'; tb=tb(sign(anchor(2)-pos(2))+2);
        if( strcmp(tb,'M') )
          maxH=min(lims(4)-mid(2), mid(2)-lims(2))*2;
          switch lr
            case 'L'; maxW=min(lims(3)-pos(1),maxH*ar);
            case 'R'; maxW=min(pos(1)+pos(3)-lims(1),maxH*ar);
          end
          w=min(w,maxW); h=w/ar;
        else
          switch lr
            case 'L'; maxW=pos(1)+pos(3)-lims(1);
            case 'M'; maxW=min(lims(3)-mid(1), mid(1)-lims(1))*2;
            case 'R'; maxW=lims(3)-pos(1);
          end
          maxH=min(pos(2)+pos(4)-lims(2),maxW/ar);
        end
        h=min(h,maxH); w=h*ar;
      end
      if( strcmp(lr,'M') )
        pos0(1)=pos0(1)-(w-pos0(3))/2; pos0(3)=w;
      elseif( strcmp(tb,'M') )
        pos0(2)=pos0(2)-(h-pos0(4))/2; pos0(4)=h;
      end
      sgnSiz = [w h] .* (2*(pos(1:2)==anchor)-1);
      pos = shiftPos( anchor, pos0, sgnSiz );
    end
  end

  function pos = shiftPos( anchor, pos0, del )
    % shift pos by del unless anchor(i)==0
    pos = pos0;
    for i=1:2, if(anchor(i)==0), continue; end
      pos(i)=anchor(i); pos(i+2)=max(minSiz,abs(del(i)));
      if(del(i)<0); pos(i)=pos(i)-pos(i+2); end;
    end
  end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

  function pnt = getCurPnt()
    pnt = get(hAx,'CurrentPoint');
    pnt = pnt([1,3]);
  end

  function d = axisUnitsPerCentimeter()
    % approximage absolute axes size
    units=get(hAx,'units');
    set(hAx,'units','centimeters');
    cms = get(hAx,'Position');
    cms = max(cms(3:4));
    set(hAx,'units',units);
    % axes size in axes units
    xLim=get(gca,'xLim'); yLim=get(gca,'yLim');
    sizInner = max([xLim(2)-xLim(1),yLim(2)-yLim(1)]);
    % units per centemeter
    d = sizInner/cms;
  end

  function [anchor,cursor] = getSide( pnt )
    % get pnt in coordintates of center of rectangle
    rs=pos(3:4)/2; pnt=pnt-pos(1:2)-rs;
    % side(i) -1=near lf/tp bnd; 0=in center; +1:near rt/bt bnd
    t = axisUnitsPerCentimeter()*.15; side=[0 0];
    for i=1:2
      ti = min(t,rs(i)/2);
      if( pnt(i)<-rs(i)+ti ), side(i)=-1; % near lf/tp boundary
      elseif( pnt(i)>rs(i)-ti), side(i)=1; % near rt/bt boundary
      end
    end
    % compute anchor in orig coord system(opposite of selected side)
    anchor=-side.*rs+pos(1:2)+rs; anchor(side==0)=0;
    % select cursor based on position
    cs={'bottom','botr','right','topr','top','topl','left','botl'};
    a=mod(round((atan2(side(1),side(2))/pi)*4),8)+1; cursor=cs{a};
  end

  function btnDwn( h, evnt, flag )
    if(isempty(hRect) || isempty(hPatch)); return; end;
    if(posLock); return; end; if(sizLock); flag=1; end;
    if( flag==-1 ) % create new rectangle
      if(isempty(pos)), anchor=ginput(1); else anchor=pos(1:2); end
      setPos( [anchor 1 1] );
      set(hRect,'Visible','on'); cursor='botr'; flag=0;
    elseif( flag==0 ) % resize (or possibly drag) rectangle
      pnt=getCurPnt(); [anchor,cursor] = getSide( pnt );
      if(all(anchor==0)), btnDwn(h,evnt,1); return; end
    elseif( flag==1 ) % move rectangle
      anchor=getCurPnt(); cursor='fleur';
    else
      assert(false);
    end
    set( hFig, 'Pointer', cursor );
    set( hFig, 'WindowButtonMotionFcn',{@drag,flag,anchor,pos} );
    set( hFig, 'WindowButtonUpFcn', @stopDrag );
  end

  function drag( h, evnt, flag, anchor, pos0 ) %#ok<INUSL>
    if(isempty(hRect) || isempty(hPatch)); return; end;
    pnt = getCurPnt(); del = pnt-anchor;
    if( flag==1 ) % shift rectangle by del
      setPos( [pos0(1:2)+del pos0(3:4)] );
    else % resize rectangle
      setPos(shiftPos(anchor,pos0,del),anchor,pos0);
    end
    drawnow
    if(~isempty(posChnCb)); posChnCb(pos); end;
  end

  function stopDrag( h, evnt ) %#ok<INUSD>
    set( hFig, 'WindowButtonMotionFcn','');
    set( hFig, 'WindowButtonUpFcn','');
    set( hFig, 'Pointer', 'arrow' );
    if(~isempty(posSetCb)); posSetCb(pos); end;
  end

  function deleteFcn( h, evnt ) %#ok<INUSD>
    [posChnCb,posSetCb]=deal([]); hs=[hPatch hRect];
    for i=1:length(hs), if(ishandle(hs(i))); delete(hs(i)); end; end
    [hRect,hPatch]=deal([]);
  end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

  function pos0 = getPos(), pos0=pos; end

  function setAr( ar1 ), ar=ar1; setPos(pos); end

  function setPosChnCb( f ), posChnCb=f; end

  function setPosSetCb( f ), posSetCb=f; end

  function setPosLock( b ), posLock=b; end

  function setSizLock( b ), sizLock=b; end

  function uistack1( varargin )
    if(isempty(hRect) || isempty(hPatch)); return; end;
    uistack( [hRect hPatch], varargin{:} );
  end

end


function [result] = caffe_anysize_test(function_handle, varargin)
% conv_anysize_test('init', param_file, model_file)
% conv_anysize_test('release')
% conv_anysize_test('set_input_size', height, width, channel, num)
% is_initialized = conv_anysize_test('is_initialized')
% response = conv_anysize_test('forward', img)

if strcmp(function_handle, 'forward')
    im = varargin{1};
    if size(im, 3) == 1
        im = im(:, :, [1, 1, 1]);
    end
    % convert to single
    im = single(im);
    % permute from RGB to BGR and subtract the data mean (already in BGR)
    data_mean = 128;
    im = im(:, :, [3 2 1]) - data_mean;
    % flip width and height to make width the fastest dimension, height
    % second
    im = permute(im, [2 1 3]);
    varargin{1} = im;

    [result] = caffe('forward', varargin);

    % result is in (width, height, channel), need trans to (height, width,
    % channel)
    for i = 1:length(result)
        result{i} = permute(result{i}, [2, 1, 3]);
    end
end

if strcmp(function_handle, 'set_input_size')
    varargin = cellfun(@(x) double(x), varargin, 'UniformOutput', false);
    caffe('set_input_size', varargin{:});
end

if strcmp(function_handle, 'init')
    caffe('init', varargin{:});
end

if strcmp(function_handle, 'is_initialized')
    result = caffe('is_initialized');
end

if strcmp(function_handle, 'release')
    caffe('release');
end


% The COBRAToolbox: testElementalBalance.m
%
% Purpose:
%     - Tests computeMW functionality
%
% Authors:
%     - CI integration: Laurent Heirendt

% save the current path
currentDir = pwd;

% initialize the test
fileDir = fileparts(which('testElementalBalance'));
cd(fileDir);

% load the model and data
load('testElementalBalanceData.mat');

% run elmental balance with no optional functions
[MW, Ematrix] = computeMW(model);

% check that the molecular weights have been calculated properly
assert(isequal(MW, stdMW))

% check that the Ematrix has been computed correctly
assert(isequal(Ematrix, stdEmatrix))

% run computeMW with a specific met list
[MW, Ematrix, elements, knownWeights, unknownElements] = computeMW(model, model.mets(25:35), false);

% check that the molecular weights have been calculated properly
assert(isequal(MW, stdMW2))

% check that the Ematrix has been computed correctly
assert(isequal(Ematrix, stdEmatrix2))

% check the list of returned elements
assert(isequal(elements, {'C', 'N', 'O', 'H', 'P', 'Other'}))

% empty knownWeights and unknownElements (non-empty only if the genericFomrula
% flag is turned on)
assert(isempty(knownWeights) & isempty(unknownElements))

% check warning message
model = addMetabolite(model, 'uranium[c]', 'metFormula', 'U');
diary('testElementBalance_warning.txt')
computeMW(model, 'uranium[c]');
diary off
f = fopen('testElementBalance_warning.txt', 'r');
l = fgets(f);
text = '';
while ~isequal(l, -1)
    text = [text, l];
    l = fgets(f);
end
fclose(f);
delete('testElementBalance_warning.txt')
% the warning should contain the following:
re = regexp(text, 'formula \=');
assert(~isempty(re))
text = text(re:end);
re = regexp(text, '''U''');
assert(~isempty(re))
text = text(re:end);
re = regexp(text, 'comp \=');
assert(~isempty(re))
text = text(re:end);
re = regexp(text, 'U');
assert(~isempty(re))

% test genericFormula = true
model = addMetabolite(model, 'hypothetical1[c]', 'metFormula', '((CH2O)3H2O)2H2O');
nMets = numel(model.mets);
model = addMetabolite(model, 'hypothetical2[c]', 'metFormula', '(H2O)2CuSO4Element2');
model = addMetabolite(model, 'hypothetical3[c]', 'metFormula', 'C0H0');
model = addMetabolite(model, 'photon[c]', 'metFormula', 'Mass0');
[MW, Ematrix, elements, knownWeights, unknownElements] = computeMW(model, [], false, true);
% check weights
assert(all(isnan(MW) == isnan(stdMW3)) && max(abs(MW(~isnan(MW)) - stdMW3(~isnan(MW)))) < 1e-5)
% check elements and Ematrix
[yn, id] = ismember(elements, {'C', 'H', 'O', 'P', 'N', 'S', 'U', 'Cu', 'Element', 'Mass'});
assert(all(yn) & numel(elements) == 10)
assert(isequal(Ematrix, stdEmatrix3(:, id)))
% check weights for the known part
assert(all(knownWeights(1:nMets) == MW(1:nMets)))
assert(abs(knownWeights(nMets + 1) - 195.6392) < 1e-4)
assert(all(knownWeights(nMets + (2:3)) == 0))
% check unknownElements
assert(isequal(sort({'Element', 'Mass'}), sort(unknownElements)))

% change the directory
cd(currentDir)


function y = isempty( x )
y = any( size( x ) == 0 );

% Copyright 2010 Michael C. Grant and Stephen P. Boyd.
% See the file COPYING.txt for full copyright information.
% The command 'cvx_where' will show where this file is located.


% SLIDER - add slider to a figure
%
% Usage:
%   >>  slider( handler, horiz, vert, horizmag, vertmag);
%
% Inputs:
%   handler  - figure handler (for the current figure, use gcf)
%   horiz    - [0|1] add a horizontal slider
%   vert     - [0|1] add a horizontal slider
%   horizmag - magnify the width of the figure before adding the slider.
%              Default is 1.
%   vertmag  - magnify the height of the figure before adding the slider.
%              Default is 1.
%   allowsup - [0|1] allow suppression of slider by the 'x' button.
%              Default is 1.
%
% Note:
%   clicking on the 'x' the right corner restores the original setting
%
% Example: figure; plot(1:10); slider(gcf, 1, 1, 2, 2);
%
% Author: Arnaud Delorme, CNL / Salk Institute, 2001
%
% See also: EEGLAB

% Copyright (C) 2001 Arnaud Delorme, Salk Institute, arno@salk.edu
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.

function slider( handler, horiz, vert, horizmag, vertmag, allowsup);

if nargin < 2
	help slider;
	return;
end
if nargin < 3
	vert = 0;
end
if nargin < 4
	horizmag = 1;
end
if nargin < 5
	vertmag = 1;
end
if nargin < 6
	allowsup = 1;
end

pos   = get(gcf, 'position');
width  = 5/pos(3)*400;
height = 5/pos(4)*400;

pos = get(gca,'position'); % plot relative to current axes
q = [0.13 0.11 0 0];
s = [0.0077 0.0081 0.0077 0.0081];
if vert
	h = uicontrol('Parent',handler, ...
 	'style', 'slider', 'Units','Normalized', 'userdata', 1, 'value', 1, ...
	'Position', [113-width -9 width 119].*s+q, 'tag', 'winslider', ...
	'string','vertslider', 'callback', ...
	['if isempty(gcbf), fig = gcf; else fig = gcbf; end;' ...
	 'h = findobj(''parent'', fig);' ...
	 'h2 = findobj(''parent'', fig, ''tag'', ''winslider'');' ...
	 'h = setdiff_bc( h, h2);' ...
	 'curobj = findobj(''parent'', fig, ''string'', ''vertslider'');' ...
	 'shift = get(curobj, ''userdata'') - get(curobj, ''value'');' ...
	 'set( curobj, ''userdata'', get(curobj, ''value''));' ...
	 'for i = 1:length(h),' ...
	 '   curpos = get( h(i), ''position'');' ...
	 '   set( h(i), ''position'', [ curpos(1) curpos(2)+' num2str(vertmag-1) '*shift curpos(3:end)]);' ...
	 'end;' ...
	 'clear h2 h shift i curpos fig curobj;'] );
end

if horiz
	hz = uicontrol('Parent',handler, ...
 	'style', 'slider', 'Units','Normalized', 'userdata', 1, 'value', 1, ...
	'Position', [-17 -19+height 125 height].*s+q, 'tag', 'winslider', ...
	'string','horizslider', 'callback', ...
	['if isempty(gcbf), fig = gcf; else fig = gcbf; end;' ...
	 'h = findobj(''parent'', fig);' ...
	 'h2 = findobj(''parent'', fig, ''tag'', ''winslider'');' ...
	 'h = setdiff_bc( h, h2);' ...
	 'curobj = findobj(''parent'', fig, ''string'', ''horizslider'');' ...
	 'shift = get(curobj, ''userdata'') - get(curobj, ''value'');' ...
	 'set( curobj, ''userdata'', get(curobj, ''value''));' ...
	 'for i = 1:length(h),' ...
	 '   curpos = get( h(i), ''position'');' ...
	 '   set( h(i), ''position'', [ curpos(1)+' num2str(horizmag-1) '*shift curpos(2:end)]);' ...
	 'end;' ...
	 'clear h2 h shift i curpos fig curobj;'] );
end

% button to remove the slider
% ---------------------------
but = uicontrol( 'style', 'pushbutton', 'Units','Normalized', ...
		   'string', 'x', 'position', [113-width -19+height width height].*s+q, 'tag', 'winslider', 'callback', ...
		  ['hx = findobj(''parent'', gcbf, ''tag'', ''winslider'');' ... % put slider to their extremities
		   'hx = setdiff_bc(hx, gcbo);' ...
		   'set(hx, ''value'', 1);' ...
		   'eval(get(hx(1), ''callback''));' ...
		   'if length(hx) >1, eval(get(hx(2), ''callback'')); end;' ...
		   'h = findobj(''parent'', gcbf);' ... % recompute positions
		   'for i = 1:length(h),' ...
		   '   curpos = get( h(i), ''position'');' ...
		   '   set( h(i), ''position'', [(curpos(1)+(' num2str(horizmag) '-1))/' num2str(horizmag) '  (curpos(2)+(' num2str(vertmag) '-1))/' num2str(vertmag) ' curpos(3)/' num2str(horizmag) ' curpos(4)/' num2str(vertmag) ']);' ...
		   'end;' ...
		   'clear h hx curpos;' ...
		   'delete( findobj(''parent'', gcbf, ''tag'', ''winslider'') );' ]);

if ~allowsup
	set(but, 'enable', 'off');
end

% magnify object in the window
% ----------------------------
h = findobj('parent', handler);
set( h, 'units', 'normalized');
h2 = findobj('parent', handler, 'tag', 'winslider');
h = setdiff_bc( h, h2);
for i = 1:length(h)
	curpos = get( h(i), 'position');
	set( h(i), 'position', [curpos(1)*horizmag-(horizmag-1)  curpos(2)*vertmag-(vertmag-1) curpos(3)*horizmag curpos(4)*vertmag]);
end

if horiz
	% set the horizontal axis to 0
	% ----------------------------
	set(hz, 'value', 0);
	eval(get(hz, 'callback'));
end
return;


%SerialLink.plot Graphical display and animation
%
% R.plot(Q, options) displays a graphical animation of a robot based on
% the kinematic model.  A stick figure polyline joins the origins of
% the link coordinate frames. The robot is displayed at the joint angle Q (1xN), or
% if a matrix (MxN) it is animated as the robot moves along the M-point trajectory.
%
% Options::
% 'workspace', W    Size of robot 3D workspace, W = [xmn, xmx ymn ymx zmn zmx]
% 'floorlevel',L    Z-coordinate of floor (default -1)
%-
% 'delay',D         Delay betwen frames for animation (s)
% 'fps',fps         Number of frames per second for display, inverse of 'delay' option
% '[no]loop'        Loop over the trajectory forever
% '[no]raise'       Autoraise the figure
% 'movie',M         Save frames as files in the folder M
% 'trail',L         Draw a line recording the tip path, with line style L
%-
% 'scale',S         Annotation scale factor
% 'zoom',Z          Reduce size of auto-computed workspace by Z, makes
%                   robot look bigger
% 'ortho'           Orthographic view
% 'perspective'     Perspective view (default)
% 'view',V          Specify view V='x', 'y', 'top' or [az el] for side elevations,
%                   plan view, or general view by azimuth and elevation
%                   angle.
% 'top'             View from the top.
%-
% '[no]shading'     Enable Gouraud shading (default true)
% 'lightpos',L      Position of the light source (default [0 0 20])
% '[no]name'        Display the robot's name
%-
% '[no]wrist'       Enable display of wrist coordinate frame
% 'xyz'             Wrist axis label is XYZ
% 'noa'             Wrist axis label is NOA
% '[no]arrow'       Display wrist frame with 3D arrows
%-
% '[no]tiles'       Enable tiled floor (default true)
% 'tilesize',S      Side length of square tiles on the floor (default 0.2)
% 'tile1color',C   Color of even tiles [r g b] (default [0.5 1 0.5]  light green)
% 'tile2color',C   Color of odd tiles [r g b] (default [1 1 1] white)
%-
% '[no]shadow'      Enable display of shadow (default true)
% 'shadowcolor',C   Colorspec of shadow, [r g b]
% 'shadowwidth',W   Width of shadow line (default 6)
%-
% '[no]jaxes'       Enable display of joint axes (default false)
% '[no]jvec'        Enable display of joint axis vectors (default false)
% '[no]joints'      Enable display of joints
% 'jointcolor',C    Colorspec for joint cylinders (default [0.7 0 0])
% 'jointdiam',D     Diameter of joint cylinder in scale units (default 5)
%-
% 'linkcolor',C     Colorspec of links (default 'b')
%-
% '[no]base'        Enable display of base 'pedestal'
% 'basecolor',C     Color of base (default 'k')
% 'basewidth',W     Width of base (default 3)
%
% The options come from 3 sources and are processed in order:
% - Cell array of options returned by the function PLOTBOTOPT (if it exists)
% - Cell array of options given by the 'plotopt' option when creating the
%   SerialLink object.
% - List of arguments in the command line.
%
% Many boolean options can be enabled or disabled with the 'no' prefix.  The
% various option sources can toggle an option, the last value is taken.
%
% Graphical annotations and options::
%
% The robot is displayed as a basic stick figure robot with annotations
% such as:
% - shadow on the floor
% - XYZ wrist axes and labels
% - joint cylinders and axes
% which are controlled by options.
%
% The size of the annotations is determined using a simple heuristic from
% the workspace dimensions.  This dimension can be changed by setting the
% multiplicative scale factor using the 'mag' option.
%
% Figure behaviour::
%
% - If no figure exists one will be created and the robot drawn in it.
% - If no robot of this name is currently displayed then a robot will
%   be drawn in the current figure.  If hold is enabled (hold on) then the
%   robot will be added to the current figure.
% - If the robot already exists then that graphical model will be found
%   and moved.
%
% Multiple views of the same robot::
%
% If one or more plots of this robot already exist then these will all
% be moved according to the argument Q.  All robots in all windows with
% the same name will be moved.
%
%  Create a robot in figure 1
%         figure(1)
%         p560.plot(qz);
%  Create a robot in figure 2
%         figure(2)
%         p560.plot(qz);
%  Now move both robots
%         p560.plot(qn)
%
% Multiple robots in the same figure::
%
% Multiple robots can be displayed in the same plot, by using "hold on"
% before calls to robot.plot().
%
%  Create a robot in figure 1
%         figure(1)
%         p560.plot(qz);
%  Make a clone of the robot named bob
%         bob = SerialLink(p560, 'name', 'bob');
%  Draw bob in this figure
%         hold on
%         bob.plot(qn)
%
%  To animate both robots so they move together:
%         qtg = jtraj(qr, qz, 100);
%         for q=qtg'
%           p560.plot(q');
%           bob.plot(q');
%         end
%
% Making an animation movie::
% - The 'movie' options saves frames as files NNNN.png into the specified folder
% - The specified folder will be created
% - To convert frames to a movie use a command like:
%        ffmpeg -r 10 -i %04d.png out.avi
%
% Notes::
% - The options are processed when the figure is first drawn, to make different options come
%   into effect it is neccessary to clear the figure.
% - The link segments do not neccessarily represent the links of the robot, they are a pipe
%   network that joins the origins of successive link coordinate frames.
% - Delay betwen frames can be eliminated by setting option 'delay', 0 or
%   'fps', Inf.
% - By default a quite detailed plot is generated, but turning off labels,
%   axes, shadows etc. will speed things up.
% - Each graphical robot object is tagged by the robot's name and has UserData
%   that holds graphical handles and the handle of the robot object.
% - The graphical state holds the last joint configuration
% - The size of the plot volume is determined by a heuristic for an all-revolute
%   robot.  If a prismatic joint is present the 'workspace' option is
%   required.  The 'zoom' option can reduce the size of this workspace.
%
% See also SerialLink.plot3d, plotbotopt, SerialLink.animate, SerialLink.teach, SerialLink.fkine.

% HANDLES:
%
% A robot comprises a bunch of individual graphical elements and these are
% kept in a structure:
%
%   h.link     the graphical elements that comprise each joint/link
%   h.wrist     the coordinate frame marking the wrist frame
%   h.shadow    the robot's shadow
%   h.floorlevel the z-coordinate of the tiled floor
%   h.robot pointer to the robot object
%   h.opt   the final options structure
%
% The h.link graphical element is tagged with the robot's name and has this
% struct as its UserData.
%
%  h.links -> h -> robot
%
%  This enables us to find all robots with a given name, in all figures,
% and update them.

% Copyright (C) 1993-2015, 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

% TODO
% deal with base transform and tool
% more consistent option names, scale, mag etc.
function plot(robot, qq, varargin)

    % check the joint angle data matches the robot
    n = robot.n;
    if numcols(qq) ~= n
        error('Insufficient columns in q')
    end

    % process options, these come from:
    %  - passed arguments
    %  - the robot object itself
    %  - the file plotopt.m
    opt = plot_options(robot, varargin);

    % logic to handle where the plot is drawn, are old figures updated or
    % replaced?
    %  calls create_floor() and create_robot() as required.

    if strcmp(get(gca,'Tag'), 'RTB.plot')
        % this axis is an RTB plot window

        rhandles = findobj('Tag', robot.name);

        if isempty(rhandles)
            % this robot doesnt exist here, create it or add it

            if ishold
                % hold is on, add the robot, don't change the floor
                handle = create_robot(robot, opt);

                % tag one of the graphical handles with the robot name and hang
                % the handle structure off it
%                 set(handle.joint(1), 'Tag', robot.name);
%                 set(handle.joint(1), 'UserData', handle);
            else
                % create the robot and floor
                newplot();

                if opt.tiles
                    create_tiled_floor(opt);
                end
                handle = create_robot(robot, opt);
                set(gca, 'Tag', 'RTB.plot');
            end

        end

    else
        % this axis never had a robot drawn in it before, let's use it
        if opt.tiles
            create_tiled_floor(opt);
        end
        handle = create_robot(robot, opt);
        set(gca, 'Tag', 'RTB.plot');
        set(gcf, 'Units', 'Normalized');
        pf = get(gcf, 'Position');
        if strcmp( get(gcf, 'WindowStyle'), 'docked') == 0
            set(gcf, 'Position', [0.1 1-pf(4) pf(3) pf(4)]);
        end
    end

    % deal with a few options that need to be stashed in the SerialLink object
    % movie mode has not already been flagged
    if opt.movie
        robot.framenum = 0;
        robot.moviepath = opt.movie;
    else
        robot.framenum = [];
    end
    robot.delay = opt.delay;
    robot.loop = opt.loop;

    if opt.raise
        % note this is a very time consuming operation
        figure(gcf);
    end

    if strcmp(opt.projection, 'perspective')
        set(gca, 'Projection', 'perspective');
    end

    if isstr(opt.view)
        switch opt.view
            case 'top'
                view(0, 90);
            case 'x'
                view(0, 0);
            case 'y'
                view(90, 0)
            otherwise
                error('rtb:plot:badarg', 'view must be: x, y, top')
        end
    elseif isnumeric(opt.view) && length(opt.view) == 2
        view(opt.view)
    end

    % enable mouse-based 3D rotation
    rotate3d on

    if ~isempty(opt.movie)
        mkdir(opt.movie);
        framenum = 1;
    end
    robot.animate(qq);

end

% Create a new graphical robot in the current figure.
% Returns a structure of handles that describe the various graphical entities in the robot model
% Make extensive use of hgtransform, all entities are defined at the origin, then moved to their
% proper pose
%
% The graphical hiearchy is:
%  hggroup: Tag = robot name
%     hgtransform: Tag = 'link#'
%
% The top-level group has user data which is the handle structure.

function h = create_robot(robot, opt)

    %disp('creating new robot');

    links = robot.links;
    s = opt.scale;

    % create an axis
    ish = ishold();
    if ~ishold
        % if hold is off, set the axis dimensions
        axis(opt.workspace);
        hold on
    end

    N = robot.n;

    % create the base
    if opt.base
        bt = transl(robot.base);
        bt = [bt'; bt'];
        bt(1,3) = opt.floorlevel;
        line(bt(:,1), bt(:,2), bt(:,3), 'LineWidth', opt.basewidth, 'Color', opt.basecolor);
    end

    % add the robot's name
    if opt.name
        b = transl(robot.base);
        bz = 0;
        if opt.base
            bz = 0.5*opt.floorlevel;
        end
        text(b(1), b(2)-s, bz, [' ' robot.name], 'FontAngle', 'italic', 'FontWeight', 'bold')
    end
    group = hggroup('Tag', robot.name);
    h.group = group;

    % create the graphical joint and link elements
    for L=1:N
        if opt.debug
            fprintf('create graphics for joint %d\n', L);
        end

        % create the transform for displaying this element (joint cylinder + link)
        h.link(L) = hgtransform('Tag', sprintf('link%d', L), 'Parent', group);

        % create a joint cylinder
        if opt.joints
            % create the body of the joint
            if links(L).isrevolute
                cyl('z', 2*s, opt.jointdiam/2*s*[-1 1], opt.jointcolor, [], 'Parent', h.link(L));
            else
                % create an additional hgtransform for positioning and scaling the prismatic
                % element.  The element is created with unit length.
                h.pjoint(L) = hgtransform('Tag', 'prismatic', 'Parent', h.link(L));
                if links(L).mdh
                    % make the box extend in negative z-dir because scaling factor in animate
                    % must be positive
                    box('z', s, [0 -1], opt.jointcolor, [], 'Parent', h.pjoint(L));
                else
                    box('z', s, [0 1], opt.jointcolor, [], 'Parent', h.pjoint(L));
                end
            end
        end

        % create the body of the link

        % create elements to represent translation between joint frames, ie. the link itself.
        % This is drawn to resemble orthogonal plumbing.
        if robot.mdh
            % modified DH convention
            if L < N
                A = links(L+1).A(0);
                t = transl(A);
                if t(1) ~= 0
                    cyl('x', s, [0 t(1)], opt.linkcolor, [], 'Parent', h.link(L));
                end
                if t(2) ~= 0
                    cyl('y', s, [0 t(2)], opt.linkcolor, [t(1) 0 0], 'Parent', h.link(L));
                end
                if t(3) ~= 0
                    cyl('z', s, [0 t(3)], opt.linkcolor, [t(1) t(2) 0], 'Parent', h.link(L));
                end
            end
        else
            % standard DH convention
            if L > 1
                Ainv = inv(links(L-1).A(0));
                t = transl(Ainv);
                if t(1) ~= 0
                    cyl('x', s, [0 t(1)], opt.linkcolor, [], 'Parent', h.link(L));
                end
                if t(2) ~= 0
                    cyl('y', s, [s t(2)], opt.linkcolor, [t(1) 0 0], 'Parent', h.link(L));
                end
                if t(3) ~= 0
                    cyl('z', s, [s t(3)], opt.linkcolor, [t(1) t(2) 0], 'Parent', h.link(L));
                end
                %line([0 t(1)]', [0 t(2)]', [0 t(3)]', 'Parent', h.link(L));
            end
        end

        if opt.jaxes && opt.jvec
            error('RTB:plot:badopt', 'Can''t specify ''jaxes'' and ''jvec''')
        end
        % create the joint axis line
        if opt.jaxes
            line('XData', [0 0], ...
                'YData', [0 0], ...
                'ZData', 12*s*[-1 1], ...
                'LineStyle', ':', 'Parent', h.link(L));

            % create the joint axis label
            text(0, 0, 12*s, sprintf('q%d', L), 'Parent', h.link(L))
        end
         % create the joint axis vector
        if opt.jvec
            daspect([1 1 1]);
            ha = arrow3([0 0 -12*s], [0 0 20*s]);
            set(ha, 'Parent', h.link(L));

            % create the joint axis label
            text(0, 0, -12*s, sprintf('q%d', L), 'Parent', h.link(L))
        end
    end
    if opt.debug
        fprintf('create graphics for tool\n');
    end
    % display the tool transform if it exists
    h.link(N+1) = hgtransform('Tag', sprintf('link%d', N+1), 'Parent', group);
    tool = eye(4,4);
    if ~robot.mdh
        tool = links(L).A(0);
    end
    if ~isempty(robot.tool)
        tool = tool * robot.tool;
    end
    t = transl(inv(tool));
    if t(1) ~= 0
        cyl('x', s, [0 t(1)], 'r', [], 'Parent', h.link(N+1));
    end
    if t(2) ~= 0
        cyl('y', s, [s t(2)], 'r', [t(1) 0 0], 'Parent', h.link(N+1));
    end
    if t(3) ~= 0
        cyl('z', s, [s t(3)], 'r', [t(1) t(2) 0], 'Parent', h.link(N+1));
    end

    % display the wrist coordinate frame
    if opt.wrist
        if opt.arrow
            h.wrist = trplot(eye(4,4), 'labels', upper(opt.wristlabel), ...
                'arrow', 'rgb', 'length', 15*s);
        else
            h.wrist = trplot(eye(4,4), 'labels', upper(opt.wristlabel), ...
                'rgb', 'length', 15*s);
        end
    else
        h.wrist = [];
    end

    % display a shadow on the floor
    if opt.shadow
        % create the polyline which is the shadow on the floor
        h.shadow = line('LineWidth', opt.shadowwidth, 'Color', opt.shadowcolor);
    end

    if opt.trail
        h.trail = plot(0, 0, opt.trail);
        robot.trail = [];
    end

    % deal with some display options
    if opt.shading
        lighting gouraud
        light('position', opt.lightpos)
    end
    xlabel('X')
    ylabel('Y')
    zlabel('Z')
    grid on

    % restore hold setting
    if ~ish
        hold off
    end
    h.floorlevel = opt.floorlevel;
    h.robot = robot;
    h.opt = opt;

    % attach the handle structure to the top graphical element
    set(group, 'UserData', h);
end

% draw a cylinder of radius r in the direction specified by ax, with an
% extent from extent(1) to extent(2)

function cyl(ax, r, extent, color, offset, varargin)
    if abs(extent(1) - extent(2)) < eps
        return
    end

    if isempty(offset)
        offset = [0 0 0];
    end

    %fprintf('   cyl: %s, r=%f, extent=[%g, %g]\n', ax, r, extent);

    n = 20;

    r = [r;r];

    theta = (0:n)/n*2*pi;
    sintheta = sin(theta); sintheta(n+1) = 0;

    switch ax
        case 'x'
            y = r * cos(theta) + offset(2);
            z = r * sintheta + offset(3);
            x = extent(:) * ones(1,n+1) + offset(1);
        case 'y'
            x = r * cos(theta) + offset(1);
            z = r * sintheta + offset(3);
            y = extent(:) * ones(1,n+1) + offset(2);
        case 'z'
            x = r * cos(theta) + offset(1);
            y = r * sintheta + offset(2);
            z = extent(:) * ones(1,n+1) + offset(3);
    end

    % walls of the cylinder
    surf(x,y,z, 'FaceColor', color, 'EdgeColor', 'none', varargin{:})

    % put the ends on
    patch(x', y', z', color, 'EdgeColor', 'none', varargin{:});
end

% draw a cylinder of radius r in the direction specified by ax, with an
% extent from extent(1) to extent(2)

function box(ax, r, extent, color, offset, varargin)
    if abs(extent(1) - extent(2)) < eps
        return
    end
    %fprintf('   box: %s, r=%f, extent=[%g, %g]\n', ax, r, extent);
    n = 4;

    r = [r;r];

    theta = (0:n)/n*2*pi;
    sintheta = sin(theta); sintheta(n+1) = 0;

    switch ax
        case 'x'
            y = r * cos(theta);
            z = r * sintheta;
            x = extent(:) * ones(1,n+1);
        case 'y'
            x = r * cos(theta);
            z = r * sintheta;
            y = extent(:) * ones(1,n+1);
        case 'z'
            y = r * cos(theta);
            x = r * sintheta;
            z = extent(:) * ones(1,n+1);
    end

    % walls of the cylinder
    surf(x,y,z, 'FaceColor', color, 'EdgeColor', 'none', varargin{:})

    % put the ends on
    patch(x', y', z', color, 'EdgeColor', 'none', varargin{:});
end

% draw a tiled floor in the current axes
function create_tiled_floor(opt)

    xmin = opt.workspace(1);
    xmax = opt.workspace(2);
    ymin = opt.workspace(3);
    ymax = opt.workspace(4);

    % create a colored tiled floor
    xt = xmin:opt.tilesize:xmax;
    yt = ymin:opt.tilesize:ymax;
    Z = opt.floorlevel*ones( numel(yt), numel(xt));
    C = zeros(size(Z));
    [r,c] = ind2sub(size(C), 1:numel(C));
    C = bitand(r+c,1);
    C = reshape(C, size(Z));
    C = cat(3, opt.tile1color(1)*C+opt.tile2color(1)*(1-C), ...
        opt.tile1color(2)*C+opt.tile2color(2)*(1-C), ...
        opt.tile1color(3)*C+opt.tile2color(3)*(1-C));
    [X,Y] = meshgrid(xt, yt);
    surface(X, Y, Z, C, ...
        'FaceColor','texturemap',...
        'EdgeColor','none',...
        'CDataMapping','direct');
end

    % process a cell array of options and return a struct
    % define all possible options and their default values

function opt = plot_options(robot, optin)

    % timing/looping
    opt.delay = 0.1;
    opt.fps = [];
    opt.loop = false;

    opt.raise = false;

    % general appearance
    opt.scale = 1;
    opt.zoom = 1;
    opt.trail = [];

    opt.workspace = [];
    opt.name = true;
    opt.projection = {'ortho', 'perspective'};
    opt.view = [];
    opt.top = false;

    % 3D rendering
    opt.shading = true;
    opt.lightpos = [0 0 20];

    % tiled floor
    opt.tiles = true;
    opt.tile1color = [0.5 1 0.5];  % light green
    opt.tile2color = [1 1 1];  % white
    opt.floorlevel = [];
    opt.tilesize = 0.2;

    % shadow on the floor
    opt.shadow = true;
    opt.shadowcolor = [0.5 0.5 0.5];
    opt.shadowwidth = 6;

    % the base or pedestal
    opt.base = true;
    opt.basewidth = 3;
    opt.basecolor = 'k';

    % wrist
    opt.wrist = true;
    opt.wristlabel = {'xyz', 'noa'};
    opt.arrow = true;

    % joint rotation axes
    opt.jaxes = false;
    opt.jvec = false;

    % joint cylinders
    opt.joints = true;
    opt.jointdiam = 5;
    opt.jointcolor = [0.7 0 0];

    % links
    opt.linkcolor = 'b';
    opt.toolcolor = 'r';

    % misc
    opt.movie = [];

    % build a list of options from all sources
    %   1. the M-file plotbotopt if it exists
    %   2. robot.plotopt
    %   3. command line arguments
    if exist('plotbotopt', 'file') == 2
        options = [plotbotopt robot.plotopt optin];
    else
        options = [robot.plotopt optin];
    end

    % parse the options
    [opt,args] = tb_optparse(opt, options);
    if ~isempty(args)
        error(['unknown option: ' args{1}]);
    end

    if opt.top
        opt.view = 'top';
    end
    if ~isempty(opt.projection)
        opt.projection = 'ortho';
    end

    % figure the size of the figure
    if isempty(opt.workspace)
        %
        % simple heuristic to figure the maximum reach of the robot
        %
        L = robot.links;
        if any(L.isprismatic)
            error('Prismatic joint(s) present: requires the ''workspace'' option');
        end
        reach = 0;
        for i=1:robot.n
            reach = reach + abs(L(i).a) + abs(L(i).d);
        end
        reach = reach + sum(abs(transl(robot.tool)));
        reach = reach/opt.zoom;

        % if we have a floor, quantize the reach to a tile size
        if opt.tiles
            reach = opt.tilesize * ceil(reach/opt.tilesize);
        end

        % now create a 3D volume based on this reach
        opt.workspace = [-reach reach -reach reach -reach reach];

        % if a floorlevel has been given, ammend the 3D volume
        if ~isempty(opt.floorlevel)
            opt.workspace(5) = opt.floorlevel;
        else
            opt.floorlevel = -reach;
        end
    else
        reach = min(abs(diff(reshape(opt.workspace, [2 3]))));
        if opt.tiles
            % set xy limits to be integer multiple of tilesize
            opt.workspace(1:4) = opt.tilesize * round(opt.workspace(1:4)/opt.tilesize);
            opt.floorlevel = opt.workspace(5);
        end
    end

    % update the fundamental scale factor (given by the user as a multiplier) by a length derived from
    % the overall workspace dimension
    %  we need that a lot when creating the robot model
    opt.scale = opt.scale * reach/40;

    if ~isempty(opt.fps)
        opt.delay = 1/opt.fps;
    end

end


%% MRimageUnitScale
% Below is a demonstration of the features of the |MRimageUnitScale| function

%%
clear; close all; clc;

%% Syntax
% |[Nr,Nr_units]=MRimageUnitScale(N,N_info);|

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


function model = setCommonZeroStandardGibbsEnergyOfFormation(model, adjustedMetList)
% Sets all Alberty's cofactor metabolites to have a common thermodynamic baseline.
%
% Sets all the exceptional metabolites to have a common baseline.
% i.e. Standard transformed Gibbs energies of reactants with the baseline adjusted
% for certain paired cofactors e.g. `fad` & `fadh2`, such that the
% difference between the two is the same as in Albertys data but
% the absolute values are consistent with the group contribution data
%
% USAGE:
%
%    model = setCommonZeroStandardGibbsEnergyOfFormation(model, adjustedMetList)
%
% INPUT:
%    model:              Thermodynamic model:
%
%                          * .met(m).dGft0 - standard transformed Gibbs energy of formation(kJ/mol)
%                          * .met(m).dGft0Keq - standard transformed Gibbs energy of formation(kJ/mol)
%                          * .met(m).dGft0Source - origin of data, `Keq` or `groupContFileName.txt`
%                          * .met(m).dGft0GroupCont - group. cont. estimate of standard transformed Gibbs energy of formation(kJ/mol)
%
% OPTIONAL INPUT:
%    adjustedMetList:
% OUTPUT:
%    model:              structure with field:
%
%                          * .met(m).dGft0 - Standard transformed Gibbs energies of reactants
%                            with the baseline adjusted for certain paired
%                            cofactors e.g. `fad` & `fadh2`, such that the
%                            difference between the two is the same as in
%                            Albertys data but the absolute values are
%                            consistent with the group contribution data
%
% .. Author: - Ronan M.T. Fleming
%
% .. Here is the list of cofactors in iAF1260 that have thermodynamic
%    properties backcalculated from Equilibrium values by Alberty
%    The compartments here are important since the same reactant in different
%    compartments may have different properties.

if ~exist('adjustedMetList','var')
%This list of cofactors contains metabolites with own baselines reported
%by Alberty in his 2006 book, plus some metabolites that appear on the
%other side of a reaction from Alberty's set. It is important to keep an
%eye on the latter as they must all be on a common baseline when the
%adjustment is done.
adjustedMetList={'coa','aacoa','accoa','ppcoa','mmcoa_R','mmcoa-R',...
    'succoa','gthrd','gthox','q8h2','q8','fmn','fmnh2','nad',...
    'nadh','nmn','fad','fadh2','nadp','nadph','malcoa'};
end

numChar=1;
[allMetCompartments,uniqueCompartments]=getCompartment(model.mets,numChar);

for p=1:length(uniqueCompartments)

    n=1;
    leftAll{n}=['fad' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['fadh2' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['q8' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['q8h2' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['nad' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['nadh' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['nad' '[' uniqueCompartments{p} ']']; % it is essential that the nad nmn pair are in this position
    rightAll{n}=['nmn' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['nadp' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['nadph' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['gthox' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['gthrd' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['fmn' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['fmnh2' '[' uniqueCompartments{p} ']'];
    n=n+1;
    %here is the list of cofactors in E. coli which are covered by Albertys data
    %see:
    %Standard transformed Gibbs energies of coenzyme A derivatives as
    %functions of pH and ionic strength
    %R.A. Alberty / Biophysical Chemistry 104 (2003) 327�334
    coAset={['coa[' uniqueCompartments{p} ']'],...
            ['aacoa[' uniqueCompartments{p} ']'],...
            ['accoa[' uniqueCompartments{p} ']'],...
            ['mmcoa-R[' uniqueCompartments{p} ']'],...
            ['mmcoa-S[' uniqueCompartments{p} ']'],...
            ['ppcoa[' uniqueCompartments{p} ']'],...
            ['succoa[' uniqueCompartments{p} ']'],...
            ['malcoa[' uniqueCompartments{p} ']']};
    leftAll{n}=[];n=n+1;
    leftAll{n}=['ppcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['succoa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['ppcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['mmcoa-S' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['ppcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=[];n=n+1;
    leftAll{n}=['succoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['succoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['mmcoa-R' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['malcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['malcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['accoa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=[];n=n+1;
    leftAll{n}=['dpcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['dpcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['2tpr3dpcoa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['dpcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['pan4p' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=[];n=n+1;
    leftAll{n}=['sbzcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['aacoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    % Note this reaction that contains three cofactors:
    % KAT1, 3-ketoacyl-CoA thiolase [c] : aacoa + coa --> (2) accoa
    leftAll{n}=['accoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['accoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['aacoa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    %check 'mmcoa-R'
    % methylmalonyl-CoA epimerase
    % equation:	[c] : mmcoa-R <==> mmcoa-S

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    [nMet,nRxn]=size(model.S);

    exceptionMetBool=false(nMet,1);
    for m=1:nMet
        abbr=model.mets{m};
        abbr=abbr(1:end-3);
        if any(strcmp(abbr,adjustedMetList))
            exceptionMetBool(m)=1;
        end
    end
    fprintf('\n%s\n%s\n','Setting certain Alberty reactants to have a common thermodynamic',...
        'baseline so that this data can be incorporated with group contribution data.')
    fprintf('%s\n','Before baseline edit of Cofactors:')
    fprintf('%s\n','Metabolite properties:')
    %print out exceptional metabolite data before edit to baseline
    fprintf('%10s\t%20s\t%s\t\t%s\t\t%s\n','abbr','albertyAbbr','dGft0Keq','dGft0gc','dGft0');
    for m=1:nMet
        if exceptionMetBool(m)
            fprintf('%10s\t%20s\t%8.4g\t\t%8.4g\t\t%8.4g\n',model.mets{m},model.met(m).albertyAbbreviation,model.met(m).dGft0Keq,model.met(m).dGft0GroupCont,model.met(m).dGft0);
        end
    end
    fprintf('\n\n')

    fprintf('\n\n');
    fprintf('%s\n','Differences between pairs of cofactors (cytoplasm only):')
    fprintf('%22s%10s%10s%10s%10s%10s%16s%16s\n','left-right','#RxnLeft','#RxnRight','#Both','#LNotR','#RNotL','dGtr0Keq','dGrt0');
    for x=1:length(leftAll)
        abbrL=leftAll{x};
        abbrL=[abbrL(1:end-3) '[c]'];
        abbrR=rightAll{x};
        abbrR=[abbrR(1:end-3) '[c]'];

        nCoFcLRxn=length(find(model.S(strcmp(model.mets,abbrL),:)~=0));
        nCoFcRRxn=length(find(model.S(strcmp(model.mets,abbrR),:)~=0));
        nCoFc2Rxn=length(intersect(find(model.S(strcmp(model.mets,abbrL),:)~=0),find(model.S(strcmp(model.mets,abbrR),:)~=0)));
        nCoLeftOnly=length(setdiff(find(model.S(strcmp(model.mets,abbrL),:)~=0),find(model.S(strcmp(model.mets,abbrR),:)~=0)));
        nCoRightOnly=length(setdiff(find(model.S(strcmp(model.mets,abbrR),:)~=0),find(model.S(strcmp(model.mets,abbrL),:)~=0)));

        %nothing to print out if no match
        if any(strcmp(model.mets,abbrL))  && any(strcmp(model.mets,abbrR))
            fprintf('%22s%10i%10i%10i%10i%10i\t%8.4g\t%8.4g\n',[abbrL '-' abbrR],nCoFcLRxn,nCoFcRRxn,nCoFc2Rxn,nCoLeftOnly,nCoRightOnly,...
                model.met(strcmp(model.mets,abbrL)).dGft0Keq-model.met(strcmp(model.mets,abbrR)).dGft0Keq,...
                model.met(strcmp(model.mets,abbrL)).dGft0-model.met(strcmp(model.mets,abbrR)).dGft0);
        end
    end

%     fprintf('%s\n','Differences between pairs of cofactors:')
%     fprintf('%20s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n','left-right','#RxnLeft','#RxnRight','#Both','#LNotR','#RNotL','dGt0Keq','dGt0gc');
%     xx=0;
%     for x=1:length(leftAll)
%         if isempty(leftAll{x})
%             %         fprintf('\n');
%         else
%             nCoFcLRxn=length(find(model.S(strcmp(model.mets,leftAll{x}),:)~=0));
%             nCoFcRRxn=length(find(model.S(strcmp(model.mets,rightAll{x}),:)~=0));
%             nCoFc2Rxn=length(intersect(find(model.S(strcmp(model.mets,leftAll{x}),:)~=0),find(model.S(strcmp(model.mets,rightAll{x}),:)~=0)));
%             nCoLeftOnly=length(setdiff(find(model.S(strcmp(model.mets,leftAll{x}),:)~=0),find(model.S(strcmp(model.mets,rightAll{x}),:)~=0)));
%             nCoRightOnly=length(setdiff(find(model.S(strcmp(model.mets,rightAll{x}),:)~=0),find(model.S(strcmp(model.mets,leftAll{x}),:)~=0)));
%
%             %nothing to print out if no match
%             if any(strcmp(model.mets,leftAll{x}))  && any(strcmp(model.mets,rightAll{x}))
%                 fprintf('%20s\t\t%i\t\t\t%i\t\t%i\t\t%i\t\t%i\t\t%8.4g\t\t%8.4g\n',[leftAll{x} '-' rightAll{x}],nCoFcLRxn,nCoFcRRxn,nCoFc2Rxn,nCoLeftOnly,nCoRightOnly,...
%                     model.met(strcmp(model.mets,leftAll{x})).dGft0Keq-model.met(strcmp(model.mets,rightAll{x})).dGft0Keq,...
%                     model.met(strcmp(model.mets,leftAll{x})).dGft0GroupCont-model.met(strcmp(model.mets,rightAll{x})).dGft0GroupCont);
%             end
%             a=1;
%         end
%     end

    %edit the baseline of a cofactor that is not always used in a pair
    %with another cofactor
    coaSorted=0;
    x=1;
    while x<=length(leftAll)
         abbrL=leftAll{x};
         abbrR=rightAll{x};

        pause(eps)
        if strcmp('coa[c]',abbrR)
            pause(eps)
        end
        %check that both left and right cofactors are always used in the network
        %together because there is no need to change if they are always used
        %together on different sides of the reaction
        if any(strcmp(abbrL,model.mets)) && any(strcmp(abbrR,model.mets))
            %check if coa is involved since coa is a special case that is not a
            %simple cofactor pair
            if ~(any(strcmp(abbrL,coAset) | any(strcmp(abbrR,coAset))))
                nCoLeftOnly=length(setdiff(find(model.S(strcmp(model.mets,abbrL),:)~=0),find(model.S(strcmp(model.mets,abbrR),:)~=0)));
                nCoRightOnly=length(setdiff(find(model.S(strcmp(model.mets,abbrR),:)~=0),find(model.S(strcmp(model.mets,abbrL),:)~=0)));
                %identify which one in each cofactor pair is synthesised/used in
                %another reaction, besides just being used in reactions with its
                %paired cofactor
                if nCoLeftOnly==0 && nCoRightOnly~=0
                    %calculate the difference between the group contribution
                    %estimate and Albertys value
                    baseLineChange=model.met(strcmp(model.mets,abbrR)).dGft0GroupCont-model.met(strcmp(model.mets,abbrR)).dGft0Keq;
                    for m=1:nMet
                        abbr=model.mets{m};
                        if any(strcmp(model.mets{m},{abbrL,abbrR}))
                            %only change if we are using Albertys data
                            if strcmp(model.met(m).dGft0Source,'Keq')
                                model.met(m).dGft0=model.met(m).dGft0+baseLineChange;
                            end
                        end
                    end
                end

                if nCoLeftOnly~=0 && nCoRightOnly==0
                    %calculate the difference between the group contribution
                    %estimate and Albertys value for the left cofactor
                    baseLineChange=model.met(strcmp(model.mets,abbrL)).dGft0GroupCont-model.met(strcmp(model.mets,abbrL)).dGft0Keq;
                    for m=1:nMet
                        if any(strcmp(model.mets{m},{abbrL,abbrR})) % change baseline for both cofactors
                            %only change if we are using Albertys data
                            if strcmp(model.met(m).dGft0Source,'Keq')
                                model.met(m).dGft0=model.met(m).dGft0+baseLineChange;
                            end
                        end
                    end
                    if strcmp(abbrL,['nad' '[' uniqueCompartments{p} ']']) && strcmp(abbrR,['nadh' '[' uniqueCompartments{p} ']']) && any(strcmp(model.mets,['nmn' '[' uniqueCompartments{p} ']'] ))
                        fprintf('%s\n',['Assuming Albertys value for nmn is set to nad baseline']);
                        for m=1:nMet
                            if any(strcmp(model.mets{m},['nmn' '[' uniqueCompartments{p} ']']))
                                %only change if we are using Albertys data
                                if strcmp(model.met(m).dGft0Source,'Keq')
                                    model.met(m).dGft0=model.met(m).dGft0+baseLineChange;
                                end
                            end
                        end
                        %ensure that nad baseline is not changed twice due to nad also
                        %appearing as a cofactor with nmn
                        %skip the next pair as it is nad[c] & nmn[c]
                        x=x+1;
                    end
                end

            else
                %for the cofactors associated with coa by Alberty, then set coA to
                %have the same baseline as the group contribution data
                if coaSorted==0
                    pause(eps)
                    for s=1:length(coAset)
                        %Changed to make suitable for multiple compartments May 24th 2011
                        baseLineChange=model.met(strcmp(model.mets,['coa[' uniqueCompartments{p} ']'])).dGft0GroupCont-model.met(strcmp(model.mets,['coa[' uniqueCompartments{p} ']'])).dGft0Keq;
                        for m=1:nMet
                            if strcmp(coAset{s},model.mets{m})
                                %only change if we are using Albertys data
                                if strcmp(model.met(m).dGft0Source,'Keq')
                                    model.met(m).dGft0=model.met(m).dGft0+baseLineChange;
                                end
                            end
                        end
                    end
                    coaSorted=1;
                end
            end
        end
        x=x+1;
    end
    %compartment by compartment
    coASorted=0;
end

fprintf('\n\n')
fprintf('%s\n','After baseline edit of Cofactors:')
fprintf('%s\n','Metabolite properties:')
fprintf('%10s\t%30s\t%s\t%s\t%s\n','abbr','albertyAbbr','dGft0Keq',' dGft0gc','   dGft0');
%print out the new changes
for m=1:nMet
    if strcmp('nad[c]',model.mets{m})
        pause(eps)
    end
    if exceptionMetBool(m)
        fprintf('%10s\t%30s\t%8.4g\t%8.4g\t%8.4g\n',model.mets{m},model.met(m).albertyAbbreviation,model.met(m).dGft0Keq,model.met(m).dGft0GroupCont,model.met(m).dGft0);
    end
end

fprintf('\n\n');
fprintf('%s\n','Differences between pairs of cofactors, after baseline edit (cytoplasm only):')
fprintf('%22s%10s%10s%10s%10s%10s%16s%16s\n','left-right','#RxnLeft','#RxnRight','#Both','#LNotR','#RNotL','dGtr0Keq','dGrt0');
for x=1:length(leftAll)
    abbrL=leftAll{x};
    abbrL=[abbrL(1:end-3) '[c]'];
    abbrR=rightAll{x};
    abbrR=[abbrR(1:end-3) '[c]'];

    nCoFcLRxn=length(find(model.S(strcmp(model.mets,abbrL),:)~=0));
    nCoFcRRxn=length(find(model.S(strcmp(model.mets,abbrR),:)~=0));
    nCoFc2Rxn=length(intersect(find(model.S(strcmp(model.mets,abbrL),:)~=0),find(model.S(strcmp(model.mets,abbrR),:)~=0)));
    nCoLeftOnly=length(setdiff(find(model.S(strcmp(model.mets,abbrL),:)~=0),find(model.S(strcmp(model.mets,abbrR),:)~=0)));
    nCoRightOnly=length(setdiff(find(model.S(strcmp(model.mets,abbrR),:)~=0),find(model.S(strcmp(model.mets,abbrL),:)~=0)));

    %nothing to print out if no match
    if any(strcmp(model.mets,abbrL))  && any(strcmp(model.mets,abbrR))
        fprintf('%22s%10i%10i%10i%10i%10i\t%8.4g\t%8.4g\n',[abbrL '-' abbrR],nCoFcLRxn,nCoFcRRxn,nCoFc2Rxn,nCoLeftOnly,nCoRightOnly,...
            model.met(strcmp(model.mets,abbrL)).dGft0Keq-model.met(strcmp(model.mets,abbrR)).dGft0Keq,...
            model.met(strcmp(model.mets,abbrL)).dGft0-model.met(strcmp(model.mets,abbrR)).dGft0);
    end
end

%OLD CODE -may still be usefull for looking at nullspace of internal
%reactions that are also involve cofactors
% if ~exist('exceptions','var')
%     exceptions={'coa','aacoa','accoa','ppcoa','mmcoa_R','mmcoa-R',...
%         'succoa','gthrd','gthox','q8h2','q8','fmn','fmnh2','nad',...
%         'nadh','fad','fadh2','nadp','nadph','malcoa'};
% end
%
%
% [nMet,nRxn]=size(model.S);
%
% if ~isfield(model,'biomassRxnAbbr')
%     if ~exist('biomassRxnAbbr')
%         fprintf('\n%s\n','...checkObjective');
%         objectiveAbbr=checkObjective(model);
%         fprintf('%s\n',['Asumming objective is ' objectiveAbbr]);
%         model.biomassRxnAbbr=objectiveAbbr;
%     else
%         model.biomassRxnAbbr=biomassRxnAbbr;
%     end
% end
%
% %finds the reactions in the model which export/import from the model
% %boundary
% %e.g. Exchange reactions
% %     Demand reactions
% %     Sink reactions
% model=findSExRxnInd(model);
% %OUTPUT
% % model.SIntRxnInd          indices of internal reactions
% % model.SExRxnInd           indices of boundary reactions
% intRxnBool=false(1,nRxn);
% intRxnBool(model.SIntRxnInd)=1;
%

%
% %extract subnetwork
% modelE=model;
% %reactions
% exceptionRxnBool=logical(sign(sum(abs(model.S(exceptionMetBool,:)),1)));
% %only keep internal reactions involving exceptional metabolites
% modelE.S=modelE.S(:,exceptionRxnBool & intRxnBool);
% modelE.rxns=modelE.rxns(exceptionRxnBool & intRxnBool);
% modelE.lb=modelE.lb(exceptionRxnBool & intRxnBool);
% modelE.ub=modelE.ub(exceptionRxnBool & intRxnBool);
% %metabolites
% keepMetBool=false(nMet,1);
% for m=1:nMet
%     if nnz(modelE.S(m,:))~=0
%         keepMetBool(m,1)=1;
%     end
% end
% modelE.S=modelE.S(keepMetBool,:);
% modelE.mets=modelE.mets(keepMetBool);
%
% exceptionMetBool=false(nMet,1);
% for m=1:nMet
%     abbr=model.mets{m};
%     if any(strcmp(abbr(end-2:end),'[c]'))
%         abbr=abbr(1:end-3);
%         if any(strcmp(abbr,exceptions))
%             exceptionMetBool(m)=1;
%         end
%     end
% end


classdef prtBrvMixtureMcmcTraining

    properties

         randnState = randn('seed');
         randState = rand('seed');
         startTime = now;
         endTime = [];

         membershipModels
         componentMemberships
         componentLogLikelihoods
         componentLogLikelihoodsInModel

         iterations = struct('logLikelihood',[]);
    end
    methods
        function self = prtBrvMixtureVbTraining(varargin)
            self = prtBrvMixtureMcmcTraining(self,varargin{:});
        end
    end
end


function [outexpdir] = ConvertMWT2Trx(expdir,rootoutputdir,varargin)

%% set parameters

% default parameters
blobsext = 'blobs';
datext = 'dat';
spineext = 'spine';
trxfilestr = 'trx.mat';
perframedirstr = 'perframe';
MINDT = .001;
MAXTIMESTAMPERR = .01;
DEBUG = false;
makesoftlink = true;

perframedict = {'x','x_mm'
  'y','y_mm'
  'area','area_mm'};
trxfns = {'x','y','a','b','theta','x_mm','y_mm','a_mm','b_mm','theta_mm'};

[~,experiment_name] = myfileparts(expdir);

[blobsext,datext,spineext,trxfilestr,perframedirstr,...
  DEBUG,makesoftlink,perframedict,trxfns] = ...
  myparse(varargin,...
  'blobsext',blobsext,...
  'datext',datext,...
  'spineext',spineext,...
  'trxfilestr',trxfilestr,...
  'perframedirstr',perframedirstr,...
  'debug',DEBUG,...
  'makesoftlink',makesoftlink,...
  'perframedict',perframedict,...
  'trxfns',trxfns);

%% read in blobs files

% find blobs files
files = dir(fullfile(expdir,[experiment_name,'*.',blobsext]));
blobsnames = cellfun(@(x) fullfile(expdir,x),{files.name},'UniformOutput',false);

if isempty(blobsnames),
  error('Could not find any blobs files in %s',expdir);
end
fprintf('Reading %d blobs files...\n',numel(blobsnames));
trx = ReadMWTBlobFile(blobsnames);
nflies = numel(trx);

% make sure all the timestamps agree with each other
timestamps = unique([trx.timestamp]);
dts = diff(timestamps);
% sanity check to look for rounding errors
if any(dts < MINDT),
  error('Rounding error check failed: there are timestamps < %f apart',MINDT);
end

% add dt, fps
fps = nanmean(dts);
for i = 1:nflies,
  trx(i).dt = diff(trx(i).timestamp);
  trx(i).fps = fps;
end

%% read in spine files

haveremoved = false;
removeid = false(1,numel(trx));
trxids = [trx.id];

% find dat files
files = dir(fullfile(expdir,[experiment_name,'*.',spineext]));
if isempty(files),
  warning('Spine file does not exist in directory %s',expdir);
else
  spinename = fullfile(expdir,files(1).name);

  fprintf('Reading spine file %s...\n',spinename);

  % read in data
  datacurr = ReadChoreSpineFile(spinename);

  % match up with identities
  dataids = [datacurr.id];
  newremoveid = ~ismember(trxids,dataids);
  if haveremoved && any(newremoveid ~= removeid),
    warning('removeid and newremoveid do not match');
  end
  removeid = removeid | newremoveid;
  haveremoved = true;
  [oldid,idx] = ismember(dataids,trxids);
  if any(~oldid),
    warning('ids found in spine file %s that are not in trx',spinename);
    idx(~oldid) = [];
  end
  for jj = 1:numel(idx),
    j = idx(jj);
    [~,f0] = min(abs(trx(j).timestamp-datacurr(jj).timestamp(1)));
    [~,f1] = min(abs(trx(j).timestamp-datacurr(jj).timestamp(end)));
    if f1-f0+1 ~= size(datacurr(jj).xspine,2),
      error('timestamps don''t match up for spine');
    end
    maxerr = max(abs(trx(j).timestamp(f0:f1)-datacurr(jj).timestamp));
    if maxerr > MAXTIMESTAMPERR,
      error('timestamps don''t match up for spine');
    end
    trx(j).xspine_mm = nan(size(datacurr(jj).xspine,1),trx(j).nframes);
    trx(j).yspine_mm = nan(size(datacurr(jj).xspine,1),trx(j).nframes);
    trx(j).xspine_mm(:,f0:f1) = datacurr(jj).xspine;
    trx(j).yspine_mm(:,f0:f1) = datacurr(jj).yspine;
  end

end

%% read in dat files

% find dat files
files = dir(fullfile(expdir,[experiment_name,'*.',datext]));
datnames = {};
perframefns = cell(0,2);

for i = 1:numel(files),
  match = regexp(files(i).name,['\.(.+)\.',datext,'$'],'tokens','once');
  if isempty(match),
    continue;
  end
  datnames{end+1} = fullfile(expdir,files(i).name); %#ok<AGROW>
  fn = match{1};
  j = find(strcmp(fn,perframedict(:,1)),1);
  if ~isempty(j),
    fn = perframedict{j,2};
  end
  if ismember(fn,trxfns),
    perframefn = fn;
  else
    perframefn = ['perframe_',fn];
  end
  perframefns(end+1,:) = {fn,perframefn}; %#ok<AGROW>

  fprintf('Reading in dat file %s...\n',datnames{end});

  % read in data
  datacurr = ReadChoreDatFile(datnames{end});

  % match up with identities
  dataids = [datacurr.id];
  newremoveid = ~ismember(trxids,dataids);
  if haveremoved && any(newremoveid ~= removeid),
    warning('removeid and newremoveid do not match');
  end
  removeid = removeid | newremoveid;
  haveremoved = true;
  [oldid,idx] = ismember(dataids,trxids);
  if any(~oldid),
    warning('ids found in dat file %s that are not in trx',datnames{end});
    idx(~oldid) = [];
  end
  for jj = 1:numel(idx),
    j = idx(jj);
    [~,f0] = min(abs(trx(j).timestamp-datacurr(jj).timestamp(1)));
    [~,f1] = min(abs(trx(j).timestamp-datacurr(jj).timestamp(end)));
    if f1-f0+1 ~= numel(datacurr(jj).value),
      error('timestamps don''t match up for %s',fn);
    end
    maxerr = max(abs(trx(j).timestamp(f0:f1)-datacurr(jj).timestamp));
    if maxerr > MAXTIMESTAMPERR,
      error('timestamps don''t match up for %s',fn);
    end
    trx(j).(perframefn) = nan(1,trx(j).nframes);
    trx(j).(perframefn)(f0:f1) = datacurr(jj).value;
  end

end

if any(removeid),
  trx(removeid) = [];
end

% replace first frames with nans
for i = 1:size(perframefns,1),
  fn = perframefns{i,2};
  off = inf;
  for j = 1:numel(trx),
    if isempty(trx(j).(fn)),
      continue;
    end
    if trx(j).(fn)(1) ~= 0,
      off = 0;
      break;
    end
    if all(trx(j).(fn) == 0),
      continue;
    end
    k = find(trx(j).(fn) ~= 0,1);
    off = min(off,k-1);
  end
  if off > 0,
    fprintf('First %d frames of data for %s are always 0, setting to nan\n',off,fn);
  end
  for j = 1:numel(trx),
    trx(j).(fn)(1:min(numel(trx(j).(fn)),off)) = nan;
  end
end

% replace last frames with nans
for i = 1:size(perframefns,1),
  fn = perframefns{i,2};
  off = inf;
  for j = 1:numel(trx),
    if isempty(trx(j).(fn)),
      continue;
    end
    if trx(j).(fn)(end) ~= 0,
      off = 0;
      break;
    end
    if all(trx(j).(fn) == 0),
      continue;
    end
    k = find(trx(j).(fn) ~= 0,1,'last');
    off = min(off,numel(trx(j).(fn))-k);
  end
  if off > 0,
    fprintf('Last %d frames of data for %s are always 0, setting to nan\n',off,fn);
  end
  for j = 1:numel(trx),
    trx(j).(fn)(max(1,numel(trx(j).(fn))-off+1):end) = nan;
  end
end

%% compute pxpermm

pxpermm = nanmean([trx.x trx.y] ./ [trx.x_mm trx.y_mm]);
for i = 1:numel(trx),
  trx(i).pxpermm = pxpermm;
  if ~isfield(trx,'x_mm'),
    trx(i).x_mm = trx(i).x / pxpermm;
  end
  if ~isfield(trx,'y_mm'),
    trx(i).y_mm = trx(i).y / pxpermm;
  end
  trx(i).a_mm = trx(i).a / pxpermm;
  trx(i).b_mm = trx(i).b / pxpermm;
  trx(i).theta_mm = trx(i).theta;
  trx(i).xspine = trx(i).xspine_mm * pxpermm;
  trx(i).yspine = trx(i).yspine_mm * pxpermm;
end

%% plot the trajectories

if DEBUG,

ncontours = numel(trx);
nframes = numel(timestamps);
colors = jet(ncontours)*.7;
for t = 1:nframes,
  hold off;
  for i = 1:ncontours,
    if t > trx(i).endframe || t < trx(i).firstframe,
      continue;
    end
    j = t + trx(i).off;
    plot(trx(i).xcontour{j},trx(i).ycontour{j},'.-','color',colors(i,:));
    hold on;
    drawellipse(trx(i).x(j),trx(i).y(j),trx(i).theta(j),trx(i).a(j)*2,trx(i).b(j)*2,'color','r');
  end
  axis equal;
  drawnow;
end

end

%% create output directory

fprintf('Saving...\n');

% make sure the root output directory exists
if ~exist(rootoutputdir,'dir'),
  mkdir(rootoutputdir);
end

% create the experiment directory
outexpdir = fullfile(rootoutputdir,experiment_name);
if ~exist(outexpdir,'dir'),
  mkdir(outexpdir);
end

%% create per-frame directory

perframedir = fullfile(outexpdir,perframedirstr);
if ~exist(perframedir,'dir'),
  mkdir(perframedir);
end

%% save the per-frame data

for i = 1:size(perframefns,1),
  fn = perframefns{i,1};
  perframefn = perframefns{i,2};
  if ~ismember(fn,trxfns),
    data = {trx.(perframefn)}; %#ok<NASGU>
    % units not set yet
    units = parseunits('unit'); %#ok<NASGU>
    outfile = fullfile(perframedir,[fn,'.mat']);
    save(outfile,'data','units');
    trx = rmfield(trx,perframefn);
  end
end

for i = 1:numel(trxfns),
  fn = trxfns{i};
  if isfield(trx,fn),
    data = {trx.(fn)}; %#ok<NASGU>
    % units not set yet
    units = parseunits('unit'); %#ok<NASGU>
    outfile = fullfile(perframedir,[fn,'.mat']);
    save(outfile,'data','units');
  end
end
%% create the trx file

outmatname = fullfile(outexpdir,trxfilestr);
save(outmatname,'trx');