function [model, metProduction, addedRxnsForTasks, deletedRxnsInINIT, fullMipRes] = ftINIT(prepData, tissue, celltype, hpaData, transcrData, metabolomicsData, INITSteps, removeGenes, useScoresForTasks, paramsFT, verbose)
% ftINIT
%   Main function for generates a model using the ftINIT algorithm, based
%   on proteomics and/or transcriptomics and/or metabolomics and/or metabolic
%   tasks. The algorithm is not designed for running with metabolomics only.
%   The function prepINITModel needs to be run first for the template
%   model (such as the generic Human-GEM), but only need to be run once. This
%   function precalculates things independent of the omics to speed up the model
%   generation process, and outputs the prepData, which is input to this function.
%
%   prepData            The prepdata for the model.
%   tissue              tissue to score for. Should exist in either
%                       hpaData.tissues or transcrData.tissues
%   celltype            cell type to score for. Should exist in either
%                       hpaData.celltypes or transcrData.celltypes for this
%                       tissue (opt, default is to use the max values
%                       among all the cell types for the tissue.
%   hpaData             HPA data structure from parseHPA (opt if transcrData
%                       is supplied, default [])
%   transcrData         gene expression data structure (opt if hpaData is
%                       supplied, default []). Used to be called arrayData.
%       genes           cell array with the unique gene names
%       tissues         cell array with the tissue names. The list may not
%                       be unique, as there can be multiple cell types per
%                       tissue.
%       celltypes       cell array with the cell type names for each tissue
%       levels          GENESxTISSUES array with the expression level for
%                       each gene in each tissue/celltype. NaN should be
%                       used when no measurement was performed
%       threshold       a single value or a vector of gene expression
%                       thresholds, above which genes are considered to be
%                       "expressed". default = 1(opt, by default, the mean expression
%                       levels of each gene across all tissues in transcrData
%                       will be used as the threshold values)
%       singleCells     binary value selecting whether to use the
%                       single-cell algorithm to identify expressed genes.
%                       If used, specify cell subpopulations in CELLTYPES
%                       (opt, default [])
%       plotResults     true if single cell probability distributions
%                       should be plotted (opt, default = false)
%   metabolomicsData    cell array with metabolite names that the model
%                       should produce (opt, default [])
%   INITSteps           Specifies the steps in the algorithm. For more info,
%                       see INITStepDesc and getINITSteps.
%                       (opt, default getINITSteps(), which is the standard ftINIT).
%   removeGenes         if true, low-abundance genes will be removed from
%                       grRules, unless they are the only gene associated
%                       with a reaction, or a subunit of an enzyme complex
%                       (see "removeLowScoreGenes" function for details).
%                       If false, grRules will not be modified; however,
%                       genes that were associated only with removed
%                       reactions will not be present in the final model.
%                       (opt, default true).
%   useScoresForTasks   true if the calculated reaction scored should be
%                       used as weights when fitting to tasks (opt, default
%                       true)
%   paramsFT            parameter structure as used by getMILPParams. This
%                       is for the fitTasks step. For the INIT algorithm,
%                       see params (opt, default [])
%   verbose             if true, the MILP progression will be shown.
%                       (opt, default true)
%
%   model                   the resulting model structure
%   metProduction           array that indicates which of the
%                           metabolites in metabolomicsData that could be
%                           produced. Note that this is before the
%                           gap-filling process to enable defined tasks. To
%                           see which metabolites that can be produced in
%                           the final model, use canProduce.
%                           -2: metabolite name not found in model
%                           -1: metabolite found, but it could not be produced
%                           1: metabolite could be produced
%   addedRxnsForTasks       cell array of the reactions which were added in
%                           order to perform the tasks
%   deletedRxnsInINIT       cell array of reactions deleted because they
%                           could not carry flux (INIT requires a
%                           functional input model)
%   fullMipRes              The solver results from the last MILP step run
%
%   This is the main function for automatic reconstruction of models based
%   on the ftINIT algorithm ().
%
%   NOTE: Exchange metabolites should normally not be removed from the model
%   when using this approach, since checkTasks/fitTasks rely on putting specific
%   constraints for each task. The INIT algorithm will remove exchange metabolites
%   if any are present. Use importModel(file,false) to import a model with
%   exchange metabolites remaining.
%
%   Usage: [model, metProduction, addedRxnsForTasks, deletedRxnsInINIT, ...
%               fullMipRes] = ...
%               ftINIT(prepData, tissue, celltype, hpaData, transcrData, ...
%               metabolomicsData, INITSteps, removeGenes, useScoresForTasks, ...
%               paramsFT);
%

if nargin < 5
    transcrData = [];
end
if nargin < 6
    metabolomicsData = [];
end
if nargin < 7 || isempty(INITSteps)
    INITSteps = getINITSteps([],'1+1');
end
if nargin < 8 || isempty(removeGenes)
    removeGenes = true;
end
if nargin < 9 || isempty(useScoresForTasks)
    useScoresForTasks = true;
end
if nargin < 10
    paramsFT = [];
end

if nargin < 11
    verbose = true;
end
%Handle detected mets:
%Previously, this was handled by giving a bonus for secreting those metabolites,
%but that doesn't work since the metabolite secretion and uptake can be lost when
%we merge linearly dependent reactions.
%Instead, we need to figure out which reactions either produce or take up the mets.
%We then give a bonus if any of them carry flux.
%To simplify things, we focus on reactions that produce the metabolite (since there must be one such reaction).
%It is still a bit complicated though. In this step, we focus on identifying
%producer reactions. We further reason that the direction doesn't matter -
%we can force one of these reactions in any direction - if it becomes a consumer, it will
%automatically force another producer on as well (otherwise we'll have a net consumption).

if (~isempty(metabolomicsData))
    if length(unique(upper(metabolomicsData))) ~= length(metabolomicsData)
        dispEM('Metabolomics contains the same metabolite multiple times');
    end
    metData = false(numel(metabolomicsData), length(prepData.minModel.rxns)); %one row per metabolite that is a boolean vector
    for i=1:numel(metabolomicsData)
        %Get the matching mets
        metSel = ismember(upper(prepData.refModel.metNames),upper(metabolomicsData{i}));
        prodRxnsSel = any(prepData.refModel.S(metSel,:) > 0,1) | ... %direct producers
                     (any(prepData.refModel.S(metSel,:) < 0,1) & prepData.refModel.rev.'); %reversible reactions that are consumers
        %convert the production rxns from refModel to minModel
        prepData.groupIds;
        [~,ia,ib] = intersect(prepData.minModel.rxns,prepData.refModel.rxns);
        grpIdsMerged = nan(length(prepData.minModel.rxns),1);
        grpIdsMerged(ia) = prepData.groupIds(ib);

        groupIdsPos = unique(prepData.groupIds(prodRxnsSel));%gets all group ids which includes a production rxn
        groupIdsPos = groupIdsPos(groupIdsPos ~= 0);%remove the 0 id, it means there is no group
        %the other option is that there is a direct match between the rxn id in minModel and refModel:
        posRxns = prepData.refModel.rxns(prodRxnsSel);
        directMatch = ismember(prepData.minModel.rxns, posRxns).';

        metData(i,:) = ismember(grpIdsMerged, groupIdsPos).' | directMatch;
    end
    metData = sparse(metData);
else
    metData = [];
end

% Get rxn scores and adapt them to the minimized model
origRxnScores = scoreComplexModel(prepData.refModel,hpaData,transcrData,tissue,celltype);
origRxnScores(origRxnScores > -0.1 & origRxnScores <= 0) = -0.1;%we don't want reaction scores that are exactly 0 (or close), this causes problems in the milp
origRxnScores(origRxnScores < 0.1 & origRxnScores > 0) = 0.1;

rxnsTurnedOn = false(length(prepData.minModel.rxns),1);
fluxes = zeros(length(prepData.minModel.rxns),1);

rxnsToIgnoreLastStep = [1;1;1;1;1;1;1;1];

%We assume that all essential rxns are irrev - this is taken care of in
%prepINITModel. We then use an initial flux "from last run" of 0.1 for all
%reactions. This is used for knowing what flux should be forced through an
%essential rxn.
fluxes = ones(length(prepData.minModel.rxns), 1).*0.1;

for initStep = 1:length(INITSteps)
    disp(['ftINIT: Running step ' num2str(initStep)])
    stp = INITSteps{initStep};

    if any ((rxnsToIgnoreLastStep - stp.RxnsToIgnoreMask) < 0)
        dispEM('RxnsToIgnoreMask may not cover rxns not covered in previous steps, but the other way around is fine.');
    end
    rxnsToIgnoreLastStep = stp.RxnsToIgnoreMask;

    mm = prepData.minModel;

    if (~isempty(stp.MetsToIgnore))
        if (~isempty(stp.MetsToIgnore.simpleMets))
            %Here, we remove simple metabolites that will not really affect the milp but
            %are very common in the S matrix. For example H2O, H+, etc.
            %It is also possible to leave compartments untouched, for example the i compartment in the mitochondria (for H+).
            metsToRem = ismember(mm.metNames,stp.MetsToIgnore.simpleMets.mets);
            compsToKeep = find(ismember(mm.comps, stp.MetsToIgnore.simpleMets.compsToKeep));
            metsToRem = metsToRem & ~ismember(mm.metComps, compsToKeep);
            mm.S(metsToRem,:) = 0;
        end
    end

    %Set up the reaction scores and essential rxns
    rxnsToIgnore = getRxnsFromPattern(stp.RxnsToIgnoreMask, prepData);
    rxnScores = groupRxnScores(prepData.minModel, origRxnScores, prepData.refModel.rxns, prepData.groupIds, rxnsToIgnore);

    essentialRxns = prepData.essentialRxns;
    toRev = false(numel(mm.rxns),1);
    %Handle the results from previous steps ('ignore', 'exclude', 'essential')
    if strcmp(stp.HowToUsePrevResults, 'exclude')
        rxnScores(rxnsTurnedOn) = 0; %This is not used anymore in any step setup.
    elseif strcmp(stp.HowToUsePrevResults, 'essential')
        %Make all reversible reactions turned on in previous steps reversible
        %in the direction that they were previously carrying flux

        %first reverse the reactions that need to be reversed
        rev = mm.rev == 1;
        toRev = rxnsTurnedOn & rev & fluxes < 0;
        mm = reverseRxns(mm, mm.rxns(toRev));

        %Then make them irreversible
        mm.rev(rxnsTurnedOn) = 0;
        mm.lb(rxnsTurnedOn) = 0;

        essentialRxns = unique([prepData.essentialRxns;mm.rxns(rxnsTurnedOn)]);
    end

    mipGap = 1;
    first = true;
    success = false;
    fullMipRes = [];
    for rn = 1:length(stp.MILPParams)
        params = stp.MILPParams{rn};
        if ~isfield(params, 'MIPGap')
            params.MIPGap = 0.0004;
        end

        if ~isfield(params, 'TimeLimit')
            params.TimeLimit = 5000;
        end

        if ~first
            %There is sometimes a problem with that the objective function becomes close to zero,
            %which leads to that a small percentage of that (which is the MIPGap sent in) is very small
            %and the MILP hence takes a lot of time to finish. We also therefore use an absolute MIP gap,
            %converted to a percentage using the last value of the objective function.
            params.MIPGap = min(max(params.MIPGap, stp.AbsMIPGaps{rn}/abs(lastObjVal)),1);
            params.seed = 1234;%use another seed, may work better

            if mipGap <= params.MIPGap
                success = true;
                break; %we're done - this will not happen the first time
            else
                disp(['MipGap too high, trying with a different run. MipGap = ' num2str(mipGap) ' New MipGap Limit = ' num2str(params.MIPGap)])
            end
        end

        first = false;

        %now run the MILP
        try
            %The prodweight for metabolomics is currently set to 5 - 0.5 was default in the old version, which I deemed very small?
            %There could be a need to specify this somewhere in the call at some point.
            %This value has not been evaluated, but is assumed in the test cases - if changed, update the test case
            startVals = [];
            if ~isempty(fullMipRes)
                startVals = fullMipRes.full;
            end
            [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, startVals, fluxes, verbose);
            %This is a bit tricky - since we reversed some reactions, those fluxes also need to be reversed
            fluxes1(toRev) = -fluxes1(toRev);

            mipGap = fullMipRes.mipgap;
            lastObjVal = fullMipRes.obj;
        catch e
            mipGap = Inf;
            lastObjVal = Inf; %we need to set something here, Inf leads to that this doesn't come into play
        end

        success = mipGap <= params.MIPGap;
    end

    if ~success
        dispEM(['Failed to find good enough solution within the time frame. MIPGap: ' num2str(mipGap)]);
    end

    %save the reactions turned on and their fluxes for the next step
    rxnsTurnedOn = rxnsTurnedOn | rxnsTurnedOn1.';
    %The fluxes are a bit tricky - what if they change direction between the steps?
    %The fluxes are used to determine the direction in which reactions are forced on
    %(to simplify the problem it is good if they are unidirectional).
    %We use the following strategy:
    %1. Use the fluxes from the most recent step.
    %2. If any flux is very low there (i.e. basically zero), use the flux from the previous steps
    %This could in theory cause problems, but seems to work well practically
    fluxesOld = fluxes;
    fluxes = fluxes1;
    %make sure that all reactions that are on actually has a flux - otherwise
    %things could go bad, since the flux will be set to essential in a random direction
    %This sometimes happens for rxns with negative score - let's just accept that.
    %if (sum(abs(fluxes1) < 10^-7 & rxnsTurnedOn))
    %    dispEM('There are rxns turned on without flux - this might cause problems');
    %end
    %fluxes(abs(fluxes1) < 10^-7) = fluxesOld(abs(fluxes1) < 10^-9);
end

%get the essential rxns
essential = ismember(prepData.minModel.rxns,prepData.essentialRxns);
%So, we only add reactions where the linearly merged scores are zero for all linearly dependent reactions
% (this cannot happen by chance, taken care of in the function groupRxnScores)
rxnsToIgn = rxnScores == 0;
deletedRxnsInINITSel = ~(rxnsTurnedOn | rxnsToIgn | essential);
deletedRxnsInINIT = prepData.minModel.rxns(deletedRxnsInINITSel);

%Here we need to figure out which original reactions (before the linear merge)
%that were removed. These are all reactions with the same group ids as the removed reactions
groupIdsRemoved = prepData.groupIds(ismember(prepData.refModel.rxns, deletedRxnsInINIT)); %can improve this slightly, use sel above
groupIdsRemoved = groupIdsRemoved(groupIdsRemoved ~= 0);%zero means that the reaction was not grouped, all with zeros are not a group!
rxnsToRem = union(prepData.refModel.rxns(ismember(prepData.groupIds,groupIdsRemoved)), deletedRxnsInINIT);%make a union here to include the ungrouped (unmerged) as well

initModel = removeReactions(prepData.refModel,rxnsToRem,false,true);

% remove metabolites separately to avoid removing those needed for tasks
unusedMets = initModel.mets(all(initModel.S == 0,2));
initModel = removeMets(initModel, setdiff(unusedMets, prepData.essentialMetsForTasks));

%if printReport == true
%    printScores(initModel,'INIT model statistics',hpaData,transcrData,tissue,celltype);
%    printScores(removeReactions(cModel,setdiff(cModel.rxns,rxnsToRem),true,true),'Reactions deleted by INIT',hpaData,transcrData,tissue,celltype);
%end

%The full model has exchange reactions in it. ftINITFillGapsForAllTasks calls
%ftINITFillGaps, which automatically removes exchange metabolites (because it
%assumes that the reactions are constrained when appropriate). In this case the
%uptakes/outputs are retrieved from the task sheet instead. To prevent
%exchange reactions being used to fill gaps, they are deleted from the
%reference model here.
initModel.id = 'INITModel';

%If gaps in the model should be filled using a task list
if ~isempty(prepData.taskStruct)
    %Remove exchange reactions and reactions already included in the INIT
    %model
    %We changed strategy and instead include all rxns except the exchange rxns in the ref model
    %But we do keep the exchange rxns that are essential.
    %Let's test to remove all, that should work

    %At this stage the model is fully connected and most of the genes with
    %good scores should have been included. The final gap-filling should
    %take the scores of the genes into account, so that "rather bad"
    %reactions are preferred to "very bad" reactions. However, reactions
    %with positive scores will be included even if they are not connected
    %in the current formulation. Therefore, such reactions will have to be
    %assigned a small negative score instead.
    exchRxns = getExchangeRxns(prepData.refModel);
    refModelNoExc = removeReactions(prepData.refModelWithBM,exchRxns,true,true);
    exchRxns = getExchangeRxns(initModel);
    initModelNoExc = removeReactions(closeModel(initModel),exchRxns,true,true);

    if useScoresForTasks == true
        %map the rxn scores to the model without exchange rxns
        [~,ia,ib] = intersect(refModelNoExc.rxns,prepData.refModel.rxns);
        rxnScores2nd = NaN(length(refModelNoExc.rxns),1);
        rxnScores2nd(ia) = origRxnScores(ib);
        %all(rxnScores2nd == refRxnScores);%should be the same, ok!
        [outModel,addedRxnMat] = ftINITFillGapsForAllTasks(initModelNoExc,refModelNoExc,[],true,min(rxnScores2nd,-0.1),prepData.taskStruct,paramsFT,verbose);
    else
        [outModel,addedRxnMat] = ftINITFillGapsForAllTasks(initModelNoExc,refModelNoExc,[],true,[],prepData.taskStruct,paramsFT,verbose);
    end
    %if printReport == true
    %    printScores(outModel,'Functional model statistics',hpaData,transcrData,tissue,celltype);
    %    printScores(removeReactions(outModel,intersect(outModel.rxns,initModel.rxns),true,true),'Reactions added to perform the tasks',hpaData,transcrData,tissue,celltype);
    %end

    addedRxnsForTasks = refModelNoExc.rxns(any(addedRxnMat,2));
else
    outModel = initModel;
    addedRxnMat = [];
    addedRxnsForTasks = {};
end

% The model can now perform all the tasks defined in the task list.
model = outModel;

% At this stage the model will contain some exchange reactions but probably
% not all (and maybe zero). This can be inconvenient, so all exchange
% reactions from the reference model are added, except for those which
% involve metabolites that are not in the model.

%Start from the original model, and just remove the reactions that are no longer there (and keep exchange rxns). The model we got out
%from the problem is not complete, it doesn't have GRPs etc.
%The logic below is a bit complicated. We identify the reactions that should be removed from the full model as
%reactions that have been removed in the init model except the ones that were added back. In addition, we make
%sure that no exchange rxns are removed - they can be removed in the init model if they were linearly merged with other
%reactions that were decided to be removed from the model. We want to keep all exchange rxns to make sure the tasks can
%be performed also without manipulating the b vector in the model (which is what is done in the gap-filling).
exchRxns = getExchangeRxns(prepData.refModel);
deletedRxnsInINIT = setdiff(prepData.refModel.rxns,union(union(initModel.rxns, addedRxnsForTasks), exchRxns));
outModel = removeReactions(prepData.refModel, deletedRxnsInINIT, true); %we skip removing the genes for now, I'm not sure it is desirable

% If requested, attempt to remove negative-score genes from the model,
% depending on their role (isozyme or complex subunit) in each grRule.
% See the "removeLowScoreGenes" function more more details, and to adjust
% any default parameters therein.
if ( removeGenes )
    [~, geneScores] = scoreComplexModel(outModel,hpaData,transcrData,tissue,celltype);
    outModel = removeLowScoreGenes(outModel,geneScores);
end

model = outModel;

end

%This is for printing a summary of a model
function [rxnS, geneS] = printScores(model,name,hpaData,transcrData,tissue,celltype)
    [a, b] = scoreComplexModel(model,hpaData,transcrData,tissue,celltype);
    rxnS = mean(a);
    geneS = mean(b,'omitnan');
    fprintf([name ':\n']);
    fprintf(['\t' num2str(numel(model.rxns)) ' reactions, ' num2str(numel(model.genes)) ' genes\n']);
    fprintf(['\tMean reaction score: ' num2str(rxnS) '\n']);
    fprintf(['\tMean gene score: ' num2str(geneS) '\n']);
    fprintf(['\tReactions with positive scores: ' num2str(100*sum(a>0)/numel(a)) '%%\n\n']);
end

function rxnsToIgnore = getRxnsFromPattern(rxnsToIgnorePattern, prepData)
    rxnsToIgnore = false(length(prepData.toIgnoreExch),1);
    if rxnsToIgnorePattern(1) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreExch; end;
    if rxnsToIgnorePattern(2) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreImportRxns; end;
    if rxnsToIgnorePattern(3) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreSimpleTransp; end;
    if rxnsToIgnorePattern(4) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreAdvTransp; end;
    if rxnsToIgnorePattern(5) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreSpont; end;
    if rxnsToIgnorePattern(6) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreS; end;
    if rxnsToIgnorePattern(7) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreCustomRxns; end;
    if rxnsToIgnorePattern(8) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreAllWithoutGPRs; end;
end


function [flat, allDist, sumDist, geodesicCoords, geodesicLayer] = measureCorticalDistance(flat, coords, verboseFlag, gray)
% USAGE: measureCorticalDistance(flat, [coords], [verboseFlag], [gray])
%
% AUTHOR:  Dougherty
% DATE:    2002.01.15
% PURPOSE:
%   Compute the shortest cortical manifold distance between
%   points. If coords is passed in, then the points are drawn
%   from there. Otherwise, the points are obtained via ginput.
%   (Note that coords must be a valid nx3 list of flat view coords,
%   such as would be obtained by 'getCurROIcoords(flatView)'.)
%
% HISTORY
%
% 7/16/02 djh, replaced mrSESSION.vAnatomyPath with global vANATOMYPATH
% 9/02/04 mms, inserted a warning not to use rotated flats.
% 9/22/04 rfd, now returns the geodesic path (as coords) and the gray layer
% of each point along the path.

global vANATOMYPATH;
mmPerPix = readVolAnatHeader(vANATOMYPATH);

% Check some flat and volume stuff here when you
% get a chance to fix this code up

% Get a gray structure because we need the gray nodes.
if notDefined('gray')
    gray = getSelectedGray;
    if isempty(gray)
        gray=initHiddenGray;
    end
end

if(~exist('coords','var') | isempty(coords))
    % Select flat figure and get a single point from the user
    figure(flat.ui.figNum)
    disp('Click left to add points, right to quit');
    button = 1;
    count = 0;
    w = 0.5;
    coords = [];
    z = viewGet(flat, 'Current Slice');
    while(button~=3)
        [x,y,button] = ginput(1);
        if(button==3)
            break;
        end
        count = count+1;
        coords = [coords,round([y;x;z])];
        h(count) = line([x-w,x-w,x+w,x+w,x-w],[y-w,y+w,y+w,y-w,y-w],'Color','w');
        if flat.rotateImageDegrees(coords(3,1))>0  %detects if the Flat your are currently selecting the points ist rotated
            errordlg('Flat must not be rotated','Warning!'); %Results would be wrong!
        end
    end
    % Delete the temporarily drawn squares
    for ii=1:length(h)
        delete(h(ii));
    end
    clear h;
end
if(~exist('verboseFlag','var') || isempty(verboseFlag))
    verboseFlag=1;
end

% the third coordinate is the 'slice', which, for flat views, means left or right hemisphere.
slice = coords(3,1);
if (slice==1)
    nodes = gray.allLeftNodes;
    edges = gray.allLeftEdges;
else
    nodes = gray.allRightNodes;
    edges = gray.allRightEdges;
end

% We loop for the number of line segements, which is the number of
% coords - 1.
geodesic = [];
for(ii=1:size(coords,2)-1)
    % get nearest flat coordinate (not all points on the flat correspond to flat coordinates)
    flatDistances = (flat.coords{slice}(1,:) - coords(1,ii)).^2 + ...
        (flat.coords{slice}(2,:) - coords(2,ii)).^2;
    % There is a one-to-many mapping of flatCoords to grayCoords, but we ignore that
    % here by using 'min', which will always reuturn one value, even if there are several
    % identical minima.
    % FIX THIS- we should always grab layer 1, or something more consistent
    % than relying on min's arbitrary sort.
    [val,startIndex] = min(flatDistances);
    % Do it again, for ii+1, to find the end point of this line segment.
    flatDistances = (flat.coords{slice}(1,:) - coords(1,ii+1)).^2 + ...
        (flat.coords{slice}(2,:) - coords(2,ii+1)).^2;
    [val,endIndex] = min(flatDistances);

    % Draw a line for each measured segment
    % we use the actual gray node coords rather than the ROI coords, so the use can see if
    % there is any non-trivial discrepancy.
    h(ii) = line([flat.coords{slice}(2,startIndex),flat.coords{slice}(2,endIndex)], ...
                 [flat.coords{slice}(1,startIndex),flat.coords{slice}(1,endIndex)], ...
                 'Color', 'r', 'LineWidth', 2);

    % Extract the gray node corresponding to the start
    startGrayNode = find(nodes(2,:) == flat.grayCoords{slice}(1,startIndex) & ...
                         nodes(1,:) == flat.grayCoords{slice}(2,startIndex) & ...
                         nodes(3,:) == flat.grayCoords{slice}(3,startIndex));
    endGrayNode = find(nodes(2,:) == flat.grayCoords{slice}(1,endIndex) & ...
                       nodes(1,:) == flat.grayCoords{slice}(2,endIndex) & ...
                       nodes(3,:) == flat.grayCoords{slice}(3,endIndex));

    % Catch errors. If we give mrManDist an empty startPoint array, it barfs.
    if(isempty(startGrayNode) | isempty(endGrayNode))
        myErrorDlg('No gray nodes were found for these coords!');
    end

    % Now, compute the manifold distance between these points.
    % mrManDist returns the distance to all other points from the given 'start' point.
    %allDist = mrManDist(nodes, edges, startGrayNode, mmPerPix, -1, 0);
    [allDist,nPts,lastPoint] = mrManDist(nodes, edges, startGrayNode, ...
                                      mmPerPix, -1,0);
    nextPoint = endGrayNode;
    while(nextPoint~=startGrayNode)
      geodesic(end+1) = lastPoint(nextPoint);
      nextPoint = lastPoint(nextPoint);
    end

    % We just want the distance from the start point to the end point, so we
    % pull that out by providing the index of the end point.
    dist(ii) = allDist(endGrayNode);
    if(verboseFlag>=0)
        disp(['Cortical distance of segment ',num2str(ii),': ',num2str(dist(ii)),' mm.']);
    end
end
sumDist=sum(dist);
if(verboseFlag>=0)
    disp(['Total cortical distance: ',num2str(sumDist),' mm.']);
end
if(verboseFlag>0)
    uiwait(msgbox(['Total cortical distance: ',num2str(sumDist),' mm.'], ...
        'Cortical Distance', 'modal'));
end

for ii=1:length(h)
    delete(h(ii))
end
geodesicCoords = nodes([2,1,3],geodesic);
geodesicLayer =  nodes(6,geodesic);
return


function [At,b,c,K,prep,origcoeff] = pretransfo(At,b,c,K,pars)

% [At,b,c,K,prep] = pretransfo(At,b,c,K)
%
% PRETRANSFO  Checks data and then transforms into internal SeDuMi format.
%
% **********  INTERNAL FUNCTION OF SEDUMI **********
%
% See also sedumi

% Nearly complete rewrite
% Copyright (c) 2013 Michael C. Grant
%
% This file is part of SeDuMi 1.1 by Imre Polik and Oleksandr Romanko
% Copyright (C) 2005 McMaster University, Hamilton, CANADA  (since 1.1)
%
% Copyright (C) 2001 Jos F. Sturm (up to 1.05R5)
%   Dept. Econometrics & O.R., Tilburg University, the Netherlands.
%   Supported by the Netherlands Organization for Scientific Research (NWO).
%
% Affiliation SeDuMi 1.03 and 1.04Beta (2000):
%   Dept. Quantitative Economics, Maastricht University, the Netherlands.
%
% Affiliations up to SeDuMi 1.02 (AUG1998):
%   CRL, McMaster University, Canada.
%   Supported by the Netherlands Organization for Scientific Research (NWO).
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA
% 02110-1301, USA

% -------------------------------------------------------------------------
% Make sure that all fields exist in K, and verify that they are valid
% -------------------------------------------------------------------------

if ~isfield(K,'f') || isempty(K.f)
    K.f = 0;
elseif numel(K.f) ~= 1 || K.f ~= floor(K.f) || K.f < 0 || ~isreal(K.f)
    error('K.f should be nonnegative integer')
end
if ~isfield(K,'l') || isempty(K.l)
    K.l = 0;
elseif K.l ~= floor(K.l) || K.l < 0 || ~isreal(K.l)
    error('K.l should be nonnegative integer')
end
if ~isfield(K,'q') || ~nnz(K.q)
    K.q = zeros(1,0);
else
    K.q = K.q(:)';
    if any(K.q ~= floor(K.q)) || any(K.q<2) || ~isreal(K.q)
        error('K.q should contain only integers bigger than 1')
    end
end
if ~isfield(K,'r') || ~nnz(K.r)
    K.r = zeros(1,0);
else
    K.r = K.r(:)';
    if any(K.r ~= floor(K.r)) || any(K.r<3) || ~isreal(K.r)
        error('K.r should contain only integers bigger than 2')
    end
end
if ~isfield(K,'s') || ~nnz(K.s)
    K.s = zeros(1,0);
else
    K.s = K.s(:)';
    if any(K.s ~= floor(K.s)) || any(K.s<1) || ~isreal(K.s)
        error('K.s should contain only positive integers')
    end
end
% As an alternative to the 'scomplex' flag, I've added a 'z' parameter
% containing a list of Hermitian semidefinite cone sizes. For now, this
% is just translated to 'scomplex' for you. In the future, we may use
% this internally *instead* of scomplex or rsdpN.
if ~isfield(K,'z') || ~nnz(K.z)
    K.z = zeros(1,0);
else
    K.z = K.z(:)';
    if any(K.z ~= floor(K.z)) || any(K.z<1) || ~isreal(K.z)
        error('K.z should contain only positive integers')
    end
end

N_f    = K.f;
N_l    = K.l;
N_fl   = N_f + N_l;
L_q    = length(K.q);
N_q    = sum(K.q);
L_r    = length(K.r);
N_r    = sum(K.r);
N_qr   = N_q + N_r;
L_qr   = L_q + L_r;
L_s    = length(K.s);
L_z    = length(K.z);
L_sz   = L_s + L_z;
N_s    = sum((K.s).^2);
N_z    = sum((K.z).^2);
N_sz   = N_s + N_z;
L_qrsz = L_qr + L_sz;
N_flqr = N_fl + N_qr;
N      = N_flqr + N_sz;

if ~isfield(K,'ycomplex') || isempty(K.ycomplex)
    K.ycomplex = zeros(1,0);
else
    K.ycomplex = sort(K.ycomplex(:))';
    K.ycomplex(find(~diff(K.ycomplex))+1) = [];
    if any(K.ycomplex ~= floor(K.ycomplex)) || any(K.ycomplex<1)
        error('K.ycomplex should contain only positive integers');
    elseif any(K.ycomplex > numel(b))
        error('Elements of K.ycomplex are out of range');
    end
end
if ~isfield(K,'xcomplex') || isempty(K.xcomplex)
    K.xcomplex = zeros(1,0);
else
    K.xcomplex = sort(K.xcomplex(:))';
    K.xcomplex(find(~diff(K.xcomplex))+1) = [];
    if any(K.xcomplex ~= floor(K.xcomplex)) || any(K.xcomplex<1)
        error('K.xcomplex should contain only positive integers');
    elseif any(K.xcomplex>N_flqr)
        error('Elements of K.xcomplex are out of range');
    end
end
if ~isfield(K,'scomplex') || isempty(K.scomplex)
    K.scomplex = zeros(1,0);
else
    K.scomplex = sort(K.scomplex(:))';
    K.scomplex(find(~diff(K.scomplex))+1) = [];
    if any(K.scomplex~=floor(K.scomplex)) || any(K.scomplex<1)
        error('K.scomplex should contain only positive integers');
    elseif any(K.scomplex>L_s)
        error('Elements of K.xcomplex are out of range');
    end
end
if L_z
    K.s = [ K.s, K.z ];
    K.scomplex = [ K.scomplex, L_s + 1 : L_sz ];
    K.z = zeros(1,0);
    L_s = L_s + L_z;
    N_s = N_s + N_z;
    L_z = 0; %#ok
    N_z = 0; %#ok
end

% -------------------------------------------------------------------------
% Verify the size and validity of At, b, and c
% N = # variables
% -------------------------------------------------------------------------

% SeDuMi assumes that if At is not consistent with K but At' is, that the
% user supplied the transpose of the coefficient matrix. This introduces a
% rare ambiguity in the case where At happens to be square. Past versions
% of SeDuMi would not have allowed this, instead rejecting the case where
% m >= N; however, with complex variables, the situation is not quite as
% clear, and there may technically be cases where m >= N is acceptable.

if ndims(At) > 2 %#ok
    error('A must be a matrix');
elseif nnz(isnan(At)) || nnz(isinf(At))
    error('A contains NaN or Inf');
elseif size(At,1) == N
    % nothing
elseif size(At,2) == N
    At = At';
else
    error('(At,K) size mismatch');
end
if all(size(b)>1)
    error('Parameter b must be a vector');
elseif any(isnan(b)) || any(isinf(b))
    error('b contains NaN or Inf');
elseif length(b) ~= size(At,2)
    error('(At,b) size mismatch');
else
    b = b(:);
end
if all(size(c)>1)
    error('Parameter c must be a vector');
elseif any(isnan(c)) || any(isinf(c))
    error('c contains NaN or Inf');
elseif length(c) ~= N
    error('(c,K) size mismatch');
else
    c = c(:);
end

% -------------------------------------------------------------------------
% Save the standardized data for further use if needed
% -------------------------------------------------------------------------

if isfield(pars,'errors') && pars.errors==1
    origcoeff.At=At;
    origcoeff.c=c;
    origcoeff.b=b;
    origcoeff.K=K;
else
    origcoeff=[];
end

% -------------------------------------------------------------------------
% Flag diagonal SDP blocks for removal
% -------------------------------------------------------------------------

% There is some serious MATLAB trickery here (if I do say so myself) that
% merits explanation. "spattern" contains the indices of symbolically
% nonzero elements of the dual variable z = c - At * y. This is a single
% vector across all LMI constraints, so "sblk" tells us which indices
% belong to which constraints. The "rem" statement is what determines if
% a particular element is off-diagonal. If there is even one off-diagonal
% element in an SDP, then its corresponding element of "sdiag" is false.
%
% This new version of the analysis replaces the old preprocessSDP(), and
% seems inexpensive enough to apply to all LMIs regardless of size/count.
% The previous version was applied more sparingly.
%
% In theory one could do a more complex analysis of the block structure of
% an LMI, potentially breaking a larger into smaller ones. But this would
% likely be significantly more expensive.

if L_s && ( ~isfield(pars,'sdp') || pars.sdp )
    ssiz        = (K.s).^2;
    strt        = cumsum([1,ssiz(1:end-1)]);
    sblk        = zeros(1,N_s);
    sblk(strt)  = 1;
    sblk        = cumsum(sblk);
    spattern    = find(c(N_flqr+1:N)~=0|any(At(N_flqr+1:N,:),2))';
    sblk        = sblk(spattern);
    sblk        = sblk(rem(spattern-strt(sblk),K.s(sblk)+1)~=0);
    sdiag       = true(1,L_s);
    sdiag(sblk) = false;
else
    % Even if we disable SDP processing, we're going to move 1x1 SDPs into
    % the nonnegative variable block. It just doesn't make sense to deploy
    % all of that SDP machinery for nonnegative variables.
    sdiag = K.s == 1;
end

% -------------------------------------------------------------------------
% Handle K.ycomplex by splitting apart the complex constraints into pairs
% of real constraints
% -------------------------------------------------------------------------

if ~isempty(K.ycomplex)
    b  = [ real(b) ; imag(b(K.ycomplex)) ];
    At = [ At, 1j * At(:,K.ycomplex) ];
else
    b  = real(b);
end

% -------------------------------------------------------------------------
% Find the locations of the the complex data, so we can convert into
% SeDuMi's internal format, which uses only MATLAB's real representation.
% -------------------------------------------------------------------------
% Strictly speaking, nonnegative variables, the first variable in a Lorentz
% cone, and the first two variables in a rotated Lorentz cone are real. But
% But SeDuMi has allowed them all to be specified as complex; the imaginary
% portions are interpreted as free variables. We have kept that behavior.

% This code replaces whichcpx.c in its entirety. It actually fixes a bug in
% rotated Lorentz cone handling that was probably never exercised.
if isempty(K.xcomplex) && isempty(K.scomplex)
    K.fcplx = zeros(1,0);
    K.qcplx = zeros(1,0);
    K.rcplx = zeros(1,0);
    scplx   = false(1,L_s);
    sreal   = ~sdiag;
    K.rsdpN = L_s;
    N_fc = 0;
    K.cdim = 0;
else
    xc = K.xcomplex;
    tt = xc <= N_fl;
    K.fcplx = xc(tt);
    xc = xc(~tt) -  N_fl;
    tt = xc <= N_q;
    K.qcplx = xc(tt);
    xc = xc(~tt) - N_q;
    tt = xc <= N_r;
    K.rcplx = xc(tt);
    if ~isempty(K.qcplx)
        ndxs = cumsum([1,K.q(1:end-1)]);
        t2 = any(bsxfun(@eq,K.qcplx,ndxs'),1);
        K.fcplx = [ K.fcplx, K.qcplx(t2) + N_fl ];
        K.qcplx(t2) = [];
        t2 = sum(bsxfun(@gt,K.qcplx,ndxs'),1);
        K.q = K.q + full(sparse(1,t2,1,1,L_q));
        K.qcplx = K.qcplx + (1:length(K.qcplx));
        N_q = N_q + length(K.qcplx);
    end
    if ~isempty(K.rcplx)
        ndxs = cumsum([1,K.r(1:end-1)]);
        t2 = any(bsxfun(@eq,K.rcplx,[ndxs,ndxs+1]'),1);
        K.fcplx = [ K.fcplx, K.rcplx(t2) + N_fl + N_q ];
        K.rcplx(t2) = [];
        t2 = sum(bsxfun(@gt,K.rcplx,ndxs'),1);
        K.r = K.r + full(sparse(1,t2,1,1,L_r));
        % This 2*t2 offset is required because QR makes two accesses
        % each of the first two elements of a rotated Lorentz cone.
        K.rcplx = K.rcplx + (1:length(K.rcplx)) + 2 * t2;
        N_r = N_r + length(K.rcplx);
    end
    N_fc = length(K.fcplx);
    N_f  = N_f + N_fc;
    scplx = false(1,L_s);
    scplx(K.scomplex&~sdiag) = true;
    sreal = ~scplx & ~sdiag;
    K.rsdpN = nnz(sreal);
    K.cdim = length(K.xcomplex) + sum(K.s(scplx).^2);
end

% -------------------------------------------------------------------------
% We have significantly rewritten this section of the code. This section
% constructs a sparse matrix that represents the following transformations:
%   --- Free variables split into differences of nonnegative variables; OR
%   --- Free variables placed in a Lorentz-cone
%   --- Rotated lorentz cones translated to standard Lorentz cones
%   --- Lorentz cones rearranged to trace block + norm-bound blocks
%   --- Conversion of diagonal SDPs to nonnegative variables
%   --- SDP coefficients moved to the lower triangle for increased sparsity
% This code replaces the rotlorenz, qreshape, and vectril MEX files.
% -------------------------------------------------------------------------

newL = 0;
newQ = zeros(1,0);
ii = {}; jj = {}; vv = {};

% Split free variables into the difference of nonnegatives
if ~isfield( pars, 'free' ) || pars.free == 2 && L_qrsz
    pars.free = 1;
end
if N_f && ~pars.free
    jt = [ 1 : K.f, K.fcplx ; 1 : K.f, K.fcplx ];
    vt = [ ones(1,K.f), -1j*ones(1,N_fc) ; -ones(1,K.f), 1j*ones(1,N_fc) ];
    ii{end+1} = 1 : 2 * N_f;
    jj{end+1} = jt(:)';
    vv{end+1} = vt(:)';
    newL = 2 * N_f;
    prep.freeL = N_f;
end

% Copy nonnegative variables without change
if K.l
    ii{end+1} = newL + 1 : newL + K.l;
    jj{end+1} = K.f + 1 : K.f + K.l;
    vv{end+1} = ones(1,K.l);
    newL = newL + K.l;
end

% Convert diagonal SDPs to nonnegative variables
if any(sdiag)
    dsize = K.s(sdiag);
    sdpL = sum(dsize);
    prep.sdiag = dsize;
    jstrt = cumsum([N_flqr+1,K.s(1:end-1).^2]);
    jstrt = jstrt(sdiag);
    istrt = cumsum([1,dsize(1:end-1)]);
    dsize = dsize + 1;
    dblks = cumsum(full(sparse(1,istrt,1,1,sdpL)));
    ii{end+1} = newL + 1 : newL + sdpL;
    jj{end+1} = jstrt(dblks) + dsize(dblks) .* ( ( 1 : sdpL ) - istrt(dblks) );
    vv{end+1} = ones(1,sdpL);
    newL = newL + sdpL;
end

% Stuff free variables into a Lorentz cone
tr_off = newL;
nb_off = newL + L_qr;
if N_f && pars.free
    tr_off = tr_off + 1;
    nb_off = nb_off + 1;
    ii{end+1} = nb_off + 1 : nb_off + N_f;
    jj{end+1} = [ 1 : K.f, K.fcplx ];
    vv{end+1} = [ ones(1,K.f), -1j*ones(1,N_fc) ];
    nb_off = nb_off + K.f;
    newQ = N_f + 1;
end

% Rearrange Lorentz cones to trace block + norm-bound blocks
if N_q
    ndxs      = cumsum([1,K.q(1:end-1)]);
    it        = zeros(1,N_q);
    it(ndxs)  = tr_off + 1 : tr_off + L_q;
    it(it==0) = nb_off + 1 : nb_off + ( N_q - L_q );
    jt = K.f + K.l + 1 : K.f + K.l + N_q;
    vt = ones(1,N_q);
    if ~isempty(K.qcplx)
        jt = jt - cumsum(full(sparse(1,K.qcplx,1,1,N_q)));
        vt(K.qcplx) = -1j;
    end
    ii{end+1} = it(:)';
    jj{end+1} = jt;
    vv{end+1} = vt;
    tr_off    = tr_off + L_q;
    nb_off    = nb_off + N_q - L_q;
end

% Transform rotated Lorentz cones to standard Lorentz cones, and rearrange
% to trace block + norm-bound blocks.
if N_r
    ndxr       = cumsum([1,K.r(1:end-1)]);
    ndxp       = ndxr + 2*(0:L_r-1);
    it         = zeros(1,N_r+2*L_r);
    it(ndxp)   = tr_off + 1 : tr_off + L_r;
    it(ndxp+1) = -1;
    it(ndxp+2) = it(ndxp);
    it(it==0)  = nb_off + 1 : nb_off + ( N_r - L_r );
    it(ndxp+1) = it(ndxp+3);
    jt = [ K.f + K.l + N_q + 1, ones(1,N_r+2*L_r-1) ];
    jt([ndxp+1,ndxp+3]) = 0;
    vt = ones(1,N_r+2*L_r);
    vt([ndxp,ndxp+1,ndxp+2]) = sqrt(0.5);
    vt(ndxp+3) = -sqrt(0.5);
    if ~isempty(K.rcplx)
        jt(K.rcplx) = 0;
        vt(K.rcplx) = -1j;
    end
    ii{end+1} = it;
    jj{end+1} = cumsum(jt);
    vv{end+1} = vt;
    nb_off = nb_off + N_r - L_r;
end

% Replace non-diagonal real SDP coefficients with tril(X) + tril(X',-1).
% This cuts the number of nonzeros approximately in half.
if K.rsdpN
    dsize = K.s(sreal);
    sdpL  = sum(dsize.^2);
    jstrt = cumsum([N_flqr+1,K.s(1:end-1).^2]);
    jstrt = jstrt(sreal);
    istrt = cumsum([1,dsize(1:end-1).^2]);
    dblks = cumsum(full(sparse(1,istrt,1,1,sdpL)));
    istrt = istrt + nb_off;
    dsize = dsize(dblks);
    istrt = istrt(dblks);
    jndxs = ( nb_off + 1 : nb_off + sdpL ) - istrt;
    cols  = floor(jndxs ./ dsize);
    rows  = jndxs - dsize .* cols;
    ii{end+1} = max(rows,cols) + min(rows,cols) .* dsize + istrt;
    jj{end+1} = jndxs + jstrt(dblks);
    vv{end+1} = ones(1,sdpL);
    nb_off = nb_off + sdpL;
    clear dsize jstrt istrt dblks jndxs rows cols
end

% Replace Hermitian SDP coefficients with tril(X) + tril(X',-1). This one's
% a bit trickier because we have the real and complex values interleaved,
% and the imaginary values along the diagonal are zero.
if K.rsdpN < length(K.s)
    dsize = K.s(scplx);
    jsize = dsize .^ 2;
    sdpL  = 2 * sum(jsize);
    jstrt = cumsum([N_flqr+1,K.s(1:end-1).^2]);
    jstrt = jstrt(scplx);
    bstrt = cumsum([1,2*jsize(1:end-1)]);
    dblks = cumsum(full(sparse(1,bstrt,1,1,sdpL)));
    istrt = bstrt + nb_off;
    dsize = dsize(dblks);
    istrt = istrt(dblks);
    bndxs = ( nb_off + 1 : nb_off + sdpL ) - istrt;
    cols  = floor( bndxs ./ dsize );
    rows  = bndxs - dsize .* cols;
    imgv  = cols >= dsize;
    cols  = cols - imgv .* dsize;
    indxs = max(rows,cols) + min(rows,cols) .* dsize + imgv .* jsize(dblks) + istrt;
    vals  = ( 1 - 2 * ( cols > rows ) ) .* ( 1 - ( 1 + 1j ) .* imgv );
    keep  = ~imgv | ( rows ~= cols );
    jndxs = rows + cols .* dsize + jstrt(dblks);
    ii{end+1} = indxs(keep);
    jj{end+1} = jndxs(keep);
    vv{end+1} = vals(keep);
    clear dsize jsize jstrt bstrt istrt bndxs rows cols vals imgv keep
end

% Update free, nonnegative, and Lorentz variable counts
K.f = 0;
K.l = newL;
K.q = [ newQ, K.q, K.r ];
K.r = zeros(0,1);
K.s = [ K.s(:,~scplx&~sdiag), K.s(scplx&~sdiag) ];
K.rsdpN = nnz(~scplx&~sdiag);
K.N = K.l + sum(K.q) + sum(K.s(1:K.rsdpN).^2)+2*sum(K.s(K.rsdpN+1:end).^2);

% Create the artificial (x0,z0) variable for the self-dual model by
% appending a zero row to At and c. This is accomplished in QR by adding
% 1 to all of the row indices created above.
K.N  = K.N + 1;
K.l  = K.l + 1;
K.m  = length(b);

% Transform At, c
% The transformation matrix QR does not satisfy QR'*QR=I. But it does, in
% fact, serve as a reverse transformation:
%    --- For Lorentz cones, QR applies a permutation; QR' reverses it.
%    --- For rotated Lorentz cones, QR applies a unitary, self-adjoint
%        rotation on the first two variables, so QR' reverses it.
%    --- For split free variables, QR creates the positive and negative
%        parts; QR' combines them back together.
%    --- For free variables placed in a Lorentz cone, QR moves them to the
%        Lorentz cone block, adding an extra epigraph variable. QR' moves
%        them back and drops the extra variable.
%    --- For semidefinte cones, QR adds the strict upper triangle to the
%        strict lower triangle; QR' copies the strict lower triangle to the
%        strict upper triangle, ensuring symmetry.
%    --- QR adds a row for the self-dual variable; QR' removes it.
[dummy,ndxs] = sort(cellfun(@(x) x(1),jj)); %#ok
QR = sparse( horzcat(ii{ndxs})+1, horzcat(jj{ndxs}), horzcat(vv{ndxs}), K.N, length(c) );
At = real( sparse( QR * At ) );
c  = real( sparse( QR * c  ) );
b  = sparse( b );
prep.QR = QR;
clear ii jj vv

% -------------------------------------------------------------------------
% Now K has field K.{l,q,s}
% Generate a more detailed description of cone K:
% Let K.blkstart(i):K.blkstart(i+1)-1 give the index range of block i.
% Compute maxN=max(order), len=sum(order) for LORENTZ, real PSD, herm PSD
% yields: K.{l,q,s,rsdpN,blkstart,rLen,hLen,qMaxn,rMaxn,hMaxn}
% -------------------------------------------------------------------------
Ksr = K.s(1:K.rsdpN);
Ksc = K.s(K.rsdpN+1:end);
K.blkstart = cumsum([K.l+1,length(K.q)+length(K.r),K.q-1,Ksr.^2,2*Ksc.^2]);
K.rLen = sum(Ksr);
K.hLen = sum(Ksc);
K.qMaxn = max([0,K.q]);
K.rMaxn = max([0,Ksr]);
K.hMaxn = max([0,Ksc]);
K.mainblks = K.blkstart(cumsum([1 1 length(K.q)]));
K.qblkstart = K.blkstart(2:2+length(K.q));  % Also include blkend
K.sblkstart = K.blkstart(2+length(K.q):end);
K.lq = K.mainblks(end)-1;


function data = loadSUN3Dv2(sequenceName, frameIDs)

if ~exist('sequenceName','var')
    sequenceName = '2014-04-29_14-39-49_094959634447';
end

SUN3Dpath = '/n/fs/sun3d/sun3dv2/';

%{
fileID = fopen(fullfile(SUN3Dpath,sequenceName,'image/time.dat'));
imageTimestamp = fread(fileID,'int64');
fclose(fileID);
data.image = VideoReader(fullfile(SUN3Dpath,sequenceName,'image/image.mp4'));
%}

imageFiles = dirSmart(fullfile(SUN3Dpath,sequenceName,'image/'),'jpg');
imageFrameID = zeros(1,length(imageFiles));
imageTimestamp = zeros(1,length(imageFiles));
for i=1:length(imageFiles)
    id_time = sscanf(imageFiles(i).name, '%d-%ld.jpg');
    imageFrameID(i) = id_time(1);
    imageTimestamp(i) = id_time(2);

    data.imageAll{i}= fullfile(fullfile(SUN3Dpath,sequenceName,'image',imageFiles(i).name));
end

depthFiles = dirSmart(fullfile(SUN3Dpath,sequenceName,'depth/'),'tif');
irFiles = dirSmart(fullfile(SUN3Dpath,sequenceName,'ir/'),'tif');

depthFrameID = zeros(1,length(depthFiles));
depthTimestamp = zeros(1,length(depthFiles));
for i=1:length(depthFiles)
    id_time = sscanf(depthFiles(i).name, '%d-%ld.tif');
    depthFrameID(i) = id_time(1);
    depthTimestamp(i) = id_time(2);

    data.depthAll{i}= fullfile(fullfile(SUN3Dpath,sequenceName,'depth',depthFiles(i).name));
end

irFrameID = zeros(1,length(irFiles));
irTimestamp = zeros(1,length(irFiles));
for i=1:length(irFiles)
    id_time = sscanf(irFiles(i).name, '%d-%ld.tif');
    irFrameID(i) = id_time(1);
    irTimestamp(i) = id_time(2);

    data.irAll{i}= fullfile(fullfile(SUN3Dpath,sequenceName,'ir',irFiles(i).name));
end

data.imageTimestamp = imageTimestamp;
data.depthTimestamp = depthTimestamp;

data.imageTotalFrames = length(data.imageTimestamp);
data.depthTotalFrames = length(data.depthTimestamp);

% synchronize: find a depth for each image
frameCount = length(imageTimestamp);
IDimage2depth = zeros(1,frameCount);
for i=1:frameCount
    [~, IDimage2depth(i)]=min(abs(double(depthTimestamp)-double(imageTimestamp(i))));
end

if ~exist('frameIDs','var') || isempty(frameIDs)
    frameIDs = 1:frameCount;
end

data.sequenceName = sequenceName;

cnt = 0;
for frameID=frameIDs
    cnt = cnt + 1;
    data.depth{cnt} = fullfile(fullfile(SUN3Dpath,sequenceName,'depth',depthFiles(IDimage2depth(frameID)).name));
    data.ir{cnt} = fullfile(fullfile(SUN3Dpath,sequenceName,'ir',irFiles(IDimage2depth(frameID)).name));
end

kinectID = strsplit(sequenceName,'_');
kinectID = kinectID{end};

data.camera = load(fullfile(SUN3Dpath,'intrinsics',[kinectID '.mat']));

end

function files = dirSmart(page, tag)
    [files, status] = urldir(page, tag);
    if status == 0
        files = dir(fullfile(page, ['*.' tag]));
    end
end

function [files, status] = urldir(page, tag)
    if nargin == 1
        tag = '/';
    else
        tag = lower(tag);
        if strcmp(tag, 'dir')
            tag = '/';
        end
        if strcmp(tag, 'img')
            tag = 'jpg';
        end
    end
    nl = length(tag);
    nfiles = 0;
    files = [];

    % Read page
    page = strrep(page, '\', '/');
    [webpage, status] = urlread(page);

    if status
        % Parse page
        j1 = findstr(lower(webpage), '<a href="');
        j2 = findstr(lower(webpage), '');
        Nelements = length(j1);
        if Nelements>0
            for f = 1:Nelements
                % get HREF element
                chain = webpage(j1(f):j2(f));
                jc = findstr(lower(chain), '">');
                chain = deblank(chain(10:jc(1)-1));

                % check if it is the right type
                if length(chain)>length(tag)-1
                    if strcmp(chain(end-nl+1:end), tag)
                        nfiles = nfiles+1;
                        chain = strrep(chain, '%20', ' '); % replace space character
                        files(nfiles).name = chain;
                        files(nfiles).bytes = 1;
                    end
                end
            end
        end
    end
end

function XYZcamera = depth2XYZcamera(K, depth)
    sz = size(depth);
    [x,y] = meshgrid(1:sz(2), 1:sz(1));
    XYZcamera(:,:,1) = (x-K(1,3)).*depth/K(1,1);
    XYZcamera(:,:,2) = (y-K(2,3)).*depth/K(2,2);
    XYZcamera(:,:,3) = depth;
    XYZcamera(:,:,4) = depth~=0;
end


% See also: hsvargplvm_init

function [options, optionsDyn] = hsvargplvmOptions(globalOpt, timeStampsTraining, labelsTrain)

if nargin < 2
    timeStampsTraining = [];
end

if nargin < 3
    labelsTrain = [];
end

%-- One options structure where there are some parts shared for all
% models/layers and some parts specific for a few layers / submodels.

options = vargplvmOptions('dtcvar');

% Taken from globalOpt
options.H = globalOpt.H;
options.baseKern = globalOpt.baseKern;
options.Q = globalOpt.Q;
options.K = globalOpt.K;
options.enableDgtN = globalOpt.DgtN;
options.initial_X = globalOpt.initial_X;
options.initX = globalOpt.initX;
options.multOutput = globalOpt.multOutput;
if options.multOutput > 2
    warning('Multoutput > 2 has wrong derivatives!!')
end
%
options.optimiser = 'scg2';

% !!!!! Be careful to use the same type of scaling and bias for all models!!!
% scale = std(Ytr);
% scale(find(scale==0)) = 1;
%options.scaleVal = mean(std(Ytr));
% options.scaleVal = sqrt(var(Ytr{i}(:))); %%% ??
options.scale2var1 = globalOpt.scale2var1;

options.fixInducing = globalOpt.fixInducing;

%----- Parent prior (we call priors "dynamics", but it can actually be some
% other type of prior, eg labels etc.).
% The relevant fields of globalOpt are coming from the svargplvm_init
% (called within hsvargplvm_init).
if isempty(globalOpt.dynamicsConstrainType) || nargout < 2
    optionsDyn = [];
else
    if ~isempty(labelsTrain)
        optionsDyn.labelsTrain = labelsTrain;
    end
    % This does not mean it needs time inputs, it's just saying that it'll
    % use the kernel types and reparametrization used in VGPDS (regressive
    % "dynamics" etc).
    optionsDyn.type = 'vargpTime';
    optionsDyn.inverseWidth=30;
    optionsDyn.vardistCovars = globalOpt.vardistCovarsMult;
    if iscell(globalOpt.initX)
        optionsDyn.initX = globalOpt.initX{end};
    else
        optionsDyn.initX = globalOpt.initX;
    end
    optionsDyn.constrainType = globalOpt.dynamicsConstrainType;
    if ~isempty(timeStampsTraining)
        optionsDyn.t = timeStampsTraining;
    end
    optionsDyn.kern = globalOpt.dynamicKern;
    optionsDyn.initCovarMedian = globalOpt.dynamicsInitCovarMedian;
    optionsDyn.initCovarMedianLowest = globalOpt.initCovarMedianLowest;
    optionsDyn.initCovarMedianHighest = globalOpt.initCovarMedianHighest;
end


function deep_attention_test()
close all;
clc;
% clear mex;
clear is_valid_handle; % to clear init_key
run(fullfile(fileparts(mfilename('fullpath')), 'startup'));
opts.vocal = true;

%% -------------------- CONFIG --------------------
opts.dataset{1} = 'pascal';
%opts.dataset{2} = 'DUT';
%opts.dataset{3} = 'MIT300';
%opts.dataset{4} = 'MIT1003';
%opts.dataset{5} = 'Toronto';
opts.caffe_version          = 'caffe_faster_rcnn';
opts.gpu_id                 = auto_select_gpu;
active_caffe_mex(opts.gpu_id, opts.caffe_version);
opts.use_gpu                = true;

attention_model_dir              = fullfile(pwd, 'models'); %% VGG
attention_model                  = load_model(attention_model_dir);

attention_net = caffe.Net(attention_model.attention_net_def, 'test');
attention_net.copy_from(attention_model.attention_net);
%attention_net.params('final_attention_pred',1).get_data()
% if opts.use_gpu
%     attention_model.image_means = gpuArray(attention_model.image_means);
% end

% set gpu/cpu
if opts.use_gpu
    caffe.set_mode_gpu();
else
    caffe.set_mode_cpu();
end

for kk = 1:length(opts.dataset)
    opts.attention_dir = fullfile(pwd, 'datasets', 'result', opts.dataset{kk});
    mkdir_if_missing(opts.attention_dir);

    files=dir(['datasets/' opts.dataset{kk} '/*.jpg']);
    files=struct2cell(files)' ;
    image_names=files(:,1);

    for imnum = 1:length(image_names)
        imnum
        image = imread(['datasets/' opts.dataset{kk} '/' image_names{imnum}]);
        [w,h,c] = size(image);
%       if opts.use_gpu
%           im = gpuArray(image);
%       end
        [~, ~, ~, final_attentionmap] = fixationmap_detect(attention_model.conf, attention_net, image);
        final_attentionmap  = imresize(final_attentionmap,[w,h]);
        imwrite(final_attentionmap,[opts.attention_dir '/'  image_names{imnum}(1:end-4) '.jpg']);
    end
end
caffe.reset_all();
clear mex;

end

function model = load_model(model_dir)
    ld             = load(fullfile(model_dir, 'model'));
    model = ld.attention_model;
    clear ld;
    %% load attention model
    model.attention_net_def ...
                                = fullfile(model_dir, model.attention_net_def);
    model.attention_net ...
                                = fullfile(model_dir, model.attention_net);
end


function imo = cnn_get_im_flow_batch2(images, varargin)

opts.subTractFlow = 'off';
opts.nFramesPerVid = 1;
opts.numAugments = 1;
opts.frameSample = 'uniformly';
opts.flowDir = '';
opts.imageDir = '';
opts.temporalStride = 0;

opts.imageSize = [227, 227] ;
opts.border = [29, 29] ;
opts.averageImage = [] ;
opts.rgbVariance = [] ;
opts.augmentation = 'croponly' ;
opts.interpolation = 'bilinear' ;
opts.numAugments = 1 ;
opts.numThreads = 0 ;
opts.prefetch = false ;
opts.keepAspect = true;
opts.flowScales = [];
opts.cheapResize = 0;
opts.nFrameStack = 10;
opts.frameList = NaN;
opts.nFrames = [];
opts.subMedian = false;
opts.stretchAspect = 4/3 ;
opts.stretchScale = 1.2 ;
opts.fetchGPU = true ;
[opts, varargin] = vl_argparse(opts, varargin);

flowDir = opts.flowDir;
imgDir = opts.imageDir;
% prefetch is used to load images in a separate thread
prefetch = opts.prefetch & isempty(opts.frameList);
fetchOpts= {'numThreads', opts.numThreads};
if opts.fetchGPU
  fetchOpts{end+1} = 'Gpu' ;
end

switch opts.augmentation
  case 'croponly'
    tfs = [.5 ; .5 ; 0 ];
  case 'f5'
    tfs = [...
      .5 0 0 1 1 .5 0 0 1 1 ;
      .5 0 1 0 1 .5 0 1 0 1 ;
       0 0 0 0 0  1 1 1 1 1] ;
  case 'f25'
    [tx,ty] = meshgrid(linspace(0,1,5)) ;
    tfs = [tx(:)' ; ty(:)' ; zeros(1,numel(tx))] ;
    tfs_ = tfs ;
    tfs_(3,:) = 1 ;
    tfs = [tfs,tfs_] ;
  case 'f25noCtr'
    [tx1,ty1] = meshgrid(linspace(.75,1,20)) ;
    [tx2,ty2] = meshgrid(linspace(0,.25,20)) ;
    tx = [tx1 tx2];     ty = [ty1 ty2];
    tfs = [tx(:)' ; ty(:)' ; zeros(1,numel(tx))] ;
    tfs_ = tfs ;
    tfs_(3,:) = 1 ;
    tfs = [tfs,tfs_] ;
end

nStack = opts.imageSize(3);

if iscell(opts.frameList)
  im = vl_imreadjpeg(opts.frameList{1}, fetchOpts{:}) ;
  sampled_frame_nr = opts.frameList{2};
else
  sampleFrameLeftRight = floor(nStack/4); % divide by 4 because of left,right,u,v
  frameOffsets = [-sampleFrameLeftRight:sampleFrameLeftRight-1]';

  frames = cell(numel(images), nStack, opts.nFramesPerVid);
  frames_rgb = cell(numel(images), 1, opts.nFramesPerVid);

  sampled_frame_nr = cell(numel(images),1);

  for i=1:numel(images)
    vid_name = images{i};
    nFrames = opts.nFrames(i);

    if  strcmp(opts.frameSample, 'uniformly')
      sampleRate = max(floor((nFrames-nStack/2)/opts.nFramesPerVid),1);
      frameSamples = nStack/4+1:sampleRate:nFrames - nStack/4 ;
      opts.temporalStride = sampleRate;
      frameSamples = vl_colsubset(nStack/4+1:nFrames-nStack/4, opts.nFramesPerVid, 'uniform') ;
      opts.temporalStride =  frameSamples(2) - frameSamples(1);
    elseif strcmp(opts.frameSample, 'temporalStride')
      frameSamples = nStack/4+1:opts.temporalStride:nFrames-nStack/4 ;
      if length(frameSamples) < opts.nFrameStack,
          frameSamples = round(linspace(nStack/4+1, nFrames - nStack/4, opts.nFramesPerVid)) ;
          opts.temporalStride = frameSamples(2) - frameSamples(1);
      end
    elseif strcmp(opts.frameSample, 'random')
      frameSamples = randperm(nFrames-nStack/2)+nStack/4;
    elseif strcmp(opts.frameSample, 'temporalStrideRandom')
      frameSamples = nStack/4 +1:opts.temporalStride:nFrames - nStack/4 ;
      if length(frameSamples) < opts.nFrameStack,
          frameSamples = round(linspace(nStack/4+1, nFrames - nStack/4, opts.nFrameStack)) ;
          opts.temporalStride = frameSamples(2) - frameSamples(1);
      end
    end

    if length(frameSamples) < opts.nFramesPerVid,
        if length(frameSamples) > opts.nFrameStack
          frameSamples = frameSamples(1:length(frameSamples)-mod(length(frameSamples),opts.nFrameStack));
        end
        diff =  opts.nFramesPerVid - length(frameSamples);
        addFrames = 0;
        while diff > 0
          last_frame = min(frameSamples(end), max(nFrames - nStack/4 - opts.nFrameStack,nStack/4 ));
          if mod(addFrames,2) % add to the front
            addSamples = nStack/4+1:opts.temporalStride:nFrames - nStack/4;
            addSamples = addSamples(1: length(addSamples) - mod(length(addSamples),opts.nFrameStack));
            if length(addSamples) > diff, addSamples = addSamples(1:diff); end
          else % add to the back
            addSamples = fliplr(nFrames - nStack/4 : -opts.temporalStride: nStack/4+1);
            addSamples = addSamples(mod(length(addSamples),opts.nFrameStack)+1:length(addSamples));
            if length(addSamples) > diff, addSamples = addSamples(end-diff+1:end); end
          end

          if addFrames > 20
            addSamples = round(linspace(nStack/4+1, nFrames - nStack/4, opts.nFrameStack)) ;
          end
          frameSamples = [frameSamples addSamples];
          diff = opts.nFramesPerVid - length(frameSamples);
          opts.temporalStride = max(ceil(opts.temporalStride-1), 1);
          addFrames = addFrames+1;

        end
    end
    if length(frameSamples) > opts.nFramesPerVid
      if strcmp(opts.frameSample, 'temporalStride')
        s = fix((length(frameSamples)-opts.nFramesPerVid)/2);
      else % random
        s = randi(length(frameSamples)-opts.nFramesPerVid);
      end
        frameSamples = frameSamples(s+1:s+opts.nFramesPerVid);
    end

    for k = 1:opts.nFramesPerVid
        frames_rgb{i,1,k} = [vid_name 'frame' sprintf('%06d.jpg', frameSamples(k))] ;
    end

      frameSamples =  repmat(frameSamples,nStack/2,1) +  repmat(frameOffsets,1,size(frameSamples,2));
      for k = 1:opts.nFramesPerVid
        for j = 1:nStack/2
            frames{i,(j-1)*2+1, k} = ['u' filesep vid_name 'frame' sprintf('%06d.jpg', frameSamples(j,k)) ] ;
            frames{i,(j-1)*2+2, k} = ['v' frames{i,(j-1)*2+1, k}(2:end)];
        end
      end

      sampled_frame_nr{i} = frameSamples;
  end

    if iscell(opts.imageDir)
          imgDir = opts.imageDir{i};
          flowDir = opts.flowDir{i};
    end

  frames_rgb = strcat([imgDir filesep], frames_rgb);
  if ~isempty(flowDir)
    frames = strcat([flowDir filesep], frames);
    frames = cat(2, frames, frames_rgb);
  else
    frames = frames_rgb;
  end
  if opts.numThreads > 0
    if prefetch
      vl_imreadjpeg(frames, fetchOpts{:}, 'prefetch') ;
      imo = {frames sampled_frame_nr}  ;
      return ;
    end
    im = vl_imreadjpeg(frames, fetchOpts{:} ) ;
  end

end

if strcmp(opts.augmentation, 'none')

  szw = cellfun(@(x) size(x,2),im);
  szh = cellfun(@(x) size(x,1),im);

  h_min = min(szh(:));
  w_min =  min(szw(:));
  sz = [h_min w_min] ;

  sz = max(opts.imageSize(1:2), sz);
  sz = min(2*opts.imageSize(1:2), sz);

  scal = ([h_min w_min] ./ sz);

  imo =  zeros(sz(1), sz(2), opts.imageSize(3)+3, ...
            numel(images), 2 * opts.nFramesPerVid, 'single') ;
  if opts.fetchGPU
    imo = gpuArray(imo);
  end

  for i=1:numel(images)
    si = 1 ;
    for k = 1:opts.nFramesPerVid

      if numel(unique(szw)) > 1 || numel(unique(szh)) > 1
        for l=1:size(im,2)
            im{i,l,k} = im{i,l,k}(1:h_min,1:w_min,:);
        end
      end
        imt = cat(3, im{i,:,k}) ;

        if any(scal ~= 1)
          imo(:, :, :, i, si) = imresize(cat(3, im{i,:,k}),sz) ;
        else
          imo(:, :, :, i, si) = imt ;
        end
        imt = [];
        imo(:, :, :, i, si+1) =  imo(:, end:-1:1, :, i, si);
        imo(:, :, 1:2:nStack, i, si+1) = -imo(:, :, 1:2:nStack, i, si+1) + 255; %invert u if we flip

        si = si + 2 ;

    end
  end

  if opts.subMedian
    median_flow = median(imo(:,:,1:nStack),1);
    median_flow = median(median_flow,2);
    imo(:,:,1:nStack,:,:) = bsxfun(@minus, imo(:,:,1:nStack,:,:), median_flow ) ;
    imo(:,:,1:nStack,:,:) = bsxfun(@plus, imo(:,:,1:nStack,:,:), 128 ) ;
  end

  if ~isempty(opts.averageImage)
    opts.averageImage = mean(mean(opts.averageImage,1),2) ;
    imo = bsxfun(@minus, imo,opts.averageImage) ;
  end
  return;
end

% augment now
if exist('tfs', 'var')
  [~,transformations] = sort(rand(size(tfs,2), numel(images)*opts.nFramesPerVid), 1) ;
end

imo = ( zeros(opts.imageSize(1), opts.imageSize(2), opts.imageSize(3)+3, ...
            numel(images), opts.numAugments * opts.nFramesPerVid, 'single') ) ;

if opts.fetchGPU
  imo = gpuArray(imo);
end

for i=1:numel(images)
  si = 1 ;

  szw = cellfun(@(x) size(x,2),im);
  szh = cellfun(@(x) size(x,1),im);

  h_min = min(szh(:));
  w_min =  min(szw(:));

  if  strcmp( opts.augmentation, 'multiScaleRegular')
    reg_szs = [256, 224, 192, 168] ;
    sz(1) = reg_szs(randi(4)); sz(2) = reg_szs(randi(4));
  elseif strcmp( opts.augmentation, 'stretch')
    aspect = exp((2*rand-1) * log(opts.stretchAspect)) ;
    scale = exp((2*rand-1) * log(opts.stretchScale)) ;
    tw = opts.imageSize(2) * sqrt(aspect) * scale ;
    th = opts.imageSize(1) / sqrt(aspect) * scale ;
    reduce = min([w_min / tw, h_min / th, 1]) ;
    sz = round(reduce * [th ; tw]) ;
  else
    sz = round(min(opts.imageSize(1:2)' .* (.75+0.5*rand(2,1)), [h_min; w_min])) ; % 0.75 +- 0.5, not keep aspect
  end

  for k = 1:opts.nFramesPerVid

      if numel(unique(szw)) > 1 || numel(unique(szh)) > 1
        for l=1:size(im,2)
          im{i,l,k} = im{i,l,k}(1:h_min,1:w_min,:);
        end
      end

      imt = cat(3, im{i,:,k}) ;
      if opts.subMedian
          median_flow = median(imt(:,:,1:nStack),1);
          median_flow = median(median_flow,2);
          imt(:,:,1:nStack) = bsxfun(@minus, imt(:,:,1:nStack), median_flow ) ;
          imt(:,:,1:nStack) = bsxfun(@plus, imt(:,:,1:nStack), 128 ) ;
      end
%       imt = gpuArray(imt);

    w = size(imt,2) ;
    h = size(imt,1) ;
    if ~strcmp(opts.augmentation, 'uniform')
      if ~isempty(opts.rgbVariance) % colour jittering only in training case
        offset = zeros(size(imt));
        offset = bsxfun(@minus, offset, reshape(opts.rgbVariance * randn(opts.imageSize(3),1), 1,1,opts.imageSize(3))) ;
        imt = bsxfun(@minus, imt, offset) ;
      end

      for ai = 1:opts.numAugments
        switch opts.augmentation
          case 'stretch'
            dx = randi(w - sz(2) + 1 ) ;
            dy = randi(h - sz(1) + 1 ) ;
            flip = rand > 0.5 ;
          case 'multiScaleRegular'
            dy = [0 h-sz(1) 0 h-sz(1)  floor((h-sz(1)+1)/2)] + 1; % 4 corners & centre
            dx = [0 w-sz(2) w-sz(2) 0 floor((w-sz(2)+1)/2)] + 1;
            corner = randi(5);
            dx = dx(corner); dy = dy(corner); % pick one corner of the image
            flip = rand > 0.5 ;
          case 'f25noCtr'
            tf = tfs(:, transformations(mod(i+ai-1, numel(transformations)) + 1)) ;
            dx = floor((w - sz(2)) * tf(2)) + 1 ;
            dy = floor((h - sz(1)) * tf(1)) + 1 ;
            flip = tf(3) ;
          otherwise
            sz = opts.imageSize(1:2) ;
            tf = tfs(:, transformations(mod(ai-1, numel(transformations)) + 1)) ;
            dx = floor((w - sz(2)) * tf(2)) + 1 ;
            dy = floor((h - sz(1)) * tf(1)) + 1 ;
            flip = tf(3) ;
        end

        if opts.cheapResize
          sx = round(linspace(dx, sz(2)+dx-1, opts.imageSize(2))) ;
          sy = round(linspace(dy, sz(1)+dy-1, opts.imageSize(1))) ;
        else
          factor = [opts.imageSize(1)/sz(1) ...
              opts.imageSize(2)/sz(2)];

          if any(abs(factor - 1) > 0.0001)
            imt =   imresize(imt(dy:sz(1)+dy-1,dx:sz(2)+dx-1,:), [opts.imageSize(1:2)]);
          end

          sx = 1:opts.imageSize(2); sy = 1:opts.imageSize(1);
        end

        if flip
          sx = fliplr(sx) ;
          imo(:,:,:,i,si) = imt(sy,sx,:) ;
          imo(:,:,1:2:nStack,i,si) = -imt(sy,sx,1:2:nStack) + 255; %invert u if we flip
        else
          imo(:,:,:,i,si) = imt(sy,sx,:) ;
        end

        si = si + 1 ;
      end
    else

      w = size(imt,2) ; h = size(imt,1) ;

      indices_y = [0 h-opts.imageSize(1)] + 1;
      indices_x = [0 w-opts.imageSize(2)] + 1;
      center_y = floor(indices_y(2) / 2)+1;
      center_x = floor(indices_x(2) / 2)+1;

      if opts.numAugments == 6,  indices_y = center_y;
      elseif opts.numAugments == 2,  indices_x = [];   indices_y = [];
      elseif opts.numAugments ~= 10, error('only 6 or 10 uniform crops allowed');  end
        for y = indices_y
        for x = indices_x
          imo(:, :, :, i, si) = ...
              imt(y:y+opts.imageSize(1)-1, x:x+opts.imageSize(2)-1, :);

          imo(:, :, :, i, si+1) = imo(:, end:-1:1, :, i, si);
          imo(:, :, 1:2:nStack, i, si+1) = -imo(:, end:-1:1, 1:2:nStack, i, si) + 255; %invert u if we flip

          si = si + 2 ;
        end
        end
        imo(:,:,:, i,si) = imt(center_y:center_y+opts.imageSize(1)-1,center_x:center_x+opts.imageSize(2)-1,:);

        imo(:,:,:, i,si+1) = imo(:, end:-1:1, :, i, si);
        imo(:,:,1:2:nStack, i,si+1) = -imo(:, end:-1:1, 1:2:nStack, i, si) + 255; %invert u if we flip

        si = si + 2;
    end
  end
end

if ~isempty(opts.averageImage)
  imo = bsxfun(@minus, imo, opts.averageImage) ;
end

end


function planC = contourSUV(structNum,percent,planC)
%function contourSUV(structNum,percent,planC)
%
%This function creates structure at percent% SUV level for structNum
%
%APA,12/15/2006
%
% Copyright 2010, Joseph O. Deasy, on behalf of the CERR development team.
%
% This file is part of The Computational Environment for Radiotherapy Research (CERR).
%
% CERR development has been led by:  Aditya Apte, Divya Khullar, James Alaly, and Joseph O. Deasy.
%
% CERR has been financially supported by the US National Institutes of Health under multiple grants.
%
% CERR is distributed under the terms of the Lesser GNU Public License.
%
%     This version of CERR is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or
%     (at your option) any later version.
%
% CERR is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
% without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
% See the GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with CERR.  If not, see <http://www.gnu.org/licenses/>.

global stateS
if ~exist('planC','var')
    global planC
end
indexS = planC{end};

scanNum                             = getStructureAssociatedScan(structNum,planC);
[rasterSegments, planC, isError]    = getRasterSegments(structNum,planC);
if isempty(rasterSegments)
    warning('Could not create conotour.')
    return
end
[mask3M, uniqueSlices]              = rasterToMask(rasterSegments, scanNum, planC);
scanArray3M                         = double(getScanArray(planC{indexS.scan}(scanNum)));
%%%% reverse data
%maxscan=max(scanArray3M(:));
%scanArray3M =maxscan-scanArray3M;
SUVvals3M                           = mask3M.*scanArray3M(:,:,uniqueSlices);
maxSUVinStruct                      = max(SUVvals3M(:));
cutoff                              = percent/100*maxSUVinStruct;
[xVals, yVals, zVals]               = getScanXYZVals(planC{indexS.scan}(scanNum));
newStructNum                        = length(planC{indexS.structures}) + 1;

newStructS = newCERRStructure(scanNum, planC);
for slcNum = 1:length(uniqueSlices)
    C = contourc(xVals, yVals, SUVvals3M(:,:,slcNum),[cutoff cutoff]);
    indC = getSegIndices(C);
    if ~isempty(indC)
        for seg = 1:length(indC)
            points = [C(:,indC{seg})' zVals(uniqueSlices(slcNum))*ones(length(C(1,indC{seg})),1)];
            newStructS.contour(uniqueSlices(slcNum)).segments(seg).points = points;
        end
    else
        newStructS.contour(uniqueSlices(slcNum)).segments.points = [];
    end
end

for l = max(uniqueSlices)+1 : length(planC{indexS.scan}(scanNum).scanInfo)
    newStructS.contour(l).segments.points = [];
end

newStructS.structureName    = [planC{indexS.structures}(structNum).structureName 'SUVMax',num2str(percent)];

planC{indexS.structures} = dissimilarInsert(planC{indexS.structures}, newStructS, newStructNum);
planC = getRasterSegs(planC, newStructNum);
planC = updateStructureMatrices(planC, newStructNum, uniqueSlices);

if ~isempty(stateS) && isfield(stateS,'handle') && isfield(stateS.handle,'CERRSliceViewer') && ishandle(stateS.handle.CERRSliceViewer)
    stateS.structsChanged = 1;
    % Refresh View
    CERRRefresh
end

return;

function indC = getSegIndices(C)
% function getSegIndices(C)
%
%This function returns the indices for each segment of input contour C.
%C is output from in-built "contourc" function
%
%APA, 12/15/2006

start = 1;
counter = 1;
indC = [];
while start < length(C(2,:))
    numPts = C(2,start);
    indC{counter} = [(start+1):(start+numPts) start+1];
    start = start + numPts + 1;
    counter = counter + 1;
end


function [DVH,volume_VOI_first,volume_VOI_check] = dicomrt_dvhcal(VOI,study,dose_xmesh,dose_ymesh,dose_zmesh,dvhselect)
% dicomrt_dvhcal(VOI,study,dose_xmesh,dose_ymesh,dose_zmesh,dvhselect)
%
% Calculate DOSE-VOLUME-HISTOGRAMS for VOIs and cell_case_study.
% This function calculate DVHs for all the available VOIs or for a selected number of them.
%
% study is the RTPLAN or the MC generated dataset containing the dose matrix
% ct is the CT dataset
% dose_xmesh,dose_ymesh,dose_zmesh are x-y-z coordinates of the center of the dose-voxels
% VOIs is a cell array and contains all the Volumes of Interest
% dvhselect is an OPTIONAL vector which contains the number of VOI to calculate DVHs for.
%
% NOTE: if dvhselect is omitted DVHs are calculated for all the VOIs
%
% DVHs are stored in a cell array with the following structure:
%
%  -----------------------------
%  | [DVH 1] | [3D dose mask]  |
%  |         -------------------
%  |         | [dvh data]      |
%  |         -------------------
%  |         | VOI volume      |
%  |         -------------------
%  |         | Voxel volume    |
%  -----------------------------
%  |   ...   |     ...         |
%  -----------------------------
%  | [DVH n] | [3D dose mask]  |
%  |         -------------------
%  |         | [dvh data]      |
%  |         -------------------
%  |         | VOI volume      |
%  |         -------------------
%  |         | Voxel volume    |
%  -----------------------------
%
% [dvh data] is a 2 columns vector with following structure:
%
% -----------------
% | dose | volume |
% | (Gy) |  (cc)  |
% -----------------
% |      |        |
% |      |        |
% |      |        |
% |      |        |
% -----------------
%
%
% Example:
%
% dvh=dicomrt_dvhcal(VOI,dose,dose_xmesh,dose_ymesh,dose_zmesh) returns
% the dvhs for the DVH for all the VOIs in VOI and the dose distribution in the
% 3D matrix 'dose'.
%
% See also dicomrt_loaddose, roifilt2, roipoly, dicomrt_dvhplot
%
% Copyright (C) 2002 Emiliano Spezi (emiliano.spezi@physics.org)

% Check case and set-up some parameters and variables
[dose_temp,type_dose,doselabel,PatientPosition]=dicomrt_checkinput(study);
dose=dicomrt_varfilter(dose_temp);
[VOI_temp]=dicomrt_checkinput(VOI);
VOI=dicomrt_varfilter(VOI_temp);

% Define cell array
DVH=cell(size(VOI,1),2);

% Write VOIs labels
for k=1:size(VOI,1)
    DVH{k,1}=VOI{k,1};
end

% Retrieve dmax
dmax=max(max(max(dose)));

% Build dose grid
dosegrid=zeros(double(int32(dmax)),1);
%for i=1:double(int32(dmax))                    % use with hist
%    x(i)=dmax/double(int32(dmax))*(0.5+i-1);   % hist uses centers
%end                                            %
for i=1:double(int32(dmax))                     % use with histc
    dosegrid(i)=i*dmax/double(int32(dmax));     % histc uses edges
end                                             %

% Calculate voxel volume
[voxelvolume, status]=dicomrt_voxelvolumecal(study,dose_xmesh,dose_ymesh,dose_zmesh);

volume_VOI_check=[];
volume_VOI_first=[];

for k=1:length(dvhselect) % loop over VOIs
    % Print header
    disp(['DVH calculation for VOI: ',VOI{dvhselect(k),1}]);
    % Retrieve info necessary to get volume_VOI
    [mask_VOI,volume_VOI,mask4VOI]=dicomrt_mask(VOI_temp,dose_temp,dose_xmesh,dose_ymesh,dose_zmesh,...
        dvhselect(k),'nan','y'); % dose matrix masked in dicomrt_Vlevel
    % Build frequency DVH data
    % case 1: uniform slice thickness
    volume_VOI_first=[volume_VOI_first,volume_VOI];
    if status==0
        vgrid=histc(reshape(mask_VOI{2,1},1,size(mask_VOI{2,1},1)*size(mask_VOI{2,1},2)*size(mask_VOI{2,1},3)),dosegrid);
        % get back voxels with dose<min(dosegrid) cause histc does not
        % account for it
        temp_min=find(mask_VOI{2,1}<dosegrid(1) & mask_VOI{2,1}~=0);
        %temp_min2=find(mask_VOI{2,1}<dosegrid(1));
        if isempty(temp_min)~=1
            vgrid(1)=vgrid(1)+length(temp_min);
        end
        vgrid=vgrid.*voxelvolume(1);
        temp2=cumsum(vgrid);
        volume_VOI_check=[volume_VOI_check,temp2(end)];
    else % case 2: non-uniform slice thickness
        vgrid=zeros(length(dosegrid),1);
        for i=1:size(mask_VOI{2,1},3)
            temp_vgrid=histc(reshape(mask_VOI{2,1}(:,:,i),1,size(mask_VOI{2,1}(:,:,i),1)*size(mask_VOI{2,1}(:,:,i),2)),dosegrid);
            % get back voxels with dose<min(dosegrid) cause histc does not
            % account for it
            temp_min=find(mask_VOI{2,1}(:,:,i)<dosegrid(1) & mask_VOI{2,1}~=0);
            if isempty(temp_min)~=1
                temp_vgrid(1)=temp_vgrid(1)+length(temp_min);
            end
            vgrid=vgrid+temp_vgrid'.*voxelvolume(i);
        end
        temp2=cumsum(vgrid);
        volume_VOI_check=[volume_VOI_check,temp2(end)];
        %for i=1:length(dosegrid)
        %    if i==1
        %        vgrid(i)=dicomrt_Vlevel(mask_VOI,0,dose_xmesh,dose_ymesh,dose_zmesh,...
        %            [[0 dosegrid(i)] 0 0 0],VOI_temp,dvhselect(k),1,volume_VOI);
        %    else
        %        vgrid(i)=dicomrt_Vlevel(mask_VOI,0,dose_xmesh,dose_ymesh,dose_zmesh,...
        %            [[dosegrid(i-1) dosegrid(i)] 0 0 0],VOI_temp,dvhselect(k),1,volume_VOI);
        %    end
        %end
    end
    vgrid=dicomrt_makevertical(vgrid);
    % Store DVH data
    DVH{dvhselect(k),2}{1,1}=mask_VOI;
    DVH{dvhselect(k),2}{2,1}=[dosegrid,vgrid];
    DVH{dvhselect(k),2}{3,1}=volume_VOI;
end


function [data] = ft_resampledata(cfg, data)

% FT_RESAMPLEDATA performs a resampling or downsampling of the data
%
% Use as
%   [data] = ft_resampledata(cfg, data)
%
% The data should be organised in a structure as obtained from the FT_PREPROCESSING
% function. The configuration should contain
%   cfg.resamplefs      = frequency at which the data will be resampled
%   cfg.detrend         = 'no' or 'yes', detrend the data prior to resampling (no default specified, see below)
%   cfg.demean          = 'no' or 'yes', whether to apply baseline correction (default = 'no')
%   cfg.baselinewindow  = [begin end] in seconds, the default is the complete trial (default = 'all')
%   cfg.feedback        = 'no', 'text', 'textbar', 'gui' (default = 'text')
%   cfg.trials          = 'all' or a selection given as a 1xN vector (default = 'all')
%   cfg.sampleindex     = 'no' or 'yes', add a channel with the original sample indices (default = 'no')
%
% Instead of specifying cfg.resamplefs, you can also specify a time axis on which you
% want the data to be resampled. This is useful for merging data from two acquisition
% devices, after resampledata you can call FT_APPENDDATA to concatenate the channels
% from the different acquisition devices.
%   cfg.time        = cell-array with one time axis per trial (i.e. from another dataset)
%   cfg.method      = interpolation method, see INTERP1 (default = 'pchip')
%   cfg.extrapval   = extrapolation behaviour, scalar value or 'extrap' (default = as in INTERP1)
%
% When you specify cfg.method='resample' an implicit anti-aliasing low pass filter is applied prior
% to the resampling. You can also explicitly specify an anti-aliasing low pass filter. This is adviced
% when downsampling using any other method than 'resample', but also when strong noise components are
% present just above the new Nyquist frequency.
%   cfg.lpfilter    = 'yes' or 'no' (default = 'no')
%   cfg.lpfreq      = scalar value for low pass frequency (there is no default, so needs to be always specified)
%   cfg.lpfilttype  = string, filter type (default is set in ft_preproc_lowpassfilter)
%   cfg.lpfiltord   = scalar, filter order (default is set in ft_preproc_lowpassfilter)
%
% More documentation about anti-alias filtering can be found in this FAQ on the FieldTrip website.
%
% Previously this function used to detrend the data by default to avoid edge artifacts fue to the
% anti-aliassing filter. Detrending is fine for removing slow drifts in data prior to frequency analysis,
% but not recommended if you want to look at ERPs or ERFs. Therefore the old default value 'yes' has been
% removed; you now explicitly have to specify whether you want to detrend or not.
%
% To facilitate data-handling and distributed computing you can use
%   cfg.inputfile   =  ...
%   cfg.outputfile  =  ...
% If you specify one of these (or both) the input data will be read from a *.mat
% file on disk and/or the output data will be written to a *.mat file. These mat
% files should contain only a single variable, corresponding with the
% input/output structure.
%
% See also FT_PREPROCESSING, FT_APPENDDATA, FT_PREPROC_LOWPASSFILTER, ESAMPLE, DOWNSAMPLE, INTERP1

% Copyright (C) 2003-2006, FC Donders Centre, Markus Siegel
% Copyright (C) 2004-2022, FC Donders Centre, Robert Oostenveld
% Copyright (C) 2022, DCCN, Jan-Mathijs Schoffelen
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    FieldTrip is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

% these are used by the ft_preamble/ft_postamble function and scripts
ft_revision = '$Id$';
ft_nargin   = nargin;
ft_nargout  = nargout;

% do the general setup of the function
ft_defaults
ft_preamble init
ft_preamble debug
ft_preamble loadvar data
ft_preamble provenance data

% the ft_abort variable is set to true or false in ft_preamble_init
if ft_abort
  return
end

% ft_checkdata is done further down

% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'forbidden', {'trial'}); % prevent accidental typos, see issue 1729
cfg = ft_checkconfig(cfg, 'renamed', {'blc', 'demean'});
cfg = ft_checkconfig(cfg, 'renamed', {'resamplemethod', 'method'});
cfg = ft_checkconfig(cfg, 'renamed', {'fsample', 'resamplefs'});

% set the defaults
cfg.resamplefs       = ft_getopt(cfg, 'resamplefs',      []);
cfg.time             = ft_getopt(cfg, 'time',            {});
cfg.factor           = ft_getopt(cfg, 'factor',          {});
cfg.detrend          = ft_getopt(cfg, 'detrend',         'no');
cfg.demean           = ft_getopt(cfg, 'demean',          'no');
cfg.baselinewindow   = ft_getopt(cfg, 'baselinewindow',  'all');
cfg.feedback         = ft_getopt(cfg, 'feedback',        'text');
cfg.trials           = ft_getopt(cfg, 'trials',          'all', 1);
cfg.method           = ft_getopt(cfg, 'method',          []);
cfg.sampleindex      = ft_getopt(cfg, 'sampleindex',     'no');
cfg.extrapval        = ft_getopt(cfg, 'extrapval',       []);
cfg.lpfilter         = ft_getopt(cfg, 'lpfilter');

% store original datatype
convert = ft_datatype(data);

% check if the input data is valid for this function, this will convert it to raw if needed
data = ft_checkdata(data, 'datatype', {'raw+comp', 'raw'}, 'feedback', 'yes');

if isempty(cfg.method) && ~isempty(cfg.time)
  % see INTERP1, shape-preserving piecewise cubic interpolation
  cfg.method = 'pchip';
elseif isempty(cfg.method)
  % see RESAMPLE
  cfg.method = 'resample';
end

usefsample = any(strcmp(cfg.method, {'resample', 'downsample', 'decimate', 'mean', 'median'}));
usetime    = ~usefsample;

% select trials of interest
tmpcfg = keepfields(cfg, {'trials', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'});
data   = ft_selectdata(tmpcfg, data);
% restore the provenance information
[cfg, data] = rollback_provenance(cfg, data);

if strcmp(cfg.sampleindex, 'yes') && isfield(data, 'sampleinfo')
  data.label{end+1} = 'sampleindex';
  for i=1:size(data.sampleinfo,1)
    % this works for one or more trials
    data.trial{i}(end+1,:) = data.sampleinfo(i,1):data.sampleinfo(i,2);
  end
elseif strcmp(cfg.sampleindex, 'yes')
  ft_warning('no sampleinfo present, cannot add sampleindex as channel');
end

% sampleinfo, if present, becomes invalid because of the resampling
if isfield(data, 'sampleinfo')
  data = rmfield(data, 'sampleinfo');
end

if usefsample && usetime
  ft_error('you should either specify cfg.resamplefs or cfg.time')
end

% remember the original sampling frequency in the configuration
cfg.origfs = double(data.fsample);

% set this to nan, it will be updated later on
data.fsample = nan;

if isempty(cfg.lpfilter), cfg.lpfilter = 'no'; end
dolpfilt = istrue(cfg.lpfilter);
if dolpfilt
  cfg.lpfilttype = ft_getopt(cfg, 'lpfilttype');
  cfg.lpfiltord  = ft_getopt(cfg, 'lpfiltord');
  cfg            = ft_checkconfig(cfg, 'required', 'lpfreq');
end

if usefsample
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % resample/downsample based on new sampling frequency
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

  ntr = length(data.trial);
  nchan  = numel(data.label);

  ft_progress('init', cfg.feedback, 'resampling data');
  [fsorig, fsres] = rat(cfg.origfs./cfg.resamplefs); %account for non-integer fs
  cfg.resamplefs  = cfg.origfs.*(fsres./fsorig); %get new fs exact

  % make sure that the resampled time axes are aligned (this is to avoid rounding
  % errors in the time axes). this procedure relies on the fact that resample assumes
  % all data outside the data window to be zero anyway. therefore, padding with zeros
  % (to the left and right) before resampling does not hurt
  begsample = zeros(ntr, 1);
  endsample = zeros(ntr, 1);
  for itr = 1:ntr
    begsample(itr) = round(cfg.origfs * data.time{itr}(1));
    endsample(itr) = round(cfg.origfs * data.time{itr}(end));
  end
  begpad = begsample-min(begsample);
  endpad = max(endsample)-endsample;

  if any(begpad~=0) || any(endpad~=0)
    ft_warning('not all trials have the same time axis; data will be zero-padded prior to resampling to avoid rounding issues in the resampled time axes');
  end

  if any(strcmp(cfg.method, {'downsample', 'mean', 'median'}))
    ft_warning('using cfg.method = ''%s''; only use this if you have applied an anti-aliasing filter prior to downsampling!', cfg.method);
  end

  if any(strcmp(cfg.method, {'decimate', 'downsample', 'mean', 'median'}))
    if mod(fsorig, fsres) ~= 0
      ft_error('the new sampling rate needs to be an integer division of the original sampling rate');
    end
  end

  for itr = 1:ntr
    ft_progress(itr/ntr, 'resampling data in trial %d from %d\n', itr, ntr);

    olddat = data.trial{itr};
    oldtim = data.time{itr};

    % detrending is in general not recommended
    if istrue(cfg.detrend)
      if ~strcmp(cfg.baselinewindow, 'all')
        olddat = ft_preproc_detrend(olddat, nearest(oldtim, cfg.baselinewindow(1)), nearest(oldtim, cfg.baselinewindow(2)));
      else
        olddat = ft_preproc_detrend(olddat);
      end
    end

    % remove the mean to avoid edge effects when there's a strong offset, the cfg.demean option is dealt with below
    if ~strcmp(cfg.baselinewindow, 'all')
      [olddat, bsl] = ft_preproc_baselinecorrect(olddat, nearest(oldtim, cfg.baselinewindow(1)), nearest(oldtim, cfg.baselinewindow(2)));
    else
      [olddat, bsl] = ft_preproc_baselinecorrect(olddat);
    end

    if istrue(cfg.lpfilter)
      olddat = ft_preproc_lowpassfilter(olddat, cfg.origfs, cfg.lpfreq, cfg.lpfiltord, cfg.lpfilttype);
    end

    % pad the data with zeros on both sides
    olddat = [zeros(nchan, begpad(itr)) olddat zeros(nchan, endpad(itr))];
    oldtim = ((begsample(itr)-begpad(itr)):(endsample(itr)+endpad(itr))) / cfg.origfs;

    % perform the resampling
    if strcmp(cfg.method, 'downsample')
      if isa(olddat, 'single')
        % temporary convert this trial to double precision
        newdat = transpose(single(downsample(double(transpose(olddat)),fsorig/fsres)));
      else
        newdat = transpose(downsample(transpose(olddat),fsorig/fsres));
      end

    elseif strcmp(cfg.method, 'resample')
      if isa(olddat, 'single')
        % temporary convert this trial to double precision
        newdat = transpose(single(resample(double(transpose(olddat)),fsres,fsorig)));
      else
        newdat = transpose(resample(transpose(olddat),fsres,fsorig));
      end

    elseif strcmp(cfg.method, 'decimate')
      if isa(olddat, 'single')
        % temporary convert this trial to double precision
        newdat = transpose(single(my_decimate(double(transpose(olddat)),fsorig/fsres)));
      else
        newdat = transpose(my_decimate(transpose(olddat),fsorig/fsres));
      end

    elseif strcmp(cfg.method, 'mean')
      if isa(olddat, 'single')
        % temporary convert this trial to double precision
        newdat = transpose(single(my_mean(double(transpose(olddat)),fsorig/fsres)));
      else
        newdat = transpose(my_mean(transpose(olddat),fsorig/fsres));
      end

    elseif strcmp(cfg.method, 'median')
      if isa(olddat, 'single')
        % temporary convert this trial to double precision
        newdat = transpose(single(my_median(double(transpose(olddat)), fsorig/fsres)));
      else
        newdat = transpose(my_median(transpose(olddat), fsorig/fsres));
      end

    else
      ft_error('unknown method ''%s''', cfg.method);
    end

    % add back the mean
    if ~strcmp(cfg.demean, 'yes')
      nsmp   = size(newdat,2);
      newdat = newdat + bsl(:,ones(1,nsmp));
    end

    % compute the new time axis, assuming that it starts at the same time
    nsmp   = size(newdat,2);
    newtim = (0:(nsmp-1))/cfg.resamplefs;

    % the middle of the time bin represented by the first samples are not aligned
    % the new time axis can be shifted by a sub-sample amount
    shift  = mean(oldtim) - mean(newtim);
    newtim = newtim + shift;

    if begpad(itr)>0 || endpad(itr)>0
      % un-pad the data
      sel = (1+round(begpad(itr)*cfg.resamplefs/cfg.origfs)):(length(newtim)-round(endpad(itr)*cfg.resamplefs/cfg.origfs));
      newtim = newtim(   sel);
      newdat = newdat(:, sel);
    end

    data.time{itr}  = newtim;
    data.trial{itr} = newdat;

  end % for itr
  ft_progress('close');

  % specify the new sampling frequency in the output
  data.fsample = cfg.resamplefs;

elseif usetime
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % resample based on the specified new time axes for each trial
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  if isempty(cfg.extrapval)
    if strcmp(cfg.method, 'spline') || strcmp(cfg.method, 'pchip')
      cfg.extrapval = 'extrap';
    else
      cfg.extrapval = nan;
    end
  end

  ntr = length(data.trial);

  ft_progress('init', cfg.feedback, 'resampling data');
  for itr = 1:ntr
    ft_progress(itr/ntr, 'resampling data in trial %d from %d\n', itr, ntr);

    olddat = data.trial{itr};
    oldtim = data.time{itr};

    % detrending is in general not recommended
    if istrue(cfg.detrend)
      if ~strcmp(cfg.baselinewindow, 'all')
        olddat = ft_preproc_detrend(olddat, nearest(oldtim, cfg.baselinewindow(1)), nearest(oldtim, cfg.baselinewindow(2)));
      else
        olddat = ft_preproc_detrend(olddat);
      end
    end

    % always remove the mean to avoid edge effects when there's a strong offset, the cfg.demean option is dealt with below
    if ~strcmp(cfg.baselinewindow, 'all')
      [olddat, bsl] = ft_preproc_baselinecorrect(olddat, nearest(oldtim, cfg.baselinewindow(1)), nearest(oldtim, cfg.baselinewindow(2)));
    else
      [olddat, bsl] = ft_preproc_baselinecorrect(olddat);
    end

    if istrue(cfg.lpfilter)
      olddat = ft_preproc_lowpassfilter(olddat, cfg.origfs, cfg.lpfreq, cfg.lpfiltord, cfg.lpfilttype);
    end

    % perform the resampling
    newtim = cfg.time{itr};
    if length(oldtim)>1
      newdat = interp1(oldtim', olddat', newtim', cfg.method, cfg.extrapval)';
    else
      newdat = repmat(olddat, [1 numel(newtim)]);
    end

    % add back the mean
    if ~strcmp(cfg.demean, 'yes')
      nsmp   = size(newdat, 2);
      newdat = newdat + bsl(:,ones(1,nsmp));
    end

    data.trial{itr} = newdat;
    data.time{itr}  = newtim;

  end % for itr
  ft_progress('close');

  % specify the new sampling frequency in the output
  t1 = cfg.time{1}(1);
  t2 = cfg.time{1}(2);
  data.fsample = 1/(t2-t1);

end % if usefsample or usetime

ft_info('original sampling rate = %d Hz\nnew sampling rate = %d Hz\n', cfg.origfs, data.fsample);

% convert back to input type if necessary
switch convert
  case 'timelock'
    data = ft_checkdata(data, 'datatype', 'timelock');
  otherwise
    % keep the output as it is
end

% do the general cleanup and bookkeeping at the end of the function
ft_postamble debug
ft_postamble previous   data
ft_postamble provenance data
ft_postamble history    data
ft_postamble savevar    data

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION that decimates along the columns
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function y = my_decimate(x, varargin)
[n, m] = size(x);
% decimate the first column
y = decimate(x(:,1), varargin{:});
if m>1
  % increase the size of the output matrix
  y(:,m) = 0;
  % decimate the subsequent columns
  for i=2:m
    y(:,i) = decimate(x(:,i), varargin{:});
  end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION that does a block-wise average along the columns
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function y = my_mean(x, r)
[n, m] = size(x);
n = n - mod(n,r);
x = x(1:n,:);
x = reshape(x, [r n/r m]);
y = mean(x, 1);
y = reshape(y, [n/r m]);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION that does a block-wise median along the columns
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function y = my_median(x, r)
[n, m] = size(x);
n = n - mod(n,r);
x = x(1:n,:);
x = reshape(x, [r n/r m]);
y = median(x, 1);
y = reshape(y, [n/r m]);


function [formula, nH, charge] = getFormulaAndChargeFromInChI(inchi)

% [formula,charge] = getFormulaAndChargeFromInChI(inchi)
%
% INPUT
% inchi.......Nonstandard IUPAC InChI for a particular pseudoisomer of a
%             metabolite
%
% OUTPUTS
% formula....The chemical formula for the input pseudoisomer
% charge.....The charge on the input pseudoisomer

layers = regexp(inchi,'/','split');
f1 = layers{2}; % Fully protonated formula

p = {};
if ~isempty(strmatch('p',layers))
    p = layers{strmatch('p',layers)}; % nH to add or subtract
end

q = {};
if ~isempty(strmatch('q',layers))
    q = layers{strmatch('q',layers)}; % charge
end

if ~isempty(q)
   charge = str2double(q(2:end));
else
    charge = 0;
end

f1_nH = numAtomsOfElementInFormula(f1,'H'); % nH in fully protonated formula
if ~isempty(p)
    nH = f1_nH + str2double(p(2:end)); % nH in pseudoisomer formula
else
    nH = f1_nH;
end

formula = regexprep(f1,'H[a-z]*[0-9]*',''); % Remove all H from fully protonated formula
if nH == 1
    formula = [formula 'H'];
elseif nH > 1
    formula = [formula 'H' num2str(nH)]; % Add appropriate nH back in to create pseudoisomer formula
elseif nH < 0
    error('Negative number of H in formula.') % Should never get here
end

% In case there is Hg in formula
f1_nHg = numAtomsOfElementInFormula(f1,'Hg');
if f1_nHg == 1
    formula = [formula 'Hg'];
elseif f1_nHg > 1
    formula = [formula 'Hg' num2str(f1_nHg)];
end

end