function model=getKEGGModelForOrganism(organismID,fastaFile,dataDir,...
    outDir,keepSpontaneous,keepUndefinedStoich,keepIncomplete,...
    keepGeneral,cutOff,minScoreRatioKO,minScoreRatioG,maxPhylDist,...
    nSequences,seqIdentity,globalModel)
% getKEGGModelForOrganism
%   Reconstructs a genome-scale metabolic model based on protein homology
%   to the orthologies in KEGG. If the target species is not available in
%   KEGG, the user must select a closely related species. It is also
%   possible to circumvent protein homology search (see fastaFile parameter
%   for more details)
%
%   Input:
%   organismID          three or four letter abbreviation of the organism
%                       (as used in KEGG). If not available, use a closely
%                       related species. This is used for determing the
%                       phylogenetic distance. Use 'eukaryotes' or
%                       'prokaryotes' to get a model for the whole domain.
%                       Only applicable if fastaFile is empty, i.e. no
%                       homology search should be performed
%   fastaFile           a FASTA file that contains the protein sequences of
%                       the organism for which to reconstruct a model (opt,
%                       if no FASTA file is supplied then a model is
%                       reconstructed based only on the organism
%                       abbreviation. This option ignores all settings
%                       except for keepSpontaneous, keepUndefinedStoich,
%                       keepIncomplete and keepGeneral)
%   dataDir             directory for which to retrieve the input data.
%                       Should contain a combination of these sub-folders:
%                       -dataDir\keggdb
%                           The KEGG database files used in 1a (see below)
%                       -dataDir\fasta
%                           The multi-FASTA files generated in 1b (see
%                           below)
%                       -dataDir\aligned
%                           The aligned FASTA files as generated in 2a (see
%                           below)
%                       -dataDir\hmms
%                           The hidden Markov models as generated in 2b or
%                           downloaded from BioMet Toolbox (see below)
%                       The final directory in dataDir should be styled as
%                       proXXX_keggYY or eukXXX_keggYY, indicating whether
%                       the HMMs were trained on pro- or eukaryotic
%                       sequences, using a sequence similarity threshold of
%                       XXX %, fitting the KEGG version YY. E.g.
%                       euk90_kegg105. (opt, see note about fastaFile. Note
%                       that in order to rebuild the KEGG model from a
%                       database dump, as opposed to using the version
%                       supplied with RAVEN, you would still need to supply
%                       this)
%   outDir              directory to save the results from the quering of
%                       the hidden Markov models. The output is specific
%                       for the input sequences and the settings used. It
%                       is stored in this manner so that the function can
%                       continue if interrupted or if it should run in
%                       parallel. Be careful not to leave output files from
%                       different organisms or runs with different settings
%                       in the same folder. They will not be overwritten
%                       (opt, default is a temporary dir where all *.out
%                       files are deleted before and after doing the
%                       reconstruction)
%   keepSpontaneous     include reactions labeled as "spontaneous". (opt,
%                       default true)
%   keepUndefinedStoich	include reactions in the form n A <=> n+1 A. These
%                       will be dealt with as two separate metabolites
%                       (opt, default true)
%   keepIncomplete      include reactions which have been labelled as
%                       "incomplete", "erroneous" or "unclear" (opt,
%                       default true)
%   keepGeneral         include reactions which have been labelled as
%                       "general reaction". These are reactions on the form
%                       "an aldehyde <=> an alcohol", and are therefore
%                       unsuited for modelling purposes. Note that not all
%                       reactions have this type of annotation, and the
%                       script will therefore not be able to remove all
%                       such reactions (opt, default false)
%   cutOff              significance score from HMMer needed to assign
%                       genes to a KO (opt, default 10^-50)
%   minScoreRatioG      a gene is only assigned to KOs for which the score
%                       is >=log(score)/log(best score) for that gene. This
%                       is to prevent that a gene which clearly belongs to
%                       one KO is assigned also to KOs with much lower
%                       scores (opt, default 0.8 (lower is less strict))
%   minScoreRatioKO     ignore genes in a KO if their score is
%                       <log(score)/log(best score in KO). This is to
%                       "prune" KOs which have many genes and where some are
%                       clearly a better fit (opt, default 0.3 (lower is
%                       less strict))
%   maxPhylDist         -1: only use sequences from the same domain
%                       (Prokaryota, Eukaryota)
%                       other (positive) value: only use sequences for
%                       organisms where the phylogenetic distance is at the
%                       most this large (as calculated in getPhylDist)
%                       (opt, default Inf, which means that all sequences
%                       will be used)
%   nSequences          for each KO, use up to this many sequences from the
%                       most closely related species. This is mainly to
%                       speed up the alignment process for KOs with very
%                       many genes. This subsampling is performed before
%                       running CD-HIT (opt, default inf)
%   seqIdentity         sequence identity threshold in CD-HIT, referred as
%                       "global sequence identity" in CD-HIT User's Guide.
%                       If -1 is provided, CD-HIT is skipped (opt, default 0.9)
%   globalModel         structure containing both model and KOModel
%                       structures as generated by getModelFromKEGG. These
%                       will otherwise be loaded by via getModelFromKEGG.
%                       Providing globalKEGGmodel can speed up model
%                       generation if getKEGGModelForOrganism is run
%                       multiple times for different strains. Example:
%                       [globalModel.model,globalModel.KOModel] = getModelFromKEGG;
%                       (opt, default empty, global model is loaded by
%                       getModelFromKEGG)
%
%   Output:
%   model               the reconstructed model
%
%   PLEASE READ THIS: The input to this function can be confusing, because
%   it is intended to be run in parallel on a cluster or in multiple
%   sessions. It therefore saves a lot of intermediate results to storage.
%   This also serves the purpose of not having to do redundant
%   calculations. This, however, comes with the disadvantage of somewhat
%   trickier handling. This is what this function does:
%
%   1a. Loads files from a local KEGG FTP dump and constructs a general
%       RAVEN model representing the metabolic network. The functions
%       getRxnsFromKEGG, getGenesFromKEGG, getMetsFromKEGG summarise the
%       data into 'keggRxns.mat', 'keggGenes.mat' and 'keggMets.mat' files,
%       which are later merged into 'keggModel.mat' by getModelFromKEGG
%       function. The function getPhylDist generates 'keggPhylDist.mat'
%       file. KEGG FTP access requires a license.
%   1b. Generates protein FASTA files from the KEGG FTP dump (see 1a). One
%       multi-FASTA file for each KO in KEGG is generated.
%
%   The Step 1 has to be re-done every time KEGG updates their database (or
%   rather when the updates are large enough to warrant re-running this
%   part). Many users would probably never use this feature.
%
%   2a. Filters KO-specific protein sets. This is done by using the
%       settings "maxPhylDist" and "nSequences" to control which sequences
%       should be used for constructing Hidden Markov models (HMMs), and
%       later for matching your sequences to.
%       The most common alternatives here would be to use sequences from
%       only eukaryotes, only prokaryotes or all sequences in KEGG, but you
%       could also play around with the parameters to use e.g. only fungal
%       sequences.
%   2b. KO-specific protein FASTA files are re-organised into
%       non-redundant protein sets with CD-HIT. The user can only set
%       seqIdentity parameter, which corresponds to '-c' parameter in
%       CD-HIT, described as "sequence identity threshold". CD-HIT suggsted
%       sequence identity specific word_length (-n) parameters are used.
%   2c. Does a multi sequence alignment for multi-FASTA files obtained in
%       Step 2b for future use. MAFFT software with automatic selection of
%       alignment algorithm is used in this step ('--auto').
%   2d. Trains hidden Markov models using HMMer for each of the aligned
%       KO-specific FASTA files obtained in Step 2c. This is performed with
%       'hmmbuild' using the default settings.
%
%   Step 2 may be reasonable to be re-done if the user wants to tweak the
%   settings in proteins filtering, clustering, multi sequence alignment or
%   HMMs training steps. However, it requires to have KO-specific protein
%   FASTA files obtained in Step 1a. As such files are not provided in
%   RAVEN and BioMet ToolBox, the user can only generate these files from
%   KEGG FTP dump files, so KEGG FTP license is needed.
%
%   3a. Queries the HMMs with sequences for the organism you are making a
%       model for. This step uses both the output from step 1a and from 2d.
%       This is done with 'hmmsearch' function under default settings. The
%       significance threshold value set in 'cutOff' parameter is used
%       later when parsing '*.out' files to filter out KO hits with higher
%       value than 'cutOff' value. The results with passable E values are
%       summarised into KO-gene occurence matrix with E values in
%       intersections as 'koGeneMat'. The parameters 'minScoreRatioG' and
%       'minScoreRatioKO' are then applied to 'prune' KO-gene associations
%       (see the function descriptions above for more details). The
%       intersection values for these 'prunable' associations are converted
%       to zeroes.
%   3b. Constructs a model based on the pre-processed KO-gene association
%       matrix (koGeneMat). As the full KEGG model already has reaction-KO
%       relationships, KOs are converted into the query genes. The final
%       draft model contains only these reactions, which are associated
%       with KOs from koGeneMat. The reactions without the genes may also
%       be included, if the user set keepSpontaneous as 'true'.
%
%   The Step 3 is specific to the organism for which the model is
%   reconstructed.
%
%   In principle the function looks at which output that is already available
%   and runs only the parts that are required for step 3. This means
%   that (see the definition of the parameters for details):
%   -1a is only performed if there are no KEGG model files in the
%   RAVEN\external\kegg directory
%   -1b is only performed if not all required HMMs OR aligned FASTA files
%   OR multi-FASTA files exist in the defined dataDir. This means that this
%   step is skipped if the HMMs are downloaded from BioMet Toolbox instead
%   (see above). If not all files exist it will try to find
%   the KEGG database files in dataDir.
%   -2a is only performed if not all required HMMs OR aligned FASTA files
%   files exist in the defined dataDir. This means that this step is skipped
%   if the HMMs are downloaded from BioMet Toolbox instead (see above).
%   -2b is only performed if not all required HMMs exist in the defined
%   dataDir. This means that this step is skipped if the FASTA files or
%   HMMs are downloaded from BioMet Toolbox instead (see above).
%   -3a is performed for the required HMMs for which no corresponding .out
%   file exists in outDir. This is just a way to enable the function to be
%   run in parallel or to resume if interrupted.
%   -3b is always performed.
%
%   These steps are specific to the organism for which you are
%   reconstructing the model.
%
%   Regarding the whole pipeline, the function checks the output that is
%   already available and runs only the parts that are required for step 3.
%   This means that (see the definition of the parameters for details):
%   -1a is only performed if there are no KEGG model files in the
%   RAVEN\external\kegg directory.
%   -1b is only performed if any of required KOs do not have HMMs, aligned
%   FASTA files, clustered FASTA files and raw FASTA files in the defined
%   dataDir. This means that this step is skipped if the HMMs are
%   downloaded from BioMet Toolbox instead (see above). If not all files
%   exist it will try to find the KEGG database files in dataDir.
%   -2ab are only performed if any of required KOs do not have HMMs,
%   aligned FASTA files and clustered FASTA files in the defined dataDir.
%   This means that this step is skipped if the HMMs are downloaded from
%   BioMet Toolbox instead (see above).
%   -2c is only performed if any of required KOs do not have HMMs and
%   aligned FASTA files in the defined dataDir. This means that this step
%   is skipped if the HMMs are downloaded from BioMet Toolbox instead (see
%   above).
%   -2d is only performed if any of required KOs do not have HMMs exist in
%   the defined dataDir. This means that this step is skipped if the FASTA
%   files or HMMs are downloaded from BioMet Toolbox instead (see above).
%   -3a is performed for the required HMMs for which no corresponding .out
%   file exists in outDir. This is just a way to enable the function to be
%   run in parallel or to resume if interrupted.
%   -3b is always performed.
%
%   NOTE: it is also possible to obtain draft model from KEGG without
%   providing protein FASTA file for the target organism. In such case the
%   organism three-four letter abbreviation set as 'organismID' must exist
%   in the local KEGG database. In such case, the program just fetches all
%   the reactions, which are associated with given 'organismID'.
%
%   Usage: model=getKEGGModelForOrganism(organismID,fastaFile,dataDir,...
%    outDir,keepSpontaneous,keepUndefinedStoich,keepIncomplete,...
%    keepGeneral,cutOff,minScoreRatioKO,minScoreRatioG,maxPhylDist,...
%    nSequences,seqIdentity)

if nargin<2 || isempty(fastaFile)
    fastaFile=[];
else
    fastaFile=char(fastaFile);
end
if nargin<3
    dataDir=[];
else
    dataDir=char(dataDir);
end
if nargin<4 || isempty(outDir)
    outDir=tempdir;
    %Delete all *.out files if any exist
    delete(fullfile(outDir,'*.out'));
else
    outDir=char(outDir);
end
if nargin<5
    keepSpontaneous=true;
end
if nargin<6
    keepUndefinedStoich=true;
end
if nargin<7
    keepIncomplete=true;
end
if nargin<8
    keepGeneral=false;
end
if nargin<9
    cutOff=10^-50;
end
if nargin<10
    minScoreRatioKO=0.3;
end
if nargin<11
    minScoreRatioG=0.8;
end
if nargin<12
    maxPhylDist=inf;
    %Include all sequences for each reaction
end
if nargin<13
    nSequences=inf;
    %Include all sequences for each reaction
end
if nargin<14
    seqIdentity=0.9;
end

if isempty(fastaFile)
    fprintf(['\n*** The model reconstruction from KEGG based on the annotation available for KEGG Species ' organismID ' ***\n\n']);
else
    fprintf('\n*** The model reconstruction from KEGG based on the protein homology search against KEGG Orthology specific HMMs ***\n\n');
    %Check if query fasta exists
    fastaFile=checkFileExistence(fastaFile,2); %Copy file to temp dir
end

%Run the external binaries multi-threaded to use all logical cores assigned
%to MATLAB
cores = evalc('feature(''numcores'')');
cores = strsplit(cores, 'MATLAB was assigned: ');
cores = regexp(cores{2},'^\d*','match');
cores = cores{1};

%Get the directory for RAVEN Toolbox.
ravenPath=findRAVENroot();

%Checking if dataDir is consistent. It must point to pre-trained HMMs set,
%compatible with the the current RAVEN version. The user may have the
%required zip file already in working directory or have it extracted. If
%the zip file and directory is not here, it is downloaded from the cloud
if ~isempty(dataDir)
    hmmOptions={'euk90_kegg105','prok90_kegg105'};
    if ~endsWith(dataDir,hmmOptions) %Check if dataDir ends with any of the hmmOptions.
                                     %If not, then check whether the required folders exist anyway.
        if ~isfile(fullfile(dataDir,'keggdb','genes.pep')) && ...
                ~isfolder(fullfile(dataDir,'fasta')) && ...
                ~isfolder(fullfile(dataDir,'aligned')) && ...
                ~isfolder(fullfile(dataDir,'hmms'))
            error(['Pre-trained HMMs set is not recognised. If you want download RAVEN provided sets, it should match any of the following: ' strjoin(hmmOptions,' or ')])
        end
    else
        if isfolder(dataDir) && isfile(fullfile(dataDir,'hmms','K00844.hmm'))
            fprintf(['NOTE: Found ' dataDir ' directory with pre-trained HMMs, it will therefore be used during reconstruction\n']);
        elseif ~isfolder(dataDir) && isfile([dataDir,'.zip'])
            fprintf('Extracting the HMMs archive file... ');
            unzip([dataDir,'.zip']);
            fprintf('COMPLETE\n');
        else
            hmmIndex=strcmp(dataDir,hmmOptions);
            if ~any(hmmIndex)
                error(['Pre-trained HMMs are only provided with proteins clustered at 90% sequence identity (i.e. prok90_kegg105 and euk90_kegg105). ' ...
                    'Use either of these datasets, or otherwise download the relevant sequence data from KEGG to train HMMs with your desired sequence identity'])
            else
                fprintf('Downloading the HMMs archive file... ');
                try
                    websave([dataDir,'.zip'],['https://github.com/SysBioChalmers/RAVEN/releases/download/v2.8.0/',hmmOptions{hmmIndex},'.zip']);
                catch ME
                    if strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError')
                        error('Failed to download the HMMs archive file, the server returned a 404 error, try again later. If the problem persists please report it on the RAVEN GitHub Issues page: https://github.com/SysBioChalmers/RAVEN/issues')
                    end
                end
            end

            fprintf('COMPLETE\n');
            fprintf('Extracting the HMMs archive file... ');
            unzip([dataDir,'.zip']);
            fprintf('COMPLETE\n');
        end
        %Check if HMMs are extracted
        if ~isfile(fullfile(dataDir,'hmms','K00844.hmm'))
            error(['The HMM files seem improperly extracted and not found in ',dataDir,'/hmms. Please remove ',dataDir,' folder and rerun getKEGGModelForOrganism']);
        end
    end
end

%Check if the fasta-file contains '/' or'\'. If not then it's probably just
%a file name. Expand to full path.
if any(fastaFile)
    if ~any(strfind(fastaFile,'\')) && ~any(strfind(fastaFile,'/'))
        fastaFile=which(fastaFile);
    end
    %Create the required sub-folders in dataDir if they dont exist
    if ~isfolder(fullfile(dataDir,'keggdb'))
        mkdir(dataDir,'keggdb');
    end
    if ~isfolder(fullfile(dataDir,'fasta'))
        mkdir(dataDir,'fasta');
    end
    if ~isfolder(fullfile(dataDir,'aligned'))
        mkdir(dataDir,'aligned');
    end
    if ~isfolder(fullfile(dataDir,'hmms'))
        mkdir(dataDir,'hmms');
    end
    if ~isfolder(outDir)
        mkdir(outDir);
    end
end

%First generate the full global KEGG model. Can be provided as input.
%Otherwise, getModelFromKEGG is run. The dataDir must not be supplied as
%there is also an internal RAVEN version available
if nargin==15
    model=globalModel.model;
    KOModel=globalModel.KOModel;
elseif any(dataDir)
    [model, KOModel]=getModelFromKEGG(fullfile(dataDir,'keggdb'),keepSpontaneous,keepUndefinedStoich,keepIncomplete,keepGeneral);
else
    [model, KOModel]=getModelFromKEGG([],keepSpontaneous,keepUndefinedStoich,keepIncomplete,keepGeneral);
end
model.id=organismID;
model.c=zeros(numel(model.rxns),1);

%If no FASTA file is supplied, then just remove all genes which are not for
%the given organism ID
if isempty(fastaFile)
    %Check if organismID can be found in KEGG species list or is
    %set to "eukaryotes" or "prokaryotes"
    phylDistsFull=getPhylDist(fullfile(dataDir,'keggdb'),true);
    if ~ismember(organismID,[phylDistsFull.ids 'eukaryotes' 'prokaryotes'])
        error('Provided organismID is incorrect. Only species abbreviations from KEGG Species List or "eukaryotes"/"prokaryotes" are allowed.');
    end

    fprintf(['Pruning the model from non-' organismID ' genes... ']);
    if ismember(organismID,{'eukaryotes','prokaryotes'})
        phylDists=getPhylDist(fullfile(dataDir,'keggdb'),maxPhylDist==-1);
        if strcmp(organismID,'eukaryotes')
            proxyid='hsa';
            %Use H. sapiens here
        else
            proxyid='eco';
            %Use E. coli here
        end
        [~, phylDistId]=ismember(proxyid,phylDists.ids);
        idsToKeep=phylDists.ids(~isinf(phylDists.distMat(phylDistId,:)));
        taxIDs=cellfun(@(x) x{1},cellfun(@(x) strsplit(x,':'),model.genes,'UniformOutput',false),'UniformOutput',false);
        I=ismember(upper(taxIDs),upper(idsToKeep));
    else
        %KEGG organism IDs may have three or four letters
        organismID=strcat(organismID,':');
        %Add colon for accurate matching
        if length(organismID)==4
            I=cellfun(@(x) strcmpi(x(1:4),organismID),model.genes);
        elseif length(organismID)==5
            I=cellfun(@(x) strcmpi(x(1:5),organismID),model.genes);
        end
    end
    %Remove those genes
    model.genes=model.genes(I);
    model.rxnGeneMat=model.rxnGeneMat(:,I);
    fprintf('COMPLETE\n');
end

%First remove all reactions without genes
if keepSpontaneous==true
    fprintf('Removing non-spontaneous reactions without GPR rules... ');
    load(fullfile(ravenPath,'external','kegg','keggRxns.mat'),'isSpontaneous');
    I=~any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous);
    spontRxnsWithGenes=model.rxns(any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous));
else
    fprintf('Removing reactions without GPR rules... ');
    I=~any(model.rxnGeneMat,2);
end
model=removeReactions(model,I,true);
fprintf('COMPLETE\n');

%Clean gene names
fprintf('Fixing gene names in the model... ');
%Get rid of the prefix organism id
model.genes=regexprep(model.genes,'^\w+?:','');
fprintf('COMPLETE\n');

%If no FASTA file is supplied, then we are done here
if isempty(fastaFile)
    %Create grRules
    fprintf('Constructing GPR associations and annotations for the model... ');
    model.grRules=cell(numel(model.rxns),1);
    model.grRules(:)={''};
    %Add the gene associations as 'or'
    for i=1:numel(model.rxns)
        %Find the involved genes
        I=find(model.rxnGeneMat(i,:));
        if any(I)
            model.grRules{i}=['(' model.genes{I(1)}];
            for j=2:numel(I)
                model.grRules{i}=[model.grRules{i} ' or ' model.genes{I(j)}];
            end
            model.grRules{i}=[model.grRules{i} ')'];
        end
    end
    %Fix grRules and reconstruct rxnGeneMat
    [grRules,rxnGeneMat] = standardizeGrRules(model); %Give detailed output
    model.grRules = grRules;
    model.rxnGeneMat = rxnGeneMat;
    %Add geneMiriams, assuming that it follows the syntax
    %kegg.genes/organismID:geneName
    model.geneMiriams='';
    for i=1:numel(model.genes)
        model.geneMiriams{i,1}.name{1,1}='kegg.genes';
        model.geneMiriams{i,1}.value{1,1}=strcat(lower(organismID),model.genes{i,1});
    end
    %Add the description to the reactions
    for i=1:numel(model.rxns)
        if ~isempty(model.rxnNotes{i})
            model.rxnNotes(i)=strcat('Included by getKEGGModelForOrganism (without HMMs).',model.rxnNotes(i));
            model.rxnNotes(i)=strrep(model.rxnNotes(i),'.','. ');
        else
            model.rxnNotes(i)={'Included by getKEGGModelForOrganism (without HMMs)'};
        end
    end
    fprintf('COMPLETE\n\n');
    fprintf('*** Model reconstruction complete ***\n');
    return;
end

%Create a phylogenetic distance structure
phylDistStruct=getPhylDist(fullfile(dataDir,'keggdb'),maxPhylDist==-1);
[~, phylDistId]=ismember(model.id,phylDistStruct.ids);

%Calculate the real maximal distance now. An abitary large number of 1000
%is used for the "all in kingdom" or "all sequences" options. This is a bit
%inconvenient way to do it, but it is to make it fit with some older code
if isinf(maxPhylDist) || maxPhylDist==-1
    maxPhylDist=1000;
end

%Get the KO ids for which files have been generated. Maybe not the neatest
%way..
fastaFiles=listFiles(fullfile(dataDir,'fasta','*.fa'));
alignedFiles=listFiles(fullfile(dataDir,'aligned','*.fa'));
alignedWorking=listFiles(fullfile(dataDir,'aligned','*.faw'));
hmmFiles=listFiles(fullfile(dataDir,'hmms','*.hmm'));
outFiles=listFiles(fullfile(outDir,'*.out'));

%Check if multi-FASTA files should be generated. This should only be
%performed if there are IDs in the KOModel structure that haven't been
%parsed yet
missingFASTA=setdiff(KOModel.rxns,[fastaFiles;alignedFiles;hmmFiles;outFiles]);

if ~isempty(missingFASTA)
    if ~isfile(fullfile(dataDir,'keggdb','genes.pep'))
        EM=['The file ''genes.pep'' cannot be located at ' strrep(dataDir,'\','/') '/ and should be downloaded from the KEGG FTP.\n'];
        dispEM(EM);
    end
    %Only construct models for KOs which don't have files already
    fastaModel=removeReactions(KOModel,setdiff(KOModel.rxns,missingFASTA),true,true);
    %Permute the order of the KOs in the model so that constructMultiFasta
    %can be run on several processors at once
    fastaModel=permuteModel(fastaModel,randperm(RandStream.create('mrg32k3a','Seed',cputime()),numel(fastaModel.rxns)),'rxns');
    constructMultiFasta(fastaModel,fullfile(dataDir,'keggdb','genes.pep'),fullfile(dataDir,'fasta'));
else
    fprintf('Generating the KEGG Orthology specific multi-FASTA files... COMPLETE\n');
end

if isunix
    if ismac
        binEnd='.mac';
    else
        binEnd='';
    end
elseif ispc
    binEnd='';
else
    EM='Unknown OS, exiting.';
    disp(EM);
    return
end

%Check if alignment of FASTA files should be performed
missingAligned=setdiff(KOModel.rxns,[alignedFiles;hmmFiles;alignedWorking;outFiles]);
if ~isempty(missingAligned)
    if seqIdentity==-1
        fprintf('Performing the multiple alignment for KEGG Orthology specific protein sets...   0%% complete');
    else
        fprintf('Performing clustering and multiple alignment for KEGG Orthology specific protein sets...   0%% complete');
    end
    missingAligned=missingAligned(randperm(RandStream.create('mrg32k3a','Seed',cputime()),numel(missingAligned)));
    tmpFile=tempname;
    %On Windows, paths need to be translated to Unix before parsing it to WSL
    if ispc
        wslPath.tmpFile=getWSLpath(tmpFile);
        %mafft has problems writing to terminal (/dev/stderr) when running
        %on WSL via MATLAB, instead write and read progress file
        mafftOutput = tempname;
        wslPath.mafftOutput=getWSLpath(mafftOutput);
        wslPath.mafft=getWSLpath(fullfile(ravenPath,'software','mafft','mafft-linux64','mafft.bat'));
        wslPath.cdhit=getWSLpath(fullfile(ravenPath,'software','cd-hit','cd-hit'));
    end

    for i=1:numel(missingAligned)
        %This is checked here because it could be that it is created by a
        %parallel process. The faw-files are saved as temporary files to
        %kept track of which files are being worked on
        if ~isfile(fullfile(dataDir,'aligned',[missingAligned{i} '.faw'])) &&...
                ~isfile(fullfile(dataDir,'aligned',[missingAligned{i} '.fa']))
            %Check that the multi-FASTA file exists. It should do so since
            %we are saving empty files as well. Print a warning and
            %continue if not
            if ~isfile(fullfile(dataDir,'fasta',[missingAligned{i} '.fa']))
                EM=['WARNING: The multi-FASTA file for ' missingAligned{i} ' does not exist'];
                dispEM(EM,false);
                continue;
            end

            %If the multi-FASTA file is empty then save an empty aligned
            %file and continue
            s=dir(fullfile(dataDir,'fasta',[missingAligned{i} '.fa']));
            if s.bytes<=0
                fid=fopen(fullfile(dataDir,'aligned',[missingAligned{i} '.fa']),'w');
                fclose(fid);
                continue;
            end

            %Create an empty file to prevent other threads to start to work
            %on the same alignment
            fid=fopen(fullfile(dataDir,'aligned',[missingAligned{i} '.faw']),'w');
            fclose(fid);

            %First load the FASTA file, then select up to nSequences
            %sequences of the most closely related species, apply any
            %constraints from maxPhylDist, and save it as a temporary file,
            %and create the model from that

            fastaStruct=fastaread(fullfile(dataDir,'fasta',[missingAligned{i} '.fa']));
            phylDist=inf(numel(fastaStruct),1);
            for j=1:numel(fastaStruct)
                %Get the organism abbreviation
                index=strfind(fastaStruct(j).Header,':');
                if any(index)
                    abbrev=fastaStruct(j).Header(1:index(1)-1);
                    [~, index]=ismember(abbrev,phylDistStruct.ids);
                    if any(index)
                        phylDist(j)=phylDistStruct.distMat(index(1),phylDistId);
                    end
                end
            end

            %Inf means that it should not be included
            phylDist(phylDist>maxPhylDist)=[];

            %Sort based on phylDist
            [~, order]=sort(phylDist);

            %Save the first nSequences hits to a temporary FASTA file
            if nSequences<=numel(fastaStruct)
                fastaStruct=fastaStruct(order(1:nSequences));
            else
                fastaStruct=fastaStruct(order);
            end

            %Do the clustering and alignment if there are more than one
            %sequences, otherwise just save the sequence (or an empty file)
            if numel(fastaStruct)>1
                 if seqIdentity~=-1
                    cdhitInpCustom=tempname;
                    fastawrite(cdhitInpCustom,fastaStruct);
                    if seqIdentity<=1 && seqIdentity>0.7
                        nparam='5';
                    elseif seqIdentity>0.6
                        nparam='4';
                    elseif seqIdentity>0.5
                        nparam='3';
                    elseif seqIdentity>0.4
                        nparam='2';
                    else
                        EM='The provided seqIdentity must be between 0 and 1\n';
                        dispEM(EM);
                    end
                    if ispc
                        wslPath.cdhitInpCustom=getWSLpath(cdhitInpCustom);
                        [status, output]=system(['wsl "' wslPath.cdhit '" -T "' num2str(cores) '" -i "' wslPath.cdhitInpCustom '" -o "' wslPath.tmpFile '" -c "' num2str(seqIdentity) '" -n ' nparam ' -M 2000']);
                    elseif ismac || isunix
                        [status, output]=system(['"' fullfile(ravenPath,'software','cd-hit',['cd-hit' binEnd]) '" -T "' num2str(cores) '" -i "' cdhitInpCustom '" -o "' tmpFile '" -c "' num2str(seqIdentity) '" -n ' nparam ' -M 2000']);
                    end
                    if status~=0
                        EM=['Error when performing clustering of ' missingAligned{i} ':\n' output];
                        dispEM(EM);
                    end
                    %Remove the old tempfile
                    if exist(cdhitInpCustom, 'file')
                        delete([cdhitInpCustom '*']);
                    end
                else
                    %This means that CD-HIT should be skipped since
                    %seqIdentity is equal to -1
                    fastawrite(tmpFile,fastaStruct);
                end
                %Do the alignment for this file
                if ismac
                    [status, output]=system(['"' fullfile(ravenPath,'software','mafft','mafft-mac','mafft.bat') '" --auto --anysymbol --thread "' num2str(cores) '" "' tmpFile '" > "' fullfile(dataDir,'aligned',[missingAligned{i} '.faw']) '"']);
                elseif isunix
                    [status, output]=system(['"' fullfile(ravenPath,'software','mafft','mafft-linux64','mafft.bat') '" --auto --anysymbol --thread "' num2str(cores) '" "' tmpFile '" > "' fullfile(dataDir,'aligned',[missingAligned{i} '.faw']) '"']);
                elseif ispc
                    wslPath.fawFile=getWSLpath(fullfile(dataDir,'aligned',[missingAligned{i} '.faw']));
                    [status, ~]=system(['wsl "' wslPath.mafft '" --auto --anysymbol --progress "' wslPath.mafftOutput '" --thread "' num2str(cores) '" --out "' wslPath.fawFile '" "' wslPath.tmpFile '"']);
                    output=fileread(mafftOutput);
                    delete(mafftOutput);
                end
                if status~=0
                    %It could be that alignment failed because only one
                    %sequence was left after clustering. If that is the
                    %case, then the clustered file is just copied as 'faw'
                    %file
                    if any(regexp(output,'Only 1 sequence found'))
                        movefile(tmpFile,fullfile(dataDir,'aligned',[missingAligned{i} '.faw']),'f');
                    else
                        EM=['Error when performing alignment of ' missingAligned{i} ':\n' output];
                        dispEM(EM);
                    end
                end
                %Remove the old tempfile
                if exist(tmpFile, 'file')
                    delete([tmpFile '*']);
                end
            else
                %If there is only one sequence then it's not possible to do
                %a multiple alignment. Just print the sequence instead. An
                %empty file was written previously so that doesn't have to
                %be dealt with
                if numel(fastaStruct)==1
                    warnState = warning; %Save the current warning state
                    warning('off','Bioinfo:fastawrite:AppendToFile');
                    fastawrite(fullfile(dataDir,'aligned',[missingAligned{i} '.faw']),fastaStruct);
                    warning(warnState) %Reset warning state to previous settings
                end
            end
            %Move the temporary file to the real one
            movefile(fullfile(dataDir,'aligned',[missingAligned{i} '.faw']),fullfile(dataDir,'aligned',[missingAligned{i} '.fa']),'f');

            %Print the progress every 25 files
            if rem(i-1,25) == 0
                progress=num2str(floor(100*numel(listFiles(fullfile(dataDir,'aligned','*.fa')))/numel(KOModel.rxns)));
                progress=pad(progress,3,'left');
                fprintf('\b\b\b\b\b\b\b\b\b\b\b\b\b%s%% complete',progress);
            end
        end
    end
    fprintf('\b\b\b\b\b\b\b\b\b\b\b\b\bCOMPLETE\n');
else
    if seqIdentity==-1
        fprintf('Performing the multiple alignment for KEGG Orthology specific protein sets... COMPLETE\n');
    else
        fprintf('Performing clustering and multiple alignment for KEGG Orthology specific protein sets... COMPLETE\n');
    end
end

%Check if training of Hidden Markov models should be performed
missingHMMs=setdiff(KOModel.rxns,[hmmFiles;outFiles]);
if ~isempty(missingHMMs)
    fprintf('Training the KEGG Orthology specific HMMs...   0%% complete');
    missingHMMs=missingHMMs(randperm(RandStream.create('mrg32k3a','Seed',cputime()),numel(missingHMMs)));
    %Train models for all missing KOs
    for i=1:numel(missingHMMs)
        %This is checked here because it could be that it is created by a
        %parallel process
        if ~isfile(fullfile(dataDir,'hmms',[missingHMMs{i} '.hmm'])) && ~isfile(fullfile(dataDir,'hmms',[missingHMMs{i} '.hmw']))
            %Check that the aligned FASTA file exists. It could be that it
            %is still being worked on by some other instance of the program
            %(the .faw file should then exist). This should not happen on a
            %single computer. It doesn't throw an error, because it should
            %finalize the ones it can
            if ~isfile(fullfile(dataDir,'aligned',[missingHMMs{i} '.fa']))
                EM=['The aligned FASTA file for ' missingHMMs{i} ' does not exist'];
                dispEM(EM,false);
                continue;
            end

            %If the multi-FASTA file is empty then save an empty aligned
            %file and continue
            s=dir(fullfile(dataDir,'aligned',[missingHMMs{i} '.fa']));
            if s.bytes<=0
                fid=fopen(fullfile(dataDir,'hmms',[missingHMMs{i} '.hmm']),'w');
                fclose(fid);
                continue;
            end
            %Create a temporary file to indicate that it is working on the
            %KO. This is because hmmbuild cannot overwrite existing files
            fid=fopen(fullfile(dataDir,'hmms',[missingHMMs{i} '.hmw']),'w');
            fclose(fid);

            %Create HMM
            [status, output]=system(['"' fullfile(ravenPath,'software','hmmer',['hmmbuild' binEnd]) '" --cpu "' num2str(cores) '" "' fullfile(dataDir,'hmms',[missingHMMs{i} '.hmm']) '" "' fullfile(dataDir,'aligned',[missingHMMs{i} '.fa']) '"']);
            if status~=0
                EM=['Error when training HMM for ' missingHMMs{i} ':\n' output];
                dispEM(EM);
            end

            %Delete the temporary file
            delete(fullfile(dataDir,'hmms',[missingHMMs{i} '.hmw']));

            %Print the progress every 25 files
            if rem(i-1,25) == 0
                progress=num2str(floor(100*numel(listFiles(fullfile(dataDir,'hmms','*.hmm')))/numel(KOModel.rxns)));
                progress=pad(progress,3,'left');
                fprintf('\b\b\b\b\b\b\b\b\b\b\b\b\b%s%% complete',progress);
            end
        end
    end
    fprintf('\b\b\b\b\b\b\b\b\b\b\b\b\bCOMPLETE\n');
else
    fprintf('Training the KEGG Orthology specific HMMs... COMPLETE\n');
end

%Check which new .out files that should be generated. Check if training of
%Hidden Markov models should be performed
missingOUT=setdiff(KOModel.rxns,outFiles);
if ~isempty(missingOUT)
    fprintf('Querying the user-specified FASTA file against the KEGG Orthology specific HMMs...   0%% complete');
    missingOUT=missingOUT(randperm(RandStream.create('mrg32k3a','Seed',cputime()),numel(missingOUT)));
    for i=1:numel(missingOUT)
        %This is checked here because it could be that it is created by a
        %parallel process
        if ~isfile(fullfile(outDir,[missingOUT{i} '.out']))
            %Check that the HMM file exists. It should do so since %we are
            %saving empty files as well. Print a warning and continue if
            %not
            if ~isfile(fullfile(dataDir,'hmms',[missingOUT{i} '.hmm']))
                EM=['The HMM file for ' missingOUT{i} ' does not exist'];
                dispEM(EM,false);
                continue;
            end

            %Save an empty file to prevent several threads working on the
            %same file
            fid=fopen(fullfile(outDir,[missingOUT{i} '.out']),'w');
            fclose(fid);

            %If the HMM file is empty then save an out file and continue
            s=dir(fullfile(dataDir,'hmms',[missingOUT{i} '.hmm']));
            if s.bytes<=0
                continue;
            end

            %Check each gene in the input file against this model
            [status, output]=system(['"' fullfile(ravenPath,'software','hmmer',['hmmsearch' binEnd]) '" --cpu "' num2str(cores) '" "' fullfile(dataDir,'hmms',[missingOUT{i} '.hmm']) '" "' fastaFile '"']);
            if status~=0
                EM=['Error when querying HMM for ' missingOUT{i} ':\n' output];
                dispEM(EM);
            end

            %Save the output to a file
            fid=fopen(fullfile(outDir,[missingOUT{i} '.out']),'w');
            fwrite(fid,output);
            fclose(fid);

            %Print the progress every 25 files
            if rem(i-1,25) == 0
                progress=num2str(floor(100*numel(listFiles(fullfile(outDir,'*.out')))/numel(KOModel.rxns)));
                progress=pad(progress,3,'left');
                fprintf('\b\b\b\b\b\b\b\b\b\b\b\b\b%s%% complete',progress);
            end
        end
    end
    fprintf('\b\b\b\b\b\b\b\b\b\b\b\b\bCOMPLETE\n');
else
    fprintf('Querying the user-specified FASTA file against the KEGG Orthology specific HMMs... COMPLETE\n');
end

%***Begin retrieving the output and putting together the resulting model

fprintf('Parsing the HMM search results... ');
%Retrieve matched genes from the HMMs
koGeneMat=zeros(numel(KOModel.rxns),3000); %Make room for 3000 genes
genes=cell(3000,1);
%Store the best score for a gene in a hash list (since it will be searching
%many times)
hTable = java.util.Hashtable;

geneCounter=0;
for i=1:numel(KOModel.rxns)
    if exist(fullfile(outDir,[KOModel.rxns{i} '.out']), 'file')
        fid=fopen(fullfile(outDir,[KOModel.rxns{i} '.out']),'r');
        beginMatches=false;
        while 1
            %Get the next line
            tline = fgetl(fid);

            %Abort at end of file
            if ~ischar(tline)
                break;
            end

            if and(beginMatches,strcmp(tline,'  ------ inclusion threshold ------'))
                break;
            end

            if beginMatches==false
                %This is how the listing of matches begins
                if any(strfind(tline,'E-value '))
                    %Read one more line that is only padding
                    tline = fgetl(fid);
                    beginMatches=true;
                end
            else
                %If matches should be read
                if ~strcmp(tline,'   [No hits detected that satisfy reporting thresholds]') && ~isempty(tline)
                    elements=regexp(tline,' ','split');
                    elements=elements(cellfun(@any,elements));

                    %Check if the match is below the treshhold
                    score=str2double(elements{1});
                    gene=elements{9};
                    if score<=cutOff
                        %If the score is exactly 0, change it to a very
                        %small value to avoid NaN
                        if score==0
                            score=10^-250;
                        end
                        %Check if the gene is added already and, is so, get
                        %the best score for it
                        I=hTable.get(gene);
                        if any(I)
                            koGeneMat(i,I)=score;
                        else
                            geneCounter=geneCounter+1;
                            %The gene was not present yet so add it
                            hTable.put(gene,geneCounter);
                            genes{geneCounter}=gene;
                            koGeneMat(i,geneCounter)=score;
                        end
                    end
                else
                    break;
                end
            end
        end
        fclose(fid);
    end
end
fprintf('COMPLETE\n');

fprintf('Removing gene, KEGG Orthology associations below minScoreRatioKO, minScoreRatioG... ');
koGeneMat=koGeneMat(:,1:geneCounter);

%Remove the genes for each KO that are below minScoreRatioKO.
for i=1:size(koGeneMat,1)
    J=find(koGeneMat(i,:));
    if any(J)
        koGeneMat(i,J(log(koGeneMat(i,J))/log(min(koGeneMat(i,J)))<minScoreRatioKO))=0;
    end
end

%Remove the KOs for each gene that are below minScoreRatioG
for i=1:size(koGeneMat,2)
    J=find(koGeneMat(:,i));
    if any(J)
        koGeneMat(J(log(koGeneMat(J,i))/log(min(koGeneMat(J,i)))<minScoreRatioG),i)=0;
    end
end
fprintf('COMPLETE\n');

fprintf('Adding gene annotations to the model... ');
%Create the new model
model.genes=genes(1:geneCounter);
model.grRules=cell(numel(model.rxns),1);
model.grRules(:)={''};
model.rxnGeneMat=sparse(numel(model.rxns),numel(model.genes));

%Loop through the reactions and add the corresponding genes
for i=1:numel(model.rxns)
    if isstruct(model.rxnMiriams{i})
        %Get all KOs
        I=find(strcmpi(model.rxnMiriams{i}.name,'kegg.orthology'));
        KOs=model.rxnMiriams{i}.value(I);
        %Find the KOs and the corresponding genes
        J=ismember(KOModel.rxns,KOs);
        [~, K]=find(koGeneMat(J,:));

        if any(K)
            model.rxnGeneMat(i,K)=1;
            %Also delete KOs for which no genes were found. If no genes at
            %all were matched to the reaction it will be deleted later
            L=sum(koGeneMat(J,:),2)==0;
            model.rxnMiriams{i}.value(I(L))=[];
            model.rxnMiriams{i}.name(I(L))=[];
        end
    end
end
fprintf('COMPLETE\n');

%Find and delete all reactions without genes. This also removes genes that
%are not used (which could happen because minScoreRatioG and
%minScoreRatioKO). If keepSpontaneous==true, the spontaneous reactions
%without genes are kept in the model. Spontaneous reactions with original
%gene associations are treated in the same way, like the rest of the
%reactions - if gene associations were removed during HMM search, such
%reactions are deleted from the model
if keepSpontaneous==true
    %Not the most comprise way to delete reactions without genes, but this
    %makes the code easier to understand. Firstly the non-spontaneous
    %reactions without genes are removed. After that, the second deletion
    %step removes spontaneous reactions, which had gene associations before
    %HMM search, but no longer have after it
    fprintf('Removing non-spontaneous reactions which after HMM search no longer have GPR rules... ');
    I=~any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous);
    model=removeReactions(model,I,true,true);
    I=~any(model.rxnGeneMat,2)&ismember(model.rxns,spontRxnsWithGenes);
    model=removeReactions(model,I,true,true);
else
    %Just simply check for any new reactions without genes and remove
    %it
    fprintf('Removing reactions which after HMM search no longer have GPR rules... ');
    I=~any(model.rxnGeneMat,2);
    model=removeReactions(model,I,true,true);
end
fprintf('COMPLETE\n');

fprintf('Constructing GPR rules and finalizing the model... ');
%Add the gene associations as 'or'
for i=1:numel(model.rxns)
    %Find the involved genes
    I=find(model.rxnGeneMat(i,:));
    if any(I)
        model.grRules{i}=['(' model.genes{I(1)}];
        for j=2:numel(I)
            model.grRules{i}=[model.grRules{i} ' or ' model.genes{I(j)}];
        end
        model.grRules{i}=[model.grRules{i} ')'];
    end
end

%Fix grRules and reconstruct rxnGeneMat
[grRules,rxnGeneMat] = standardizeGrRules(model,false); %Give detailed output
model.grRules = grRules;
model.rxnGeneMat = rxnGeneMat;

%Add the description to the reactions
for i=1:numel(model.rxns)
    if ~isempty(model.rxnNotes{i})
        model.rxnNotes(i)=strcat('Included by getKEGGModelForOrganism (using HMMs).',model.rxnNotes(i));
        model.rxnNotes(i)=strrep(model.rxnNotes(i),'.','. ');
    else
        model.rxnNotes(i)={'Included by getKEGGModelForOrganism (using HMMs)'};
    end
end
%Remove the temp fasta file
delete(fastaFile)
fprintf('COMPLETE\n\n*** Model reconstruction complete ***\n');
end

function files=listFiles(directory)
%Supporter function to list the files in a directory and return them as a
%cell array
temp=dir(directory);
files=cell(numel(temp),1);
for i=1:numel(temp)
    files{i}=temp(i,1).name;
end
files=strrep(files,'.fa','');
files=strrep(files,'.hmm','');
files=strrep(files,'.out','');
files=strrep(files,'.faw','');
end


function all_bbox_gt = remove_difficult_ground_truth_bboxes(all_bbox_gt)
%
% This file is part of the code that implements the following paper:
% Title      : "LocNet: Improving Localization Accuracy for Object Detection"
% Authors    : Spyros Gidaris, Nikos Komodakis
% Institution: Universite Paris Est, Ecole des Ponts ParisTech
% ArXiv link : http://arxiv.org/abs/1511.07763
% code       : https://github.com/gidariss/LocNet
%
% AUTORIGHTS
% --------------------------------------------------------
% Copyright (c) 2016 Spyros Gidaris
%
% Title     : "LocNet: Improving Localization Accuracy for Object Detection"
% ArXiv link: http://arxiv.org/abs/1511.07763
% Licensed under The MIT License [see LICENSE for details]
% ---------------------------------------------------------

num_imgs = numel(all_bbox_gt);
for img_idx = 1:num_imgs
    if size(all_bbox_gt{img_idx},2) >= 6
        is_easy_mask  = all_bbox_gt{img_idx}(:,6) == 0;
        all_bbox_gt{img_idx} = all_bbox_gt{img_idx}(is_easy_mask,:);
    end
end
end


% LFUtilProcessCalibrations - process a folder/tree of camera calibrations
%
% Usage:
%
%     LFUtilProcessCalibrations
%     LFUtilProcessCalibrations( CalibrationsPath )
%     LFUtilProcessCalibrations( CalibrationsPath, FileOptions )
%     LFUtilProcessCalibrations( [], FileOptions )
%
% All parameters are optional and take on default values as set in the "Defaults" section at the top
% of the implementation. As such, this can be called as a function or run directly by editing the
% code. When calling as a function, pass an empty array "[]" to omit a parameter.
%
% This function recursively crawls through a folder identifying camera "calibration info" files and
% building a database for use in selecting a calibration appropriate to a light field. An example
% usage is in the image rectification code in LFUtilDecodeLytroFolder / LFSelectFromDatabase.
%
% Inputs -- all are optional, see code below for default values :
%
%    CalibrationsPath : Path to folder containing calibrations -- note the function operates
%                       recursively, i.e. it will search sub-folders. The calibration database will
%                       be created at the top level of this path. A typical configuration is to
%                       create a "Cameras" folder with a separate subfolder of calibrations for each
%                       camera in use. The appropriate path to pass to this function is the top
%                       level of that cameras folder.
%
%
%     FileOptions : struct controlling file naming and saving
%                       .SaveResult : Set to false to perform a "dry run"
%         .CalibrationDatabaseFname : Name of file to which white image database is saved
%           .CalInfoFilenamePattern : File search pattern for finding calibrations
%
% Output takes the form of a saved calibration database.
%
% User guide: LFToolbox.pdf
% See also: LFUtilDecodeLytroFolder, LFCalRectifyLF, LFSelectFromDatabase, LFUtilCalLensletCam,
% LFUtilProcessWhiteImages

% Copyright (c) 2013-2020 Donald G. Dansereau

function LFUtilProcessCalibrations( CalibrationsPath, FileOptions )

%---Defaults---
CalibrationsPath = LFDefaultVal( 'CalibrationsPath', 'Cameras' );

FileOptions = LFDefaultField( 'FileOptions', 'SaveResult', true );
FileOptions = LFDefaultField( 'FileOptions', 'CalibrationDatabaseFname', 'CalibrationDatabase.mat' );
FileOptions = LFDefaultField( 'FileOptions', 'CalInfoFilenamePattern', 'CalInfo*.json' );

%---Crawl folder structure locating calibrations---
fprintf('Building database of calibrations in %s\n', CalibrationsPath);
CamInfo = LFGatherCamInfo( CalibrationsPath, FileOptions.CalInfoFilenamePattern );

%---Optionally save---
if( FileOptions.SaveResult )
    SaveFpath = fullfile(CalibrationsPath, FileOptions.CalibrationDatabaseFname);
    fprintf('Saving to %s\n', SaveFpath);

    TimeStamp = datestr(now,'ddmmmyyyy_HHMMSS');
    GeneratedByInfo = struct('mfilename', mfilename, 'time', TimeStamp, 'VersionStr', LFToolboxVersion);
    save(SaveFpath, 'GeneratedByInfo', 'CamInfo');
end


function[]=noxlabels(str)
%NOXLABELS   Remove some or all x-axis tick mark labels.
%
%   NOXLABELS by itself removes all tick mark labels on the current x-axis.
%
%   NOXLABELS MAX removes only the label of the maximum x-tick mark.
%
%   NOXLABELS MIN removes only the label of the minimum x-tick mark.
%
%   NOXLABELS ALL is the same as the default.
%
%   See also NOYLABELS.
%
%   Usage:  noxlabels
%           noxlabels xmin
%           noxlabels ymin
%   _________________________________________________________________
%   This is part of JLAB --- type 'help jlab' for more information
%   (C) 2004--2008 J.M. Lilly --- type 'help jlab_license' for details

if nargin==0
    str='all';
end

if strcmpi(str(1:3),'min')
    xlabels=get(gca,'xticklabel');
    xlabels(1,:)=' ';
    set(gca,'xticklabel',xlabels)
elseif strcmpi(str(1:3),'max')
    xlabels=get(gca,'xticklabel');
    xlabels(end,:)=' ';
    set(gca,'xticklabel',xlabels)
elseif strcmpi(str(1:3),'all')
    set(gca,'xticklabel',[])
end


function value = iif(condition, value1, value2)
% SYNTAX:
%   value = iif(condition, value1, value2)
% DESCRIPTION:
%   return value1 if condition is true otherwise value2

%--- * --. --- --. .--. ... * ---------------------------------------------
%               ___ ___ ___
%     __ _ ___ / __| _ | __|
%    / _` / _ \ (_ |  _|__ \
%    \__, \___/\___|_| |___/
%    |___/                    v 1.0RC1
%
%--------------------------------------------------------------------------
%  Copyright (C) 2021 Geomatics Research & Development srl (GReD)
%  Written by:       Andrea Gatti
%  Contributors:     Andrea Gatti ...
%  A list of all the historical goGPS contributors is in CREDITS.nfo
%--------------------------------------------------------------------------
%
%   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/>.
%
%--------------------------------------------------------------------------
% 01100111 01101111 01000111 01010000 01010011
%--------------------------------------------------------------------------

    if condition
        value = value1;
    else
        value = value2;
    end
end


function [x,y,button, key] = PointInput(varargin)
%function [x,y,button, key] = PointInput(n,figh, axish)
% Input:
% n = number of clicks
% figh - which figure to analyze the clicks in
% axish - in which axes .. try to avoid this complications, use defaults
% Output:
% x,y - coordinates of the axes where the click happened,
% button - number of button presseed (1 -left,2 -middle, 3 -right
% if key not button was pressed then button=0, and key returns the key that
% was pressed
% also if it is not a character but a system key (like arrow)
% you can get double(key) and use my table from ~antsiro/matlab/keycodes
% file . enjoy :))
% Anton
[n, figh, axish] = DefaultArgs(varargin,{1,gcf,gca});
x=[];y=[];key={};
if n>0
    for i=1:n
        res = waitforbuttonpress;
        if res==0
            whatbutton = get(figh,'SelectionType');
            mousecoord = get(axish,'CurrentPoint');
            x(i)=mousecoord(1,1);
            y(i) = mousecoord(1,2);

            switch whatbutton
                case 'normal'
                    button(i)=1;
                case 'extend'
                    button(i)=2;
                case 'alt'
                    button(i)=3;
            end
            key{end+1}= '';
        else
            x=NaN; y=NaN; button=0;
            key{end+1} = get(gcf,'CurrentCharacter');
        end
    end
    if n==1
        key=key{1};
    end
else
    whatbutton = get(figh,'SelectionType');
    mousecoord = get(axish,'CurrentPoint');
    x= mousecoord(1,1);
    y = mousecoord(1,2);

    switch whatbutton
        case 'normal'
            button=1;
        case 'extend'
            button=2;
        case 'alt'
            button=3;
        otherwise
   %         fprintf('you pressed button %s\n',whatbutton);
            button = 0;
    end
end


%% SPARSE_BATCH_LOCAL uses the BATCH command to run the SPARSE code locally.
%
%  Discussion:
%
%    The SPARSE code is a function, so first we must write a script
%    called SPARSE_SCRIPT that runs the function.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    23 May 2012
%
%  Author:
%
%    John Burkardt
%
  workers = 3;

  fprintf ( 1, '\n' );
  fprintf ( 1, 'SPARSE_BATCH_LOCAL\n' );
  fprintf ( 1, '  Run SPARSE_SCRIPT locally.\n' );
%
%  The BATCH command sends the script for execution.
%
  my_job = batch ( 'sparse_script', ...
    'Configuration', 'local', ...
    'CaptureDiary', true, ...
    'FileDependencies', { 'sparse_fun' }, ...
    'matlabpool', workers );
%
%  WAIT pauses the MATLAB session til the job completes.
%
  wait ( my_job );
%
%  DIARY displays any messages printed during execution.
%
  diary ( my_job );
%
%  Here is where we can use the LOAD command
%  to make the script's workspace available.
%  The SPARSE function returns a matrix called A_sparse.
%
  load ( my_job );
%
%  Get the size of the matrix, just to verify it is here.
%
  size ( A_sparse );
%
%  DESTROY cleans up data about the job we no longer need.
%
  destroy ( my_job );

  fprintf ( 1, '\n' );
  fprintf ( 1, 'SPARSE_BATCH_LOCAL\n' );
  fprintf ( 1, '  Normal end of execution.\n' );


function t = isupper(c)
%ISUPPER  True for uppercase letters.
%
%   For a string C, ISUPPER(C) is 1 for uppercase letters and 0
%   otherwise.
%
%   See also: ISALNUM, ISALPHA, ISASCII, ISDIGIT, ISLOWER, ISPRTCHR,
%   ISXDIGIT.

%   Author:      Peter J. Acklam
%   Time-stamp:  2002-03-03 13:50:48 +0100
%   E-mail:      pjacklam@online.no
%   URL:         http://home.online.no/~pjacklam

   error(nargchk(1, 1, nargin));

   t = isletter(c) & (c == upper(c));


function pot = set_domain_pot(pot, domain)
% SET_DOMAIN_POT Change the domain of a potential (cgpot)
% pot = set_domain_pot(pot, domain)

delta = domain(1) - pot.domain(1);
assert(all(domain == pot.domain + delta));
pot.domain = pot.domain + delta;
pot.ddom = pot.ddom + delta;
pot.cdom = pot.cdom + delta;


function imsegs = msCreateSuperpixels(im, imname)

prefix = num2str(floor(rand(1)*1E6));
fn1 = ['./tmpim' prefix '.ppm'];
outfn = ['./tmpimsp' prefix '.ppm'];

imwrite(im, fn1);
segcmd = '/home/dhoiem/cmu/tools/superpixelsFH/segment 0.8 100 100';
syscall = [segcmd ' ' fn1 ' ' outfn];

system(syscall);
imsegs = processSuperpixelImage(outfn);
if exist('imname', 'var')
    imsegs.imname = imname;
end

delete(fn1);
delete(outfn);


function mv_plotScans(view,groupFlag);
% mv_plotScans(view,[groupFlag]);
%
% Shell/dialog for calling multi voxel UI for multiple scans.
%
% The idea here is to concatenate similar scans together and view the
% concatenated time course (mainly event-related or non-AB block designs,
% but can also be used if you have many cyclic scans and want to see all
% the cycles w/o averaging). If the scans have parfiles assigned, ras_tc will
% concatenate them together also.
%
% groupFlag, if passed as 1, rather than prompting for the scans to plot
% the code will look for a 'scanGroup' field in the dataTYPES.scanParams
% struct, which specifies which scans to use. If it can't find it, it
% will create it calling er_groupScans.
%
% For simplicity, assumes the data will be from the currently-selected ROI.
%
% 04/12/05 ras: adapted from tc_plotScans.
global dataTYPES HOMEDIR;

if nargin < 1
    help mv_plotScans;
    return
end

if ~exist('groupFlag','var')
    groupFlag = 0;
end

cdt = view.curDataType;

if groupFlag==1     % use pre-assigned group of scans
	[scans,dt] = er_getScanGroup(view);
else
    [scans ok] = er_selectScans(view,'View voxel data from which scans?');
    if ~ok  return;  end
    dt = cdt; % data type is current data type
end

view.curDataType = dt;

multiVoxelUI(view,[],scans);

view.curDataType = cdt; % switch back to orig. data type

return


function datapt = ma_GASpacecraftMassTasks(stateLogEntry, subTask, celBodyData)
%ma_GASpacecraftMassTasks Summary of this function goes here
%   Detailed explanation goes here

    switch subTask
        case 'fuelOx'
            datapt = stateLogEntry(10);
        case 'monoprop'
            datapt = stateLogEntry(11);
        case 'xenon'
            datapt = stateLogEntry(12);
        case 'dry'
            datapt = stateLogEntry(9);
        case 'total'
            datapt = sum(stateLogEntry(9:12));
    end
end


function copyaxes(source, destination, varargin)
%
% COPYAXES - copy a handle object axes inpt
%
% COPYAXES(SOURCE, DESTINATION) - copy axes from SOURCE to DESTINATION
%
% COPYAXES(..., isInSubplot) - if the destination is in a subplot figure
%       (default false).
%
% COPYAXES(..., isLegend) - if the axes is a legend (only if isInSubplot is
%       true) (default false)
%
%
% EXAMPLE: Copy a axes with plot
%
%   plot([1:0.1:2*pi], sin([1:0.1:2*pi]));
%   title('sin function')
%   xlabel('x')
%   ylabel('sin(x)')
%   ax = gca;
%
%   figure;
%   ax_new = axes;
%   copyaxes(ax, ax_new)
%
%
% EXAMPLE: Copy a axes with bar
%
%   bar(rand(10,5),'stacked');
%   title('bar stacket function')
%   xlabel('x label')
%   ylabel('y label')
%   ax = gca;
%
%   figure;
%   ax_new = axes;
%   copyaxes(ax, ax_new)
%
%
% EXAMPLE: Copy a axes and legend (the legend is an axes object)
%
%   plot([1:0.1:2*pi], sin([1:0.1:2*pi]));
%   title('sin function')
%   xlabel('x')
%   ylabel('sin(x)')
%   lg = legend('sin');
%   ax = gca;
%
%   figure;
%   ax_new = axes;
%   lg_new = axes;
%   copyaxes(ax, ax_new)
%   copyaxes(lg, lg_new)
%
% EXAMPLE: Copy an axes with a surface in a subplot.
%   Colormap is a figure property, not axes property.
%
%   k = 5;
%   n = 2^k-1;
%   [x,y,z] = sphere(n);
%   c = hadamard(2^k);
%   surf(x,y,z,c);
%   axis equal
%   title('sphere')
%   xlabel('x label')
%   ylabel('y label')
%   zlabel('z label')
%   ax = gca;
%   colormap([1  1  0; 0  1  1])
%
%   figure;
%   ax_new = subplot(2,2,1);
%   copyaxes(ax, ax_new, true)
%   colormap([1  1  0; 0  1  1])
%
% EXAMPLE:  Copy a surface in a subplot
%
%   [x,y] = meshgrid([-2:.4:2]);
%   Z = x.*exp(-x.^2-y.^2);
%   fh = figure('Position',[350 275 400 300],'Color','w');
%   ax = subplot(1,2,1);
%     set(ax, 'Color',[.8 .8 .8],'XTick',[-2 -1 0 1 2],...
%           'YTick',[-2 -1 0 1 2]);
%   sh = surface('XData',x,'YData',y,'ZData',Z,...
%              'FaceColor',get(ax,'Color')+.1,...
%              'EdgeColor','k','Marker','o',...
%              'MarkerFaceColor',[.5 1 .85]);
%   view(3)
%
%   ax_new = subplot(1,2,2);
%   copyaxes(ax, ax_new, true)
%
% EXAMPLE:  Copy an hist in a subplot
%
%     x = -4:0.1:4;
%     y = randn(500,1);
%     figure;
%     ax = subplot(1,2,1);
%     hist(y,x)
%
%     ax_new = subplot(1,2,2);
%     copyaxes(ax, ax_new, true)
%
%
% EXAMPLE:  Copy an pie in a subplot
%
%     x = [1 3 0.5 2.5 2];
%     explode = [0 1 0 0 0];
%     figure;
%     colormap jet
%     ax =  subplot(1,2,1);
%     pie(x,explode)
%
%     ax_new = subplot(1,2,2);
%     copyaxes(ax, ax_new, true)
%
%
% EXAMPLE:  Copy an figure in a subplot
%
%     load earth
%     figure
%     ax = subplot(1,2,1);
%     image(X); colormap(map)
%
%     ax_new = subplot(1,2,2);
%     copyaxes(ax, ax_new, true)
%
%
% tags: figure, copy, axes, line, pie, bar, hist, surface,
%
%
% author: Mar Callau-Zori
% PhD student - Universidad Politecnica de Madrid
%
% version: 1.2, December 2011
%

    isInSubplot = false;
    isLegend    = false;

    if nargin>2
        switch nargin

            case 3
                isInSubplot = varargin{1};
            case 4
                isInSubplot = varargin{1};
                isLegend    = varargin{2};
            otherwise
                error('ERROR:copyaxes:argChk', 'Wrong input arguments');
        end
    end

    if ~isLegend
        copyobj(get(source, 'Children'), destination);

        title = copyobj(get(source, 'Title'), destination);
        set(destination, 'Title', title);

        xlabel = copyobj(get(source, 'XLabel'), destination);
        set(destination, 'XLabel', xlabel);

        ylabel = copyobj(get(source, 'YLabel'), destination);
        set(destination, 'YLabel', ylabel);

        zlabel = copyobj(get(source, 'ZLabel'), destination);
        set(destination, 'ZLabel', zlabel);
    else
        set(destination, 'string', get(source, 'string'))
    end

    properties_str = {  'Units'
                        'ActivePositionProperty'
                        'ALim'
                        'ALimMode'
                        'AmbientLightColor'
                        'Box'
                        'CameraPosition'
                        'CameraPositionMode'
                        'CameraTarget'
                        'CameraTargetMode'
                        'CameraUpVector'
                        'CameraUpVectorMode'
                        'CameraViewAngle'
                        'CameraViewAngleMode'
                        'CLim'
                        'CLimMode'
                        'Color'
                        'CurrentPoint'
                        'ColorOrder'
                        'DataAspectRatio'
                        'DataAspectRatioMode'
                        'DrawMode'
                        'FontAngle'
                        'FontName'
                        'FontSize'
                        'FontUnits'
                        'FontWeight'
                        'GridLineStyle'
                        'Layer'
                        'LineStyleOrder'
                        'LineWidth'
                        'MinorGridLineStyle'
                        'NextPlot'
                        'OuterPosition'
                        'PlotBoxAspectRatio'
                        'PlotBoxAspectRatioMode'
                        'Projection'
                        'Position'
                        'TickLength'
                        'TickDir'
                        'TickDirMode'
                        'TightInset'
                        'View'
                        'XColor'
                        'XDir'
                        'XGrid'
                        'XAxisLocation'
                        'XLim'
                        'XLimMode'
                        'XMinorGrid'
                        'XMinorTick'
                        'XScale'
                        'XTick'
                        'XTickLabel'
                        'XTickLabelMode'
                        'XTickMode'
                        'YColor'
                        'YDir'
                        'YGrid'
                        'YAxisLocation'
                        'YLim'
                        'YLimMode'
                        'YMinorGrid'
                        'YMinorTick'
                        'YScale'
                        'YTick'
                        'YTickLabel'
                        'YTickLabelMode'
                        'YTickMode'
                        'ZColor'
                        'ZDir'
                        'ZGrid'
                        'ZLim'
                        'ZLimMode'
                        'ZMinorGrid'
                        'ZMinorTick'
                        'ZScale'
                        'ZTick'
                        'ZTickLabel'
                        'ZTickLabelMode'
                        'ZTickMode'
                        'BeingDeleted'
                        'ButtonDownFc'
                        'Clipping'
                        'CreateFcn'
                        'DeleteFcn'
                        'BusyAction'
                        'HandleVisibility'
                        'HitTest'
                        'Interruptible'
                        'Selected'
                        'SelectionHighlight'
                        'Tag'
                        'Type'
                        'UIContextMenu'
                        'UserData'
                        'Visible'};

    for i=1:length(properties_str)

        if (strcmpi(properties_str{i}, 'position') || strcmpi(properties_str{i}, 'outerposition'))

            if ~isInSubplot
                try
                    set(destination, properties_str{i}, get(source, properties_str{i}));
                catch e
                    if ~strcmpi(e.identifier, 'MATLAB:hg:propswch:FindObjFailed') && ...
                       ~strcmpi(e.identifier, 'MATLAB:hg:g_object:MustBeInSameFigure')
                            rethrow(e);
                    end
                end

            elseif isLegend
                % change the reference
                    try
                        units_dst = get(destination, 'units');
                        units_src = get(source, 'units');
                        set(destination, 'units', 'pixels');
                        set(source, 'units', 'pixels');
                        aux_dst = get(destination, properties_str{i});
                        aux_src = get(source, properties_str{i});
                        aux_dst(3) =  aux_src(3);
                        aux_dst(4) =  aux_src(4);
                        set(destination, properties_str{i}, aux_dst);
                        set(destination, 'units', units_dst);
                        set(source, 'units', units_src);
                    catch e
                        if ~strcmpi(e.identifier, 'MATLAB:hg:propswch:FindObjFailed') && ...
                           ~strcmpi(e.identifier, 'MATLAB:hg:g_object:MustBeInSameFigure')
                                rethrow(e);
                        end
                    end
            end
        else
            try
                set(destination, properties_str{i}, get(source, properties_str{i}));
            catch e
                if ~strcmpi(e.identifier, 'MATLAB:hg:propswch:FindObjFailed') && ...
                   ~strcmpi(e.identifier, 'MATLAB:hg:g_object:MustBeInSameFigure')
                         rethrow(e);
                end
            end
        end
    end

end


function opt = mpoption_info_cplex(selector)
%MPOPTION_INFO_CPLEX  Returns MATPOWER option info for CPLEX.
%
%   DEFAULT_OPTS = MPOPTION_INFO_CPLEX('D')
%   VALID_OPTS   = MPOPTION_INFO_CPLEX('V')
%   EXCEPTIONS   = MPOPTION_INFO_CPLEX('E')
%
%   Returns a structure for CPLEX options for MATPOWER containing ...
%   (1) default options,
%   (2) valid options, or
%   (3) NESTED_STRUCT_COPY exceptions for setting options
%   ... depending on the value of the input argument.
%
%   This function is used by MPOPTION to set default options, check validity
%   of option names or modify option setting/copying behavior for this
%   subset of optional MATPOWER options.
%
%   See also MPOPTION.

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

if nargin < 1
    selector = 'D';
end
if have_feature('cplex')
    switch upper(selector)
        case {'D', 'V'}     %% default and valid options
            opt = struct(...
                'cplex',        struct(...
                    'lpmethod',     0, ...
                    'qpmethod',     0, ...
                    'opts',         [], ...
                    'opt_fname',    '', ...
                    'opt',          0 ...
                ) ...
            );
        case 'E'            %% exceptions used by nested_struct_copy() for applying
            opt = struct(...
                'name',         { 'cplex.opts' }, ...
                'check',        0 ...
                );
%                 'copy_mode',    { @cplex_options } ...
        otherwise
            error('mpoption_info_cplex: ''%s'' is not a valid input argument', selector);
    end
else
    opt = struct([]);       %% CPLEX is not available
end


function startmen(parent_fig)
    % startmen adds menus for the basic zmap functions (Data, help)

    report_this_filefun();
    %
    %  This file display the original menu
    %
    %  Stefan Wiemer 12/94

    %  Create new figure
    % Find out if figure already exists
    %

    % Set up the Seismicity Map window Enviroment
    %
    if nargin==1
        disp(parent_fig)
        genmen = uimenu(parent_fig,'Label','Data');

        uimenu(genmen, ...
            'Label','Load Catalog (*.mat file)',...
            'MenuSelectedFcn', @(~,~)cb_load_file);%ZmapImportManager(@load_zmapfile));
        uimenu(genmen, ...
            'Label','Import Catalog from other formatted file',... %was Data ImportFilters
            'MenuSelectedFcn', @(~,~)ZmapImportManager(@zdataimport));
        uimenu(genmen, ...
            'Label','FDSN web fetch',... %TODO
            'MenuSelectedFcn', @(~,~)cb_load_web);%ZmapImportManager(@get_fdsn_data_from_web_callback));
        uimenu(genmen, ...
            'Label', 'Create or Modify *.mat datafile',...
            'MenuSelectedFcn', @(~,~)think_and_do('setup'));
        uimenu(genmen, ...
            'Label', 'Current Dataset Info',...
            'Enable','off',... % may no longer be relevent
            'MenuSelectedFcn', @(~,~)datinf());

        genmen = uimenu(parent_fig,'Label','Help');

        uimenu(genmen, ...
            'Label','Introduction and Help',...
            'MenuSelectedFcn', @(s,e) showweb('new'));

        uimenu(genmen, ...
            'Label','Sample Slide Show',...
            'MenuSelectedFcn',@(s,e) slshow());

    end
    function cb_load_file
        ok=ZmapImportManager(@load_zmapfile);
        if ok
            ZG= ZmapGlobal.Data;
            ZmapMainWindow([],ZG.primeCatalog);
        end
    end
    function cb_load_web
        ok=ZmapImportManager(@get_fdsn_data_from_web_callback);
        if ok
            ZG= ZmapGlobal.Data;
            ZmapMainWindow([],ZG.primeCatalog);
        end
    end
end

function think_and_do(~, ~, f_handle, varargin)

    if ischar(f_handle) && nargin==3
        evalin('base',f_handle); %use evalin??
    else
        f_handle(varargin{:});
    end
end


function [pvec, pstruct] = tapas_bayes_optimal_whatworld_transp(r, ptrans)
% --------------------------------------------------------------------------------------------------
% Copyright (C) 2012-2013 Christoph Mathys, TNU, UZH & ETHZ
%
% This file is part of the HGF toolbox, which is released under the terms of the GNU General Public
% Licence (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/>.

% This is a dummy function

pvec    = [];
pstruct = struct;

return;


function res = chantype(this, varargin)
% Method for setting/getting channel types
% FORMAT chantype(this, ind, type)
%   ind - channel index
%   type - type (string: 'EEG', 'MEG', 'LFP' etc.)
%
% FORMAT chantype(this, ind), chantype(this)
% Sets channel types to default using Fieldtrip channelselection
% _______________________________________________________________________
% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging

% Vladimir Litvak
% $Id: chantype.m 5933 2014-03-28 13:22:28Z vladimir $

if this.montage.Mind==0
    res = getset(this, 'channels', 'type', varargin{:});
else
    if nargin == 3
        this.montage.M(this.montage.Mind) = getset(this.montage.M(this.montage.Mind), 'channels', 'type', varargin{:});
        res = this;
    else
        res = getset(this.montage.M(this.montage.Mind), 'channels', 'type', varargin{:});
    end
end


% The COBRA Toolbox: testexportSetToGAMS
%
% Purpose:
%     - test exportSetToGAMS function
%
% Authors:
%     - Loic Marx, November 2018

% save the current path
currentDir = pwd;

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

% if nargin < 2
model = getDistributedModel('ecoli_core_model.mat');
fileName = 'refData_reactions.txt';

% read in the file
rxnsRef = textread(fileName, '%s', 'delimiter', '\n');

% test if the first and end cells are equal to /
assert(isequal(rxnsRef{1}, '/'))
assert(isequal(rxnsRef{end}, '/'))

% test if all the other cells correspond to the reaction names
for k = 2:length(model.rxns)-1
    assert(isequal(cellstr(rxnsRef{k}(2:end-1)), model.rxns(k-1)));
end

% run the function
exportSetToGAMS(model.rxns, 'Reactions.txt');

% compare to the ref data
refData = textread('Reactions.txt', '%s', 'delimiter', '\n');
assert(isequal(refData, rxnsRef))

% test for 1 input: exportSetToGAMS(model.rxns)
assert(verifyCobraFunctionError('exportSetToGAMS', 'inputs', {model.rxns}, 'testMessage', 'All inputs for the function exportSetToGAMS must be specified'))

% remove the test file
delete('Reactions.txt');

% change back to the current directory
cd(currentDir);


% mff_getobj - import information from MFF object
%
% Usage:
%   values = mff_setobj(mffObj, structure);
%
% Inputs:
%  mffObj    - MFF object of any type
%  structure - description of variables to assign
%
% Outputs:
%  values    - values for the structure
%
% Note: this function may take any MFF object structure

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

function matStruct = mff_getobj(mffObj, variables)

for iVar = 1:size(variables,1)
    varName  = variables{iVar,1};
    varType  = variables{iVar,2};
    varArray = variables{iVar,3};

    if ~isequal(varType, 'array')
        eval( [ 'matStruct.(lower(varName)) = ' varType '(mffObj.get' varName '());' ] );
    else
        if isempty(varName)
            for iArray = 1:mffObj.size
                matStruct.value(iArray) = mffObj.get(iArray-1);
            end
        else
            eval( [ 'tmparray = mffObj.get' varName '();' ] );
            if ~isempty(tmparray)
                for iArray = 1:tmparray.size
                    tmpval = tmparray.get(iArray-1);
                    if ~isempty(varArray)
                        matStruct.(lower(varName))(iArray) = mff_getobj(tmpval, varArray);
                    else
                        matStruct.(lower(varName))(iArray) = tmpval;
                    end
                end
            else
                matStruct.(lower(varName)) = [];
            end
        end
    end
end


function legendlinestyles(h,markers,linestyles,linecolors)
% legendlinestyles(h,markers,linestyles,linecolors)
%==========================================================================
% Change the linestyles in an existing plot legend.
%
% Input: Handle to an existing legend object, h
%        (optional) Cell array with the new markers, markers
%        (optional) Cell array with the new linestyles, linestyles
%        (optional) Cell array with the new line and marker colors
%
% If you do not want to change a markerstyle or linestyle, supply an empty
% array {}. See examples below.
%
% If the 'linestyles' arument is ommitted, the original lines are left
% unchanged. Note, that no lines in the actual plot are affected. If emtpy,
% {}, the styles are left unchanged but the colors are changed according to
% the fourth argument.
%
% Output: None
%
%
% Examples on how to use (see also accompanying file
% legendlinestyles_test.m):
%
%  legendlinestyles(h, markers);
%  legendlinestyles(h, markers,linestyles);
%  legendlinestyles(h, markers,linestyles,linecolors);
%  legendlinestyles(h, markers,{},linecolors);
%  legendlinestyles(h, {},linestyles,linecolors);
%  legendlinestyles(h, {},{},linecolors);
%
%==========================================================================
% Version: 1.0
% Created: October 3, 2008, by Johan E. Carlson
% Last modified: May 12, 2009, by Johan E. Carlson
%==========================================================================

lines = findobj(get(h,'children'),'type','line');
m = 1;
for k = length(lines):-2:1,
    if ~isempty(markers),
        set(lines(k-1),'marker',char(markers{m}));
    end
    if (nargin >= 3) && (~isempty(linestyles)),
        set(lines(k),'linestyle',char(linestyles{m}));
    end
    if nargin == 4,
        if ischar(linecolors{m})==1,
            set(lines(k),'color',char(linecolors{m}));
            if ~isempty(markers),
                set(lines(k-1),'MarkerEdgeColor',char(linecolors{m}));
            end
        else
            set(lines(k),'color',linecolors{m});
            if ~isempty(markers),
                set(lines(k-1),'MarkerEdgeColor',linecolors{m});
            end
        end
    end
    m = m+1;
end


function [ hdls ] = tracks_viewer( dates, track_data, background_data, ...
    pixel_dir, track_indexes, eddy_attribute_data, eddy_attribute_dir, eddy_plotting_data, chelton_eddies )
%TRACKS_VIEWER Start the eddy viewers with the following components
%   Current date box: display current date that the data will be plotted
%   Eddy type box: what type of eddies that will be plotted
%   Track type: what type of tracks will be plotted
%   Track length: minimum and maximum length of the tracks which will be plotted
%   Backgrond panel:
%       Dropdown box: select what background will be plotted
%       Min value, max value edit box: min and max values of the background data which will be plotted
%       Auto panel: auto adjusting background values when zoomed in/out
%   Show contours: whether or not showing eddies' contour
%   Show hurricanes: whether or not showing hurricanes
%   Select eddy button: select an eddy on the map to see more information of that eddy

hdls = ViewerHandle();
hdls.dates = dates;
hdls.track_data = track_data;
hdls.pixel_dir = pixel_dir;
hdls.track_indexes = track_indexes;
hdls.attribute_dir = eddy_attribute_dir;
hdls.eddy_attribute_data = eddy_attribute_data;
hdls.chelton_eddies = chelton_eddies;

hdls.constants.eddy_types = eddy_plotting_data.eddy_types;
hdls.constants.eddy_markers = eddy_plotting_data.eddy_markers;
hdls.constants.eddy_colors = eddy_plotting_data.eddy_colors;
hdls.constants.eddy_type_names = eddy_plotting_data.eddy_type_names;

hdls.constants.PAST_TRACK_COLOR = [0 1 0];
hdls.constants.CYC_FUTURE_TRACK_COLOR = [0 0 1];
hdls.constants.ANT_FUTURE_TRACK_COLOR = [1 0 0];
hdls.constants.CHELTON_CYC_FUTURE_TRACK_COLOR = 'k';
hdls.constants.CHELTON_ANT_FUTURE_TRACK_COLOR = 'm';
hdls.constants.CHELTON_CURRENT_TRACK_COLOR = 'k';
hdls.constants.CHELTON_MARKER = 'd';

hdls.constants.CURRENT_EDDY_SIZE = 9;
hdls.constants.OTHER_EDDY_SIZE = 6;

hdls.constants.COAST_COLOR = [205 133 63] / 255;

horizontalBarFontSize = 12;

%% Initialize GUI Components
hdls.fig = figure('Name', 'Tracks Viewer', ...
    'NumberTitle', 'off', ...
    'Units', 'normalized', ...
    'OuterPosition', [0 .05 1 .9], ...
    'NextPlot', 'replace');

set(hdls.fig,'toolbar','figure');
% Initialize map axes, set properties.
hdls.mapAx = axesm('pcarree', ...
    'Grid', 'on', ...
    'parallellabel', 'on', ...
    'meridianlabel', 'on',...
    'maplatlimit', [-90 90], ...
    'maplonlimit', [20 380]);
set(gca, ...
    'Units', 'normalized', ...
    'Position', [0 0 .85 .8]);

zoom_handle = zoom(hdls.fig);
set(zoom_handle,'ActionPostCallback',@postZoomCallback);
set(zoom_handle,'ActionPreCallback',@preZoomCallback);
mapLatLimit = getm(hdls.mapAx, 'maplatlimit');
mapLonLimit = getm(hdls.mapAx, 'maplonlimit');
[hdls.mapXLimit(1), hdls.mapYLimit(1)] = mfwdtran(mapLatLimit(1), mapLonLimit(1)); % The axis coordinates at which the map starts
[hdls.mapXLimit(2), hdls.mapYLimit(2)] = mfwdtran(mapLatLimit(2), mapLonLimit(2));
hdls.axisXLimit = [-4 4]; %Initial axis limits, will be used to figure out zoomed in region
hdls.axisYLimit = [-2 2];

colorbar();

hdls.coast_plot = draw_coast_line(hdls.mapAx, hdls.constants.COAST_COLOR);

hdls.currentDateIndex = 1;
initializeHorizontalBar();

initializeDateListBox(dates, hdls);

display_axis_legend();

%% GUI initialization functions
    function initializeHorizontalBar()
        width = .85;
        height = .075;
        separator = .005; % The distance between horizontal panels, corresponding to horizontal bar width
        horizontalBar = uipanel(hdls.fig, ...
            'Units', 'normalized', ...
            'Position', [0 .875 width height],...
            'BorderType', 'none', ...
            'BackgroundColor', [.8 .8 .8]);

        panel_width = 0.1;

        currentSize = [panel_width 1];
        currentLeftBottom = [0 0];
        initializeDateBoxPanel(horizontalBar, currentLeftBottom, currentSize, hdls, horizontalBarFontSize);

        lastSize = currentSize;
        lastLeftBottom = currentLeftBottom;
        currentSize = [panel_width 1];
        currentLeftBottom = ...
            [(lastLeftBottom(1) + lastSize(1) + separator) 0];
        initializeEddyTypePanel(horizontalBar, currentLeftBottom, currentSize, hdls, horizontalBarFontSize);

        lastSize = currentSize;
        lastLeftBottom = currentLeftBottom;
        currentSize = [panel_width 1];
        currentLeftBottom = ...
            [(lastLeftBottom(1) + lastSize(1) + separator) 0];
        initializeTrackTypePanel(horizontalBar, currentLeftBottom, currentSize, hdls, horizontalBarFontSize);

        lastSize = currentSize;
        lastLeftBottom = currentLeftBottom;
        currentSize = [panel_width 1];
        currentLeftBottom = ...
            [(lastLeftBottom(1) + lastSize(1) + separator) 0];
        initializeLengthFilterPanel(horizontalBar, currentLeftBottom, currentSize, hdls);

        lastSize = currentSize;
        lastLeftBottom = currentLeftBottom;
        currentSize = [panel_width 1];
        currentLeftBottom = ...
            [(lastLeftBottom(1) + lastSize(1) + separator) 0];
        initializeBackgroundPanel(horizontalBar, currentLeftBottom, currentSize, hdls, horizontalBarFontSize, background_data);

        lastSize = currentSize;
        lastLeftBottom = currentLeftBottom;
        currentSize = [panel_width 1];
        currentLeftBottom = ...
            [(lastLeftBottom(1) + lastSize(1) + separator) 0];
        initializeContourPanel(horizontalBar, currentLeftBottom, currentSize, hdls, horizontalBarFontSize);

        lastSize = currentSize;
        lastLeftBottom = currentLeftBottom;
        currentSize = [panel_width 1];
        currentLeftBottom = ...
            [(lastLeftBottom(1) + lastSize(1) + separator) 0];
        initializeCheltonPanel(horizontalBar, currentLeftBottom, currentSize, hdls, horizontalBarFontSize);

        lastSize = currentSize;
        lastLeftBottom = currentLeftBottom;
        currentSize = [panel_width 1];
        currentLeftBottom = ...
            [(lastLeftBottom(1) + lastSize(1) + separator) 0];
        initializeSelectEddyPanel(horizontalBar, currentLeftBottom, currentSize, hdls, horizontalBarFontSize);
    end

    function preZoomCallback(~, ~)
    end

    function postZoomCallback(~, evd)
        hdls.axisXLimit = get(evd.Axes, 'XLim');
        hdls.axisYLimit = get(evd.Axes, 'YLim');
        if hdls.is_autoadjusting_background
            hdls.update_background();
        end
    end

    function display_axis_legend()
        % Create dumb plot objects to legend plots on the map

        cyc_past_track_plot = plotm([NaN NaN], ...
            'Color', hdls.constants.PAST_TRACK_COLOR, ...
            'Parent', hdls.mapAx);
        cyc_future_track_plot = plotm([NaN NaN], ...
            'Color', hdls.constants.CYC_FUTURE_TRACK_COLOR, ...
            'Parent', hdls.mapAx);
        ant_future_track_plot = plotm([NaN NaN], ...
            'Color', hdls.constants.ANT_FUTURE_TRACK_COLOR, ...
            'Parent', hdls.mapAx);
        chelton_cyc_future_track_plot = plotm([NaN NaN], ...
            'Color', hdls.constants.CHELTON_CYC_FUTURE_TRACK_COLOR, ...
            'Parent', hdls.mapAx);
        chelton_ant_future_track_plot = plotm([NaN NaN], ...
            'Color', hdls.constants.CHELTON_ANT_FUTURE_TRACK_COLOR, ...
            'Parent', hdls.mapAx);

        eddy_plots = zeros(1, length(hdls.constants.eddy_types));
        for i = 1:length(hdls.constants.eddy_type_names)
            eddy_plots(i) = plotm([NaN NaN], ...
                'Color', hdls.constants.eddy_colors{i}, ...
                'marker', hdls.constants.eddy_markers{i}, ...
                'LineStyle', 'None', ...
                'Parent', hdls.mapAx);
        end

        chelton_eddy_plot = plotm([NaN NaN], ...
            'Color', hdls.constants.CHELTON_CURRENT_TRACK_COLOR, ...
            'Marker', hdls.constants.CHELTON_MARKER, ...
            'LineStyle', 'None');

        legend_names{1} = 'Coast';
        legend_names{2} = 'Tracks in the past';
        legend_names{3} = 'Future cyclonic tracks';
        legend_names{4} = 'Future anticyclonic tracks';
        legend_names{5} = 'Chelton future cyclonic tracks';
        legend_names{6} = 'Chelton future anticyclonic tracks';
        for i = 1:length(eddy_plots)
            legend_names{6 + i} = [hdls.constants.eddy_type_names{i} ' features'];
        end
        legend_names{end + 1} = 'Chelton features';

        legend( [hdls.coast_plot cyc_past_track_plot cyc_future_track_plot ant_future_track_plot ...
            chelton_cyc_future_track_plot chelton_ant_future_track_plot eddy_plots chelton_eddy_plot], ...
            legend_names);
    end
end