function [candidates, candScores, alignInfo, otherInfo] = lstmDecoder(models, data, params)
% Decode from an LSTM model.
%   stackSize: the maximum number of translations we want to get.
% Output:
%   - candidates: list of candidates
%   - candScores: score of the corresponding candidates (stackSize * batchSize)
%
% Thang Luong @ 2015, <lmthang@stanford.edu>
%   With help from Hieu Pham.

  % backward compatibility: not a cell, single model, put into a cell format
  if ~iscell(models)
    tmpModels = cell(1, 1);
    tmpModels{1} = models;
    tmpModels{1}.params = params;
    models = tmpModels;
  end

  beamSize = params.beamSize;
  stackSize = params.stackSize;
  batchSize = size(data.srcInput, 1);

  srcMaxLen = data.srcMaxLen;
  params.srcMaxLen = srcMaxLen;

  %% init
  minLen = floor(data.srcMinLen*params.minLenRatio);
  if params.forceDecoder
    maxLen = data.tgtMaxLen;
  else
    maxLen = floor(srcMaxLen*params.maxLenRatio);
  end
  fprintf(2, '# Decoding batch of %d sents, minLen=%d, maxLen=%d, tgtEos=%d, %s\n', batchSize, minLen, maxLen, params.tgtEos, datestr(now));
  fprintf(params.logId, '# Decoding batch of %d sents, minLen=%d, maxLen=%d, tgtEos=%d, %s\n', batchSize, minLen, maxLen, params.tgtEos, datestr(now));

  startTime = clock;

  %%%%%%%%%%%%
  %% encode %%
  %%%%%%%%%%%%
  %% multiple models
  numModels = length(models);
  modelData = cell(numModels, 1);
  zeroStates = cell(numModels, 1);
  for mm=1:numModels
    models{mm}.params.curBatchSize = batchSize;
    models{mm}.params.srcMaxLen = srcMaxLen;
    [models{mm}.params] = setAttnParams(models{mm}.params);

    [zeroStates{mm}] = createZeroState(models{mm}.params);
    modelData{mm} = data;

    % compatible
    [models{mm}.params] = backwardCompatible(models{mm}.params, {'unkDiscount'}, 0);
  end

  % encoder
  prevStates = cell(numModels, 1);
  for mm=1:numModels
    [~, prevStates{mm}, ~, modelData{mm}, ~] = encoderLayerForward(models{mm}, zeroStates{mm}, modelData{mm}, models{mm}.params, 1);
  end

  %%%%%%%%%%%%
  %% decode %%
  %%%%%%%%%%%%
  [candidates, candScores, alignInfo, otherInfo] = rnnDecoder(models, params, prevStates, minLen, maxLen, beamSize, stackSize, batchSize, ...
  modelData, data, params.tgtEos, 0);

  % char: now generate words for <unk>
  if params.charTgtGen % assume all models are char-level models
    assert(stackSize == 1); % TODO: remove this, then char_initEmb needs to be initialized differently.
    ss = 1;

    % prepare starting states
    char_initEmb = cell(numModels, 1);
    for mm=1:numModels
      char_initEmb{mm} = zeroMatrix([params.lstmSize, batchSize * maxLen], params.isGPU, params.dataType);
    end

    numRareWords = 0;
    otherInfo.rarePositions = cell(batchSize, 1);
    for sentId=1:batchSize
      flags = candidates{sentId}{ss} == params.tgtUnk;
      positions = find(flags);

      for mm=1:numModels
        char_initEmb{mm}(:, numRareWords+1:numRareWords+length(positions)) = otherInfo.transStates{ss, mm}(:, positions, sentId);
        % assert(isempty(find(otherInfo.transStates{ss, mm}(:, length(flags), sentId) ~= 0, 1)));
      end

      numRareWords = numRareWords + length(positions);
      otherInfo.rarePositions{sentId} = positions;
    end

    for mm=1:numModels
      char_initEmb{mm}(:, numRareWords+1:end) = [];
    end

    otherInfo.numRareWords = numRareWords;
    otherInfo.rareWords = cell(batchSize, 1);
    if numRareWords
      % char params
      charParams = params;
      charParams.curBatchSize = numRareWords;

      % model
      charModels = cell(numModels, 1);
      for mm=1:numModels
        charModels{mm}.W_tgt = models{mm}.W_tgt_char;
        charModels{mm}.W_emb_tgt = models{mm}.W_emb_tgt_char;
        charModels{mm}.W_soft = models{mm}.W_soft_char;

        charParams.numLayers = models{mm}.params.charNumLayers;
        charParams.tgtVocab = models{mm}.params.tgtCharVocab;

        charModels{mm}.params = charParams;
      end

      % prev states
      char_minLen = 2;
      char_maxLen = 20;
      fprintf(2, '  # Decoding char , minLen=%d, maxLen=%d, charEos=%d, %s\n', char_minLen, char_maxLen, params.tgtCharEos, datestr(now));
      fprintf(params.logId, '  # Decoding char , minLen=%d, maxLen=%d, charEos=%d, %s\n', char_minLen, char_maxLen, params.tgtCharEos, datestr(now));

      char_modelData = cell(numModels, 1);
      char_prevStates = cell(numModels, 1);
      zeroBatch = zeroMatrix([params.lstmSize, charParams.curBatchSize], params.isGPU, params.dataType);

      for mm=1:numModels
        char_prevStates{mm} = cell(charModels{mm}.params.numLayers, 1);
        for ll=1:charModels{mm}.params.numLayers % layer
          if ll == 1
            char_prevStates{mm}{ll}.h_t = char_initEmb{mm}(:, :);
          else
            char_prevStates{mm}{ll}.h_t = zeroBatch;
          end
          char_prevStates{mm}{ll}.c_t = zeroBatch;
        end
      end

      [char_candidates, ~, ~, ~] = rnnDecoder(charModels, charParams, char_prevStates, char_minLen, ...
        char_maxLen, beamSize, stackSize, numRareWords, char_modelData, [], params.tgtCharEos, 1);

      % get words
      count = 0;
      for sentId=1:batchSize
        numCharWords = length(otherInfo.rarePositions{sentId});
        otherInfo.rareWords{sentId} = cell(numCharWords, 1);
        for jj=1:numCharWords
          otherInfo.rareWords{sentId}{jj} = [params.tgtCharVocab{char_candidates{count+jj}{1}(1:end-1)}];
        end
        count = count + numCharWords;
      end
    end
  end

  endTime = clock;
  timeElapsed = etime(endTime, startTime);
  fprintf(2, '  Done, minLen=%d, maxLen=%d, speed %f sents/s, time %.0fs, %s\n', minLen, maxLen, batchSize/timeElapsed, timeElapsed, datestr(now));
  fprintf(params.logId, '  Done, minLen=%d, maxLen=%d, speed %f sents/s, time %.0fs, %s\n', minLen, maxLen, batchSize/timeElapsed, timeElapsed, datestr(now));
end

%%
% Take the initial states, starting symbol, and then decode!
% This method should be reusable.
%%
function [candidates, candScores, alignInfo, otherInfo] = rnnDecoder(models, params, prevStates, minLen, maxLen, beamSize, stackSize, ...
  batchSize, modelData, data, tgtEos, isChar)
  numModels = length(models);

  % first decoder timestep
  attnInfos = cell(numModels, 1);
  firstAlignIdx = [];
  for mm=1:numModels
    % char
    if isChar
      isFeedInput = 0;
      attnFunc = 0;
    else
      isFeedInput = models{mm}.params.feedInput;
      attnFunc = models{mm}.params.attnFunc;
    end
    initEmb = models{mm}.W_emb_tgt(:, repmat(models{mm}.params.tgtSos, batchSize, 1));
    decRnnFlags = struct('decode', 1, 'test', 1, 'attn', attnFunc, 'feedInput', isFeedInput);
    [prevStates{mm}, attnInfos{mm}] = rnnStepLayerForward(models{mm}.W_tgt, initEmb, ...
      prevStates{mm}, ones(batchSize, 1), models{mm}.params, decRnnFlags, modelData{mm}, models{mm});
  end

  % output alignment
  if params.align && isChar == 0
    [~, firstAlignIdx] = getAlignWeights(attnInfos, data.srcLens, models, params);
  end

  if isChar
    sentIndices = 1:batchSize;
  else
    sentIndices = data.startId:(data.startId+batchSize-1);
  end
  [candidates, candScores, alignInfo, otherInfo] = decodeBatch(models, params, prevStates, attnInfos, minLen, maxLen, beamSize, stackSize, batchSize, ...
    sentIndices, modelData, firstAlignIdx, data, tgtEos, isChar);
end

%%
% Beam decoder from an LSTM model, works for multiple sentences
% Input:
%   - encoded vector of the source sentences
%   - maximum length willing to go
%   - beamSize
%   - stackSize: maximum number of translations collected for one example
%%
function [candidates, candScores, alignInfo, otherInfo] = decodeBatch(models, params, prevStates, attnInfos, minLen, maxLen, beamSize, stackSize, batchSize, ...
originalSentIndices, modelData, firstAlignIdx, data, tgtEos, isChar)
  numElements = batchSize*beamSize;

  candidates = cell(batchSize, 1);
  candScores = -1e10*oneMatrix([stackSize, batchSize], params.isGPU, params.dataType); % set to a very small value
  numDecoded = zeros(batchSize, 1);
  for ii=1:batchSize
    candidates{ii} = cell(stackSize, 1);
  end

  numModels = length(models);
  otherInfo = [];

  % align
  if params.align && isChar == 0
    alignInfo = cell(batchSize, 1);
    for ii=1:batchSize
      alignInfo{ii} = cell(stackSize, 1);
    end
  else
    alignInfo = [];
  end

  %% first prediction
  % scores, words: beamSize * batchSize
  if params.forceDecoder
    assert(isChar == 0);
    [scores, words, otherData] = nextBeamStep(models, prevStates, beamSize, 0, isChar, params.tgtUnk, params.unkDiscount, data.tgtOutput(:, 1));
    otherInfo.forceDecodeOutputs = zeroMatrix([maxLen, numElements], params.isGPU, params.dataType); % maxLen * (numElements)
    otherInfo.forceDecodeOutputs(1, :) = otherData.maxWords;
  else
    [scores, words] = nextBeamStep(models, prevStates, beamSize, tgtEos, isChar, params.tgtUnk, params.unkDiscount);
  end

  % TODO: by right, we should filter out words == tgtEos, but I
  % think for good models, we don't have to worry :)

  %% matrix dimension note
  % note that we order matrices in the following dimension: n * numElements
  % columns correspond to the 1st sent go first, then the 2nd one, until the batchSize-th sent.
  sentIndices = repmat(1:batchSize, [beamSize, 1]);
  sentIndices = sentIndices(:)'; % 1 ... 1, 2 ... 2, ...., batchSize ... batchSize . 1 * (beamSize*batchSize)

  %% init beam
  beamScores = scores(:)'; % 1 * numElements
  beamHistory = zeroMatrix([maxLen, numElements], params.isGPU, params.dataType); % maxLen * (numElements)
  beamHistory(1, :) = words(:); % words for sent 1 go together, then sent 2, ...
  if params.align && isChar == 0
    alignHistory = zeroMatrix([maxLen, numElements], params.isGPU, params.dataType); % maxLen * (numElements)
    for ii=1:batchSize
      alignHistory(1,(ii-1)*beamSize+1:ii*beamSize) = firstAlignIdx(ii);
    end
  end

  %% replicate
  beamStates = cell(numModels, 1);
  for mm=1:numModels % model
    beamStates{mm} = cell(models{mm}.params.numLayers, 1);
    for ll=1:models{mm}.params.numLayers % lstmSize * numElements
      beamStates{mm}{ll}.c_t = reshape(repmat(prevStates{mm}{ll}.c_t, beamSize, 1),  models{mm}.params.lstmSize, numElements);
      beamStates{mm}{ll}.h_t = reshape(repmat(prevStates{mm}{ll}.h_t, beamSize, 1),  models{mm}.params.lstmSize, numElements);
    end
    beamStates{mm}{end}.softmax_h = reshape(repmat(prevStates{mm}{end}.softmax_h, beamSize, 1),  models{mm}.params.lstmSize, numElements);
  end

  %% char
  if params.charTgtGen && isChar == 0
    % assert(stackSize == 1);
    beamHistTopStates = cell(numModels, 1);
    otherInfo.transStates = cell(stackSize, numModels);
    for ss=1:stackSize
      for mm=1:numModels
        % sent 1, ..., sent2, ..., sent_batchSize
        otherInfo.transStates{ss, mm} = zeroMatrix([models{mm}.params.lstmSize, maxLen-1, batchSize], params.isGPU, params.dataType);
      end
    end
    for mm=1:numModels
      assert(models{mm}.params.charTgtGen == 1);
      beamHistTopStates{mm} = zeroMatrix([models{mm}.params.lstmSize, numElements, maxLen], params.isGPU, params.dataType);
      if models{mm}.params.charFeedOpt
        tgtWordStates = hiddenLayerForward(models{mm}.W_h_char, attnInfos{mm}.input, params.nonlinear_f);
        beamHistTopStates{mm}(:, :, 1) = reshape(repmat(tgtWordStates, beamSize, 1),  models{mm}.params.lstmSize, numElements);
      else
        beamHistTopStates{mm}(:, :, 1) = beamStates{mm}{end}.softmax_h;
      end
    end
  end

  oneMask = ones(1, numElements);

  if isChar == 0
    data.srcLens = reshape(repmat(data.srcLens, beamSize, 1), 1, []);

    % attentional / positional models
    for mm=1:numModels % model
      if models{mm}.params.attnFunc
        models{mm}.params.curBatchSize = numElements;

        modelData{mm}.curBatchSize = numElements;
        modelData{mm}.srcLens = reshape(repmat(modelData{mm}.srcLens, beamSize, 1), 1, []);

        % duplicate srcHidVecs
        modelData{mm}.srcHidVecsOrig = duplicateSrcHidVecs(modelData{mm}.srcHidVecsOrig, batchSize, beamSize);

        %% replicate srcMask
        % first transpose to srcMaxLen * batchSize
        % replicate (srcMaxLen*beamSize)*batchSize
        % reshape maxLen*(beamSize*batchSize)
        % then transpose back to (beamSize*batchSize)*maxLen
        srcMaxLen = size(modelData{mm}.srcMask, 2);
        modelData{mm}.srcMask = reshape(repmat(modelData{mm}.srcMask', beamSize, 1), srcMaxLen, beamSize*batchSize)';
      end
    end
  end

  % decodeCompleteCount = 0; % count how many sentences we have completed collecting the translations
  beamWords = zeroMatrix([1, numElements], params.isGPU, params.dataType);
  beamIndices = zeroMatrix([1, numElements], params.isGPU, params.dataType);
  % align
  if params.align && isChar == 0
    beamAlignIds = zeroMatrix([1, numElements], params.isGPU, params.dataType);
  end

  if beamSize == 1 % useful for force decoding
    doneFlags = zeros(1, batchSize); % mark if we have finished decoding a sentence
  end

  attnInfos = cell(numModels, 1);
  for sentPos=1:(maxLen-1)
    %% Description:
    % At this point, hypotheses of length sentPos are completed.
    % This loop will prepare hypotheses of length(sentPos+1) by:
    %   (a) first, finding out what the top beamSize^2 nextWords are.
    %   (b) among these nextWords, those which are equal to <eos>, will signal
    %      a complete translations and we will collect. These complete
    %      translations have length (sentPos+1), inclusive of <eos>.
    %   (c) we keep beamSize non-eos nextWords to build hypotheses of length (sentPos+1).
    tgtPos = sentPos+1;

    %% compute next lstm hidden states
    for mm=1:numModels
      % char
      if isChar
        isFeedInput = 0;
        attnFunc = 0;
      else
        isFeedInput = models{mm}.params.feedInput;
        attnFunc = models{mm}.params.attnFunc;
      end

      decRnnFlags = struct('decode', 1, 'test', 1, 'attn', attnFunc, 'feedInput', isFeedInput);
      [beamStates{mm}, attnInfos{mm}] = rnnStepLayerForward(models{mm}.W_tgt, models{mm}.W_emb_tgt(:, beamHistory(sentPos, :)), beamStates{mm}, ...
        oneMask, models{mm}.params, decRnnFlags, modelData{mm}, models{mm});
    end

    %% output alignment
    if params.align && isChar == 0
      [~, alignIdx] = getAlignWeights(attnInfos, data.srcLens, models, params);

      % we want to mimic the structure of allBestWords
      % size (beamSize * beamSize) x batchSize
      alignIdx = reshape(repmat(alignIdx, beamSize, 1), [], batchSize);
    end

    %% predict the next word
    % allBestScores, allBestWords should have size beamSize * (beamSize*batchSize)
    if params.forceDecoder
      assert(isChar == 0);
      [allBestScores, allBestWords, otherData] = nextBeamStep(models, beamStates, beamSize, 0, isChar, params.tgtUnk, params.unkDiscount, data.tgtOutput(:, tgtPos));
      otherInfo.forceDecodeOutputs(tgtPos, :) = otherData.maxWords;
    else
      [allBestScores, allBestWords] = nextBeamStep(models, beamStates, beamSize, 0, isChar, params.tgtUnk, params.unkDiscount);
    end

    % allBestWords, allBestScores should have size: (beamSize*beamSize) * batchSize
    [allBestScores, allBestWords, indices] = addSortScores(allBestScores, allBestWords, beamScores, beamSize, batchSize);

    %% build new beam
    for sentId=1:batchSize
      rowIndices = indices(:, sentId)';
      bestWords = allBestWords(rowIndices, sentId);

      % get candidates
      selectedIndices = find(bestWords~=tgtEos, beamSize);
      if ~isempty(selectedIndices)
        % update words
        startId = (sentId-1)*beamSize+1;
        endId = sentId*beamSize;
        sentWords = bestWords(selectedIndices);
        beamWords(startId:endId) = sentWords;

        % align
        if params.align && isChar == 0
          bestAlignIds = alignIdx(rowIndices, sentId);
          beamAlignIds(startId:endId) = bestAlignIds(selectedIndices);
        end

        % update beam
        sentBeamIndices = floor((rowIndices(selectedIndices)-1)/beamSize) + 1;
        beamIndices(startId:endId) = sentBeamIndices;

        % update scores
        beamScores(startId:endId) = allBestScores(selectedIndices, sentId);

        % get words that are eos and ranked before the last hypothesis in the next beam
        endIndices = find(bestWords(1:selectedIndices(end))==tgtEos);
      else % special case, happen for beamSize = 1, useful for force decoding
        assert(beamSize == 1);
        assert(bestWords == tgtEos);
        if doneFlags(sentId) == 1
          continue;
        else
          doneFlags(sentId) = 1;
          endIndices = 1;
        end
      end

      %% store translations
      if ~isempty(endIndices) && (sentPos+1)>=minLen % we don't want to start recording very short translations
        numTranslations = length(endIndices);
        eosBeamIndices = floor((rowIndices(endIndices)-1)/beamSize) + 1;
        histIndices = (sentId-1)*beamSize + eosBeamIndices;

        transScores = allBestScores(endIndices, sentId);
        for ii=1:numTranslations
          if numDecoded(sentId)<stackSize || transScores(ii) > candScores(stackSize, sentId) % haven't collected enough translations or better than the last
            if numDecoded(sentId)<stackSize
              numDecoded(sentId) = numDecoded(sentId) + 1;
            end

            candScores(numDecoded(sentId), sentId) = transScores(ii);
            candidates{sentId}{numDecoded(sentId)} = [beamHistory(1:sentPos, histIndices(ii)); tgtEos];

            % align
            if params.align && isChar == 0
              alignInfo{sentId}{numDecoded(sentId)} = [alignHistory(1:sentPos, histIndices(ii)); beamAlignIds(histIndices(ii))];
            end

            % char
            if params.charTgtGen && isChar == 0
              for mm=1:numModels % copy from existing states
                otherInfo.transStates{numDecoded(sentId), mm}(:, 1:sentPos, sentId) = beamHistTopStates{mm}(:, histIndices(ii), 1:sentPos);
              end
            end
          end
        end
      end
    end

    %% update history
    % overwrite previous history
    colIndices = (sentIndices-1)*beamSize + beamIndices;
    beamHistory(1:sentPos, :) = beamHistory(1:sentPos, colIndices);
    beamHistory(sentPos+1, :) = beamWords;
    % char
    if params.charTgtGen && isChar == 0
      for mm=1:numModels
        beamHistTopStates{mm}(:, :, 1:sentPos) = beamHistTopStates{mm}(:, colIndices, 1:sentPos);
        if models{mm}.params.charFeedOpt
          beamHistTopStates{mm}(:, :, sentPos+1) = hiddenLayerForward(models{mm}.W_h_char, attnInfos{mm}.input(:, colIndices), params.nonlinear_f);
        else
          beamHistTopStates{mm}(:, :, sentPos+1) = beamStates{mm}{end}.softmax_h(:, colIndices);
        end
      end
    end

    % align
    if params.align && isChar == 0
      alignHistory(1:sentPos, :) = alignHistory(1:sentPos, colIndices);
      alignHistory(sentPos+1, :) = beamAlignIds;
    end

    %% update lstm states
    for mm=1:numModels % model
      for ll=1:models{mm}.params.numLayers
        % lstmSize * (numElements): h_t and c_t vectors of each sent are arranged near each other
        beamStates{mm}{ll}.c_t = beamStates{mm}{ll}.c_t(:, colIndices);
        beamStates{mm}{ll}.h_t = beamStates{mm}{ll}.h_t(:, colIndices);
      end
      beamStates{mm}{end}.softmax_h = beamStates{mm}{end}.softmax_h(:, colIndices);
    end
  end % for sentPos

  for sentId=1:batchSize
    if numDecoded(sentId) == 0 % no translations found, use what we have in the beam
      fprintf(2, '  ! Sent %d: no translations end in %s, appending.\n', originalSentIndices(sentId), params.tgtVocab{tgtEos});
      for bb = 1:beamSize
        eosIndex = (sentId-1)*beamSize + bb;

        if numDecoded(sentId)<stackSize || beamScores(eosIndex) > candScores(stackSize, sentId) % haven't collected enough translations or better than the last
          if numDecoded(sentId)<stackSize
            numDecoded(sentId) = numDecoded(sentId) + 1;
          end

          candidates{sentId}{numDecoded(sentId)} = [beamHistory(1:maxLen, eosIndex); tgtEos]; % append eos at the end
          candScores(numDecoded(sentId), sentId) = beamScores(eosIndex);
          fprintf('  %.2f, num decoded %d:', candScores(numDecoded(sentId), sentId), numDecoded(sentId));
          fprintf(' %s', candidates{sentId}{numDecoded(sentId)});
          fprintf('\n');

          % align
          if params.align && isChar == 0
            if params.isReverse
              alignInfo{sentId}{numDecoded(sentId)} = [alignHistory(1:maxLen, eosIndex); 1];
            else
              alignInfo{sentId}{numDecoded(sentId)} = [alignHistory(1:maxLen, eosIndex); modelData{1}.srcLens(sentId)-1];
            end
          end

          % char
          if params.charTgtGen && isChar == 0
            for mm=1:numModels
              otherInfo.transStates{numDecoded(sentId), mm}(:, 1:maxLen, sentId) = beamHistTopStates{mm}(:, eosIndex, 1:maxLen);
            end
          end
        end
      end % for bb
    end
    candidates{sentId}(numDecoded(sentId)+1:end) = [];
  end
end

function [allBestScores, allBestWords, indices] = addSortScores(allBestScores, allBestWords, beamScores, beamSize, batchSize)
  % use previous beamScores, 1 * (beamSize*batchSize), update along the first dimentions
  allBestScores = bsxfun(@plus, allBestScores, beamScores);
  allBestScores = reshape(allBestScores, [beamSize*beamSize, batchSize]);
  allBestWords = reshape(allBestWords, [beamSize*beamSize, batchSize]);

  % for each sent, select the best beamSize candidates, out of beamSize*beamSize ones
  if beamSize > 1
    [allBestScores, indices] = sort(allBestScores, 'descend'); % (beamSize*beamSize) * batchSize
  else
    indices = ones(1, batchSize);
  end
end

%%
% return bestLogProbs, bestWords of sizes beamSize * curBatchSize
%%
function [bestLogProbs, bestWords, otherData] = nextBeamStep(models, lastDecStates, beamSize, ignoreSymbol, isChar, tgtUnk, unkDiscount, varargin)
  softmax_input = models{1}.W_soft*lastDecStates{1}{end}.softmax_h;
  if length(models)>1 % aggregate predictions from multiple models
    for ii=2:length(models)
      softmax_input = softmax_input + models{ii}.W_soft*lastDecStates{ii}{end}.softmax_h;
    end
    softmax_input = softmax_input./length(models);
  end
  [logProbs] = softmaxDecode(softmax_input);

  % sort
  if length(varargin)==1 % force decoder
    correctWords = reshape(varargin{1}, 1, []);
    bestWords = correctWords;
    bestLogProbs = logProbs(sub2ind(size(logProbs), correctWords, 1:size(logProbs, 2)));
    [~, otherData.maxWords] = max(logProbs, [], 1);
  else
    [sortedLogProbs, sortedWords] = sort(logProbs, 'descend');
    bestWords = sortedWords(1:beamSize, :);
    bestLogProbs = sortedLogProbs(1:beamSize, :);
    otherData = [];

    if ignoreSymbol>0
      flags = bestWords == ignoreSymbol;
      cols = find(any(flags)); % mark which column has the ignoreSymbol
      if ~isempty(cols)
        indices = find(flags);
        assert(length(indices) == length(cols)); % each col has a single ignoreSymbol
        bestWords(indices) = sortedWords(beamSize+1, cols);
        bestLogProbs(indices) = sortedLogProbs(beamSize+1, cols);
      end
    end

    if isChar==0 && unkDiscount>0
      flags = bestWords == tgtUnk;
      bestLogProbs(flags) = bestLogProbs(flags) - unkDiscount;
    end
  end
end

function [logProbs] = softmaxDecode(scores)
%% only compute logProbs
  mx = max(scores);
  scores = bsxfun(@minus, scores, mx); % subtract max elements
  logProbs = bsxfun(@minus, scores, log(sum(exp(scores))));
end

function [srcHidVecs] = duplicateSrcHidVecs(srcHidVecs, batchSize, beamSize)
  numElements = batchSize*beamSize;
  lstmSize = size(srcHidVecs, 1);
  numPositions = size(srcHidVecs, 3);
  srcHidVecs = permute(srcHidVecs, [1, 3, 2]); % lstmSize * numPositions * batchSize
  srcHidVecs = reshape(srcHidVecs, lstmSize*numPositions, batchSize);
  srcHidVecs = repmat(srcHidVecs, beamSize, 1);
  srcHidVecs = reshape(srcHidVecs, lstmSize, numPositions, numElements);
  srcHidVecs = permute(srcHidVecs, [1, 3, 2]); % lstmSize * numElements * numPositions
end


fid = fopen('G:/100_25/zzzpairs.txt');
CC = fscanf(fid,'%d %d');
n_set = CC(1);n_num=CC(2);

same_pair = cell(n_set*n_num,2);
diff_pair = cell(n_set*n_num,2);
lfw_label = zeros(n_set*n_num * 2,2);
label_ht = java.util.Hashtable;

for i=1:n_set
    for j = 1 : n_num
        CC = textscan(fid, '%s %d %d\n');
        p = CC{1};id1=CC{2};id2=CC{3};
        same_pair((i-1)*n_num + j,1) = {sprintf('G:/100_25/%s_%04d-l.bmp',p{1},id1)};
        same_pair((i-1)*n_num + j,2) = {sprintf('G:/100_25/%s_%04d-l.bmp',p{1},id2)};
        if label_ht.containsKey(p{1})
            lfw_label((i-1)*n_num + j,1) = label_ht.get(p{1});
            lfw_label((i-1)*n_num + j,2) = label_ht.get(p{1});
        else
            idx = label_ht.size()+1;
            lfw_label((i-1)*n_num + j,1) = idx;
            lfw_label((i-1)*n_num + j,2) = idx;
            label_ht.put(p{1},idx);
        end;
    end;
    for j = 1 : n_num
         CC = textscan(fid, '%s %d %s %d\n');
         p1 = CC{1};id1=CC{2};p2=CC{3};id2=CC{4};
        diff_pair((i-1)*n_num + j,1) = {sprintf('G:/100_25/%s_%04d-l.bmp',p1{1},id1)};
        diff_pair((i-1)*n_num + j,2) = {sprintf('G:/100_25/%s_%04d-l.bmp',p2{1},id2)};
        if label_ht.containsKey(p1{1})
            lfw_label((i-1)*n_num + j + n_set*n_num,1) = label_ht.get(p1{1});
        else
            idx = label_ht.size()+1;
            label_ht.put(p1{1},idx);
            lfw_label((i-1)*n_num + j + n_set*n_num,1) = idx;
        end;
        if label_ht.containsKey(p2{1})
            lfw_label((i-1)*n_num + j + n_set*n_num,2) = label_ht.get(p2{1});
        else
            idx = label_ht.size()+1;
            label_ht.put(p2{1},idx);
            lfw_label((i-1)*n_num + j + n_set*n_num,2) = idx ;
        end;
    end;
end;
fclose(fid);


function [ sStudy ] = db_parse_study( studiesDir, studySubDir, sizeProgress )
% DB_PARSE_DIR_STUDY: Parse a study directory.
%
% USAGE:  sStudy = db_parse_study(studiesDir, studySubDir, sizeProgress);
%
% INPUT:
%     - studiesDir   : Top-level studies directory (protocol/data/)
%     - studySubDir  : Study subdirectory
%     - sizeProgress : The full process increments the available progress bar by this amount
% OUTPUT:
%     - sStudy : array of Study structures (one entry for each subdirectory with a brainstormstudy file),
%                or [] if no brainstormstudy was found in directory

% NOTES:
% In a study subdirectory (=condition) there is :
%     - '*_brainstormstudy*.mat' : One and only one
%     - '*_channel*.mat'         : One and only one
%     - '*_headmodel*.mat'       : any number
%     - '*_data*.mat'            : any number
%     - '*_result*.mat'          : any number
%     - '*_linkresult*.mat'      : any number
%     - '*_presults*.mat'        : any number
%     - '*_pdata*.mat'           : any number
%     - '*_ptimefreq*.mat'       : any number
%     - '*_dipoles*.mat'         : any number
%     - 'Default' directory      : Defaults files for current study subdirectory
%                                  => May contain : '*_channel*.mat' and '*_headmodel*.mat' files
%     - Any number of subdirectories, that will be interpreted as sub-conditions (processed recursively)
% All the .MAT files in the directory are associated with the current study.
%
% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2008-2022

%% ===== PARSE INPUTS =====
if ~file_exist(bst_fullfile(studiesDir, studySubDir))
    error('Path in argument does not exist, or is not a directory');
end
if (nargin < 3) || isempty(sizeProgress)
    sizeProgress = [];
end

%% ===== LOOK FOR STUDY DEFINITION =====
% List 'brainstormstudy*.mat' files in the studySubDir directory
studyFiles = dir(bst_fullfile(studiesDir, studySubDir, 'brainstormstudy*.mat'));
% List all '*.mat' files and subcondition directories in the studySubDir directory
allFiles = dir(bst_fullfile(studiesDir, studySubDir, '*'));
% If there is no subject definition file : ignore the directory (but process subdirectories)
if isempty(studyFiles)
    studyMat = [];
% If there is more that one subject definition file : warning and ignore the directory (but process subdirectories)
elseif (length(studyFiles) > 1)
    studyMat = [];
    warning('Brainstorm:InvalidDirectory','There is more than one brainstormstudy file in directory ''%s'' : ignoring directory.', bst_fullfile(studiesDir, studySubDir));
else
    % Then there is only one file in studyFiles list
    studyFile = studyFiles(1);
    % Open file and get subject description
    try
        studyMat = load(bst_fullfile(studiesDir, studySubDir, studyFile.name));
    catch
        warning('Brainstorm:InvalidFile', 'Cannot open file ''%s'' : ignoring study', bst_fullfile(studiesDir, studySubDir, studyFile.name));
        studyMat = [];
    end
end

%% ===== READ STUDY DESCRIPTION =====
BadTrials = {};
if ~isempty(studyMat)
    % Initialize returned variable
    sStudy = db_template('Study');
    % ==== STUDY DESCRIPTION ====
    % Store subject's .MAT filename
    sStudy(1).FileName = bst_fullfile(studySubDir, studyFile.name);
    % Subject file
    if ismember(studySubDir, {bst_get('DirAnalysisInter'), bst_get('DirDefaultStudy')})
        sStudy(1).BrainStormSubject = bst_fullfile(bst_get('DirDefaultSubject'), 'brainstormsubject.mat');
    else
        sStudy(1).BrainStormSubject = bst_fullfile(bst_fileparts(studySubDir,1), 'brainstormsubject.mat');
    end
    % Name
    if isfield(studyMat, 'Name')
        sStudy(1).Name = studyMat.Name;
    end
    % DateOfStudy
    if isfield(studyMat, 'DateOfStudy')
        sStudy(1).DateOfStudy = studyMat.DateOfStudy;
    end
    % Bad trials
    if isfield(studyMat, 'BadTrials')
        % Remove files that do not exist from the list of bad trials
        if ~isempty(studyMat.BadTrials)
            iRemove = find(~ismember(studyMat.BadTrials, {allFiles.name}));
            if ~isempty(iRemove)
                studyMat.BadTrials(iRemove) = [];
                bst_save(bst_fullfile(studiesDir, sStudy(1).FileName), studyMat, 'v7');
            end
        end
        % Keep list of bad trials for parsing the rest of the folder
        BadTrials = studyMat.BadTrials;
    end

    % ==== STUDY CONDITION ====
    % The study subdirectory has a structure 'subject/condition/subcondition/...'
    % To get the conditions list : split the filename, and eliminate the first part (subject)
    conditionsList = str_split(studySubDir);
    conditionsList(1) = [];
    sStudy(1).Condition = conditionsList;
else
    sStudy = repmat(db_template('Study'), 0);
end

%% ===== READ ALL FILES IN FOLDER =====
% Exclude from the list of files:
%    - Files starting with a '.'
%    - ANALYSIS INTER directory
%    - ROOT DEFAULT STUDY directory
%    - Any other study folder that is immediately in the "data" folder
dirFiles = repmat(allFiles,0);
for iFile = 1:length(allFiles)
    if (allFiles(iFile).name(1) ~= '.') && ~strcmpi(allFiles(iFile).name, bst_get('DirAnalysisInter')) ...
                                        && ~(isempty(studySubDir) && strcmpi(allFiles(iFile).name, bst_get('DirDefaultStudy')))
        dirFiles(end+1,1) = allFiles(iFile);
    end
end
if isempty(dirFiles)
    return;
end
% Find channel file ('channel' tag)
isChannelFile = ~cellfun(@(c)isempty([strfind(lower(c),'_channel'), strfind(lower(c),'channel_')]), {dirFiles.name});
% Reorder directory files (put channel files first)
dirFiles = cat(1, dirFiles(isChannelFile), dirFiles(~isChannelFile));
% Initialize list of trials
trialFiles = {};
trialData = [];
trialInd = {};

% Progress bar?
isProgressBar = bst_progress('isvisible') && ~isempty(sizeProgress);
startValue = bst_progress('get');
curValue = startValue;
% Process all the files
for iFile = 1:length(dirFiles)
    % Increment progress bar
    if isProgressBar
        tmpValue = round(startValue + (iFile-1)/length(dirFiles) * sizeProgress);
        if (curValue ~= tmpValue)
            bst_progress('set', curValue);
            curValue = tmpValue;
        end
    end
    % Directories : recursively call this function, to add subconditions studies
    if (dirFiles(iFile).isdir)
        % Append studies in the subdirectory to the sStudy array
        sStudy = [sStudy, db_parse_study(studiesDir, bst_fullfile(studySubDir, dirFiles(iFile).name), round(sizeProgress / length(dirFiles)))];

    % Files (only if there is a brainstormstudy file in current directory)
    elseif ~isempty(studyMat)
        % Reconstruct filename
        filenameRelative = bst_fullfile(studySubDir, dirFiles(iFile).name);
        filenameFull     = bst_fullfile(studiesDir, studySubDir, dirFiles(iFile).name);
        % Determine filetype
        fileType = file_gettype(filenameFull);
        if isempty(fileType)
            continue;
        end
        % Process file
        switch(fileType)
            case 'channel'
                % Try to load channel info
                channelInfo = io_getChannelInfo(filenameRelative);
                % If a channel file was found : copy channel information
                if ~isempty(channelInfo)
                    sStudy(1).Channel = channelInfo;
                end
            case 'noisecov'
                % Try to load noisecov info
                noisecovInfo = io_getNoisecovInfo(filenameRelative);
                % If a channel file was found : copy channel information
                if ~isempty(noisecovInfo)
                    sStudy(1).NoiseCov(1) = noisecovInfo;
                end
            case 'ndatacov'
                % Try to load noisecov info
                noisecovInfo = io_getNoisecovInfo(filenameRelative);
                % If a channel file was found : copy channel information
                if ~isempty(noisecovInfo)
                    sStudy(1).NoiseCov(2) = noisecovInfo;
                end

            case {'data', 'spike'}
%                 % Get data filename without the trial block
%                 [groupName, iTrial] = str_remove_trial(filenameRelative);
%                 dataInfo = [];
%                 % If it is a trial in a group of trials
%                 if ~isempty(groupName)
%                     iTrialGroup = find(strcmpi(groupName, trialFiles));
%                     % Not existing in the trial group
%                     if isempty(iTrialGroup)
%                         trialFiles{end+1} = groupName;
%                         trialData(end+1) = length(sStudy(1).Data) + 1;
%                         trialInd{end+1} = iTrial;
%                     % Existing in the trial group
%                     elseif ~isempty(iTrial)
%                         dataInfo = sStudy(1).Data(trialData(iTrialGroup));
%                         dataInfo.Comment = strrep(dataInfo.Comment, ['(#' num2str(trialInd{iTrialGroup}) ')'], ['(#' num2str(iTrial) ')']);
%                         dataInfo.FileName = filenameRelative;
%                     end
%                 end
                % Try to load channel info
%                 if isempty(dataInfo)
                    dataInfo = io_getDataInfo(filenameRelative);
                    if ~isempty(dataInfo) && isempty(dataInfo.Comment)
                        dataInfo.Comment = '';
                    end
%                 end
                % If a data file was found : copy data information
                if ~isempty(dataInfo)
                    % Bad trial ?
                    if ~isempty(BadTrials)
                        dataInfo.BadTrial = ismember(dirFiles(iFile).name, BadTrials);
                    else
                        dataInfo.BadTrial = 0;
                    end
                    % Copy file description
                    sStudy(1).Data(end+1) = dataInfo;
                % Else : file is not valid : rename it to avoid it slowing the parsing
                else
                    fullfilename = bst_fullfile(studiesDir, filenameRelative);
                    file_move(fullfilename, [fullfilename, '.bak']);
                end

            case 'dipoles'
                % Try to load channel info
                dipolesInfo = io_getDipolesInfo(filenameRelative);
                % If a data file was found : copy data information
                if ~isempty(dipolesInfo)
                    sStudy(1).Dipoles(end+1) = dipolesInfo;
                % Else : file is not valid : rename it to avoid it slowing the parsing
                else
                    fullfilename = bst_fullfile(studiesDir, filenameRelative);
                    file_move(fullfilename, [fullfilename, '.bak']);
                end
            case 'timefreq'
                % Try to load channel info
                timefreqInfo = io_getTimefreqInfo(filenameRelative);
                % If a data file was found : copy data information
                if ~isempty(timefreqInfo)
                    sStudy(1).Timefreq(end+1) = timefreqInfo;
                % Else : file is not valid : rename it to avoid it slowing the parsing
                else
                    fullfilename = bst_fullfile(studiesDir, filenameRelative);
                    file_move(fullfilename, [fullfilename, '.bak']);
                end
            case 'headmodel'
                % Try to load headmodel info
                headModelInfo = io_getHeadModelInfo(filenameRelative);
                % If a data file was found : copy headmodel information
                if ~isempty(headModelInfo)
                    sStudy(1).HeadModel(end+1) = headModelInfo;
                end
            case 'results'
                % Try to load result info
                resultsInfo = io_getResultInfo(filenameRelative);
                % If a data file was found : copy information
                if ~isempty(resultsInfo)
                    % Mark if it is a link-results file
                    resultsInfo.isLink = 0;
                    % Store file in study list
                    sStudy(1).Result(end+1) = resultsInfo;
                % Else : file is not valid : rename it to avoid it slowing the parsing
                else
                    fullfilename = bst_fullfile(studiesDir, filenameRelative);
                    file_move(fullfilename, [fullfilename, '.bak']);
                end

            case 'linkresults'
                % Remove file
                file_delete(bst_fullfile(studiesDir, filenameRelative), 1);
                disp(['Old linkresults file deleted: ', filenameRelative]);

            case {'presults', 'pdata', 'ptimefreq', 'pmatrix'}
                % Try to load result info
                statInfo = io_getStatInfo(filenameRelative, fileType);
                % If a data file was found : copy headmodel information
                if ~isempty(statInfo)
                    sStudy(1).Stat(end+1) = statInfo;
                % Else : file is not valid : rename it to avoid it slowing the parsing
                else
                    fullfilename = bst_fullfile(studiesDir, filenameRelative);
                    file_move(fullfilename, [fullfilename, '.bak']);
                end
            case 'matrix'
                % Try to load headmodel info
                matrixInfo = io_getMatrixInfo(filenameRelative);
                % If a data file was found : copy headmodel information
                if ~isempty(matrixInfo)
                    sStudy(1).Matrix(end+1) = matrixInfo;
                end
            case 'image'
                [fPath, fBase, fExt] = bst_fileparts(filenameRelative);
                sStudy(1).Image(end+1) = struct('FileName', filenameRelative, ...
                                                'Comment',  [fBase, fExt]);
            case 'videolink'
                % Load file info
                try
                    % Try to load information
                    warning off MATLAB:load:variableNotFound
                    infoMat = load(bst_fullfile(studiesDir, filenameRelative), 'Comment');
                    warning on MATLAB:load:variableNotFound
                    fileComment = infoMat.Comment;
                catch
                    [fPath, fBase, fExt] = bst_fileparts(filenameRelative);
                    fileComment = [fBase, fExt];
                end
                % Create structure
                sStudy(1).Image(end+1) = struct('FileName', filenameRelative, ...
                                                'Comment',  fileComment);
        end
    end
end
% Set progress bar to the end
if isProgressBar
    bst_progress('set', round(startValue + sizeProgress));
end

% Select study defaults in the processed files
if ~isempty(studyMat)
    % HeadModel : last file
    if ~isempty(sStudy(1).HeadModel)
        sStudy(1).iHeadModel = length(sStudy(1).HeadModel);
    else
        sStudy(1).iHeadModel = [];
    end
end

return

%% ===================================================================================
%  === HELPER FUNCTIONS ==============================================================
%  ===================================================================================
    % CHANNEL : Load channel*.mat file information from a relative filename
    % Return a Channel structure :
    %    |- FileName
    %    |- Comment
    %    |- nbChannels

    % or an empty structure if error
    function sInfo = io_getChannelInfo(relativeFilename)
        sInfo = repmat(db_template('Channel'),0);
        % Check if the file exists, and load information fields
        if file_exist(bst_fullfile(studiesDir, relativeFilename))
            try
                % Try to load whole file
                warning off MATLAB:load:variableNotFound
                infoMat = load(bst_fullfile(studiesDir, relativeFilename),'Channel','Comment');
                warning on MATLAB:load:variableNotFound
                % Process all the channels, to count canals of each type
                sInfo(1).nbChannels = length(infoMat.Channel);
                channelList = infoMat.Channel;
                % Comment field
                if isfield(infoMat, 'Comment')
                    sInfo(1).Comment = infoMat.Comment;
                else
                    % Build channel name
                    [tmp__, chComment] = bst_fileparts(relativeFilename);
                    chComment = lower(chComment);
                    chComment = strrep(chComment, '_channel', '');
                    chComment = strrep(chComment, 'channel_', '');
                    chComment = strrep(chComment, 'channel', '');
                    if ~isempty(chComment)
                        sInfo(1).Comment = chComment;
                    else
                        sInfo(1).Comment = sprintf('Channels ', sInfo(1).nbChannels);
                    end
                end
                if isempty(strfind(sInfo(1).Comment, ')'))
                    sInfo(1).Comment = sprintf('%s (%d)', sInfo(1).Comment, sInfo(1).nbChannels);
                end
                % Copy filename
                sInfo(1).FileName = relativeFilename;
                % Get all available modalities
                if ~isempty(channelList)
                    [sInfo(1).Modalities, sInfo(1).DisplayableSensorTypes] = channel_get_modalities(channelList);
                end
            catch
                % An error occured during the 'load' operation
                warning('Cannot open file ''%s''.', bst_fullfile(studiesDir, relativeFilename));
                sInfo = [];
            end
        else
            % File does not exist
            warning('File ''%s'' was not found.', bst_fullfile(studiesDir, relativeFilename));
            sInfo = [];
        end
    end

    % HEADMODEL : Load *headmodel*.mat file information from a relative filename
    % Return a Headmodel info structure :
    %    |- FileName
    %    |- Comment
    %    |- HeadModelType
    %    |- MEGMethod
    %    |- EEGMethod
    %    |- ECOGMethod
    %    |- SEEGMethod
    % or an empty structure if error
    function sInfo = io_getHeadModelInfo(relativeFilename)
        sInfo = repmat(db_template('HeadModel'),0);
        % Check if the file exists, and load information fields
        if file_exist(bst_fullfile(studiesDir, relativeFilename))
            try
                % Try to load information
                warning off MATLAB:load:variableNotFound
                infoMat = in_bst_headmodel(bst_fullfile(studiesDir, relativeFilename), 0, ...
                    'Comment', 'HeadModelType', 'MEGMethod', 'EEGMethod', 'ECOGMethod', 'SEEGMethod');
                warning on MATLAB:load:variableNotFound
                % Try to copy information
                if isfield(infoMat, 'Comment')
                    sInfo(1).Comment = infoMat.Comment;
                end
                if isfield(infoMat, 'HeadModelType')
                    sInfo(1).HeadModelType = infoMat.HeadModelType;
                end
                if isfield(infoMat, 'MEGMethod')
                    sInfo(1).MEGMethod   = infoMat.MEGMethod;
                end
                if isfield(infoMat, 'EEGMethod')
                    sInfo(1).EEGMethod   = infoMat.EEGMethod;
                end
                if isfield(infoMat, 'ECOGMethod')
                    sInfo(1).ECOGMethod   = infoMat.ECOGMethod;
                end
                if isfield(infoMat, 'SEEGMethod')
                    sInfo(1).SEEGMethod   = infoMat.SEEGMethod;
                end
                % Copy filename
                sInfo(1).FileName = relativeFilename;
            catch
                % An error occured during the 'load' operation
                warning('Cannot open file ''%s''.', bst_fullfile(studiesDir, relativeFilename));
                sInfo = [];
            end
        else
            % File does not exist
            warning('File ''%s'' was not found.', bst_fullfile(studiesDir, relativeFilename));
            sInfo = [];
        end
    end

    % DATA : Load *data*.mat file information from a relative filename
    % Return a Data info structure :
    %    |- FileName
    %    |- Comment
    % or an empty structure if error
    function sInfo = io_getDataInfo(relativeFilename)
        sInfo = repmat(db_template('Data'),0);
        % Check if the file exists, and load information fields
        if file_exist(bst_fullfile(studiesDir, relativeFilename))
            try
                % Try to load information
                infoMat = in_bst_data(bst_fullfile(studiesDir, relativeFilename), 'Comment', 'DataType');
                % Copy information (required fields)
                sInfo(1).Comment  = infoMat.Comment;
                sInfo(1).FileName = relativeFilename;
                sInfo(1).DataType = infoMat.DataType;
            catch
                % An error occured during the 'load' operation
                warning('Cannot open file ''%s''.', bst_fullfile(studiesDir, relativeFilename));
                sInfo = [];
            end
        else
            % File does not exist
            warning('File ''%s'' was not found.', bst_fullfile(studiesDir, relativeFilename));
            sInfo = [];
        end
    end

    % DIPOLES : Load *dipoles*.mat file information from a relative filename
    % Return a Dipoles info structure :
    %    |- FileName
    %    |- Comment
    %    |- DataFile
    % or an empty structure if error
    function sInfo = io_getDipolesInfo(relativeFilename)
        sInfo = repmat(db_template('Dipoles'),0);
        % Check if the file exists, and load information fields
        if file_exist(bst_fullfile(studiesDir, relativeFilename))
            try
                % Try to load information
                warning off MATLAB:load:variableNotFound
                infoMat = load(bst_fullfile(studiesDir, relativeFilename), 'Comment', 'DataFile');
                warning on MATLAB:load:variableNotFound
                % Check if data was loaded
                if ~all(isfield(infoMat, 'Comment'))
                    warning('File "%s" is not a valid result file. Appending ''.bak'' at the filename...', relativeFilename);
                    return
                end
                % Copy information (required fields)
                sInfo(1).Comment  = infoMat.Comment;
                sInfo(1).FileName = relativeFilename;
                sInfo(1).DataFile = infoMat.DataFile;
            catch
                % An error occured during the 'load' operation
                warning('Cannot open file ''%s''.', bst_fullfile(studiesDir, relativeFilename));
                sInfo = [];
            end
        else
            % File does not exist
            warning('File ''%s'' was not found.', bst_fullfile(studiesDir, relativeFilename));
            sInfo = [];
        end
    end

    % TIMEFREQ : Load *timefreq*.mat file information from a relative filename
    % Return a Timefreq info structure :
    %    |- FileName
    %    |- Comment
    %    |- DataFile
    % or an empty structure if error
    function sInfo = io_getTimefreqInfo(relativeFilename)
        sInfo = repmat(db_template('Timefreq'),0);
        % Check if the file exists, and load information fields
        if file_exist(bst_fullfile(studiesDir, relativeFilename))
            try
                % Try to load information
                warning off MATLAB:load:variableNotFound
                infoMat = load(bst_fullfile(studiesDir, relativeFilename), 'Comment', 'DataFile', 'DataType');
                warning on MATLAB:load:variableNotFound
                % Check if data was loaded
                if ~all(isfield(infoMat, 'Comment'))
                    warning('File "%s" is not a valid result file. Appending ''.bak'' at the filename...', relativeFilename);
                    return
                end
                % Copy information (required fields)
                sInfo(1).Comment  = infoMat.Comment;
                sInfo(1).FileName = relativeFilename;
                sInfo(1).DataFile = infoMat.DataFile;
                sInfo(1).DataType = infoMat.DataType;
            catch
                % An error occured during the 'load' operation
                warning('Cannot open file ''%s''.', bst_fullfile(studiesDir, relativeFilename));
                sInfo = [];
            end
        else
            % File does not exist
            warning('File ''%s'' was not found.', bst_fullfile(studiesDir, relativeFilename));
            sInfo = [];
        end
    end

    % STAT : Load pdata, presults, ptimefreq file information from a relative filename
    % Return a Data info structure :
    %    |- FileName
    %    |- Comment
    %    |- Type
    % or an empty structure if error
    function sInfo = io_getStatInfo(relativeFilename, fileType)
        sInfo = repmat(db_template('Stat'),0);
        % Check if the file exists, and load information fields
        if file_exist(bst_fullfile(studiesDir, relativeFilename))
            try
                % Try to load information
                warning off MATLAB:load:variableNotFound
                infoMat = load(bst_fullfile(studiesDir, relativeFilename), 'Comment', 'Type');
                warning on MATLAB:load:variableNotFound
                % Check if data was loaded
                if ~all(isfield(infoMat, {'Comment'}))
                    warning('File "%s" is not a valid stat file. Appending ''.bak'' at the filename...', relativeFilename);
                    return
                end
                % Copy information (required fields)
                sInfo(1).Comment    = infoMat.Comment;
                sInfo(1).FileName   = relativeFilename;
                % Type
                if isfield(infoMat, 'Type')
                    sInfo(1).Type = infoMat.Type;
                else
                    switch(fileType)
                        case 'pdata',      sInfo(1).Type = 'data';
                        case 'presults',   sInfo(1).Type = 'results';
                        case 'ptimefreq',  sInfo(1).Type = 'timefreq';
                        case 'pmatrix',    sInfo(1).Type = 'matrix';
                    end
                end
            catch
                % An error occured during the 'load' operation
                warning('Cannot open file ''%s''.', bst_fullfile(studiesDir, relativeFilename));
                sInfo = [];
            end
        else
            % File does not exist
            warning('File ''%s'' was not found.', bst_fullfile(studiesDir, relativeFilename));
            sInfo = [];
        end
    end

    % RESULT : Load *result*.mat file information from a relative filename
    % Return a Result info structure :
    %    |- FileName
    %    |- Comment
    %    |- DataFile
    % or an empty structure if error
    function sInfo = io_getResultInfo(relativeFilename)
        sInfo = repmat(db_template('Results'),0);
        % Check if the file exists, and load information fields
        if file_exist(bst_fullfile(studiesDir, relativeFilename))
            try
                % Try to load information
                warning off MATLAB:load:variableNotFound
                infoMat = load(bst_fullfile(studiesDir, relativeFilename), 'Comment', 'DataFile', 'HeadModelType');
                warning on MATLAB:load:variableNotFound
                % Check if data was loaded
                if ~all(isfield(infoMat, {'DataFile', 'Comment'}))
                    warning('File "%s" is not a valid result file. Appending ''.bak'' at the filename...', relativeFilename);
                    return
                end
                % Create results structure
                sInfo(1).Comment  = infoMat.Comment;
                sInfo(1).DataFile = infoMat.DataFile;
                sInfo(1).FileName = relativeFilename;
                if isfield(infoMat, 'HeadModelType') && ~isempty(infoMat.HeadModelType)
                    sInfo(1).HeadModelType = infoMat.HeadModelType;
                else
                    sInfo(1).HeadModelType = 'surface';
                end
            catch
                % An error occured during the 'load' operation
                warning('Cannot open file ''%s''.', bst_fullfile(studiesDir, relativeFilename));
                sInfo = [];
            end
        else
            % File does not exist
            warning('File ''%s'' was not found.', bst_fullfile(studiesDir, relativeFilename));
            sInfo = [];
        end
    end

    % NOISECOV : Load *noisecov*.mat file information from a relative filename
    % Return a NoiseCov info structure :
    %    |- FileName
    %    |- Comment
    % or an empty structure if error
    function sInfo = io_getNoisecovInfo(relativeFilename)
        sInfo = repmat(db_template('NoiseCov'),0);
        % Check if the file exists, and load information fields
        if file_exist(bst_fullfile(studiesDir, relativeFilename))
            try
                % Try to load information
                warning off MATLAB:load:variableNotFound
                infoMat = load(bst_fullfile(studiesDir, relativeFilename), 'Comment');
                warning on MATLAB:load:variableNotFound
                % Check if data was loaded
                if ~all(isfield(infoMat, {'Comment'}))
                    warning('File "%s" is not a valid brainstorm file. Appending ''.bak'' at the filename...', relativeFilename);
                    return
                end
                % Create results structure
                sInfo(1).Comment  = infoMat.Comment;
                sInfo(1).FileName = relativeFilename;
            catch
                % An error occured during the 'load' operation
                warning('Cannot open file ''%s''.', bst_fullfile(studiesDir, relativeFilename));
                sInfo = [];
            end
        else
            % File does not exist
            warning('File ''%s'' was not found.', bst_fullfile(studiesDir, relativeFilename));
            sInfo = [];
        end
    end

    % MATRIX : Load *matrix*.mat file information from a relative filename
    % Return a Matrix info structure :
    %    |- FileName
    %    |- Comment
    % or an empty structure if error
    function sInfo = io_getMatrixInfo(relativeFilename)
        sInfo = repmat(db_template('Matrix'),0);
        % Check if the file exists, and load information fields
        if file_exist(bst_fullfile(studiesDir, relativeFilename))
            try
                % Try to load information
                warning off MATLAB:load:variableNotFound
                infoMat = load(bst_fullfile(studiesDir, relativeFilename), 'Comment');
                warning on MATLAB:load:variableNotFound
                % Check if data was loaded
                if ~all(isfield(infoMat, {'Comment'}))
                    warning('File "%s" is not a valid brainstorm file. Appending ''.bak'' at the filename...', relativeFilename);
                    return
                end
                % Create results structure
                sInfo(1).Comment  = infoMat.Comment;
                sInfo(1).FileName = relativeFilename;
            catch
                % An error occured during the 'load' operation
                warning('Cannot open file ''%s''.', bst_fullfile(studiesDir, relativeFilename));
                sInfo = [];
            end
        else
            % File does not exist
            warning('File ''%s'' was not found.', bst_fullfile(studiesDir, relativeFilename));
            sInfo = [];
        end
    end

end


%{
    Copyright (C) Cambridge Electronic Design Limited 2014
    Author: James Thompson
    Web: www.ced.co.uk email: james@ced.co.uk, softhelp@ced.co.uk

    This file is part of CEDS64ML, a MATLAB interface to the SON64 library.

    CEDS64ML 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.

    CEDS64ML 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 CEDS64ML.  If not, see <http://www.gnu.org/licenses/>.
%}

function [ dTBaseOut ] = CEDS64TimeBase( fhand, dTBaseIn )
%CEDS64TIMEBASE Gets and sets the file time base
%   [ dTBaseOut ] = CEDS64TimeBase( fhand, dTBaseIn )
%   Inputs
%   fhand - An integer handle to an open file
%   dTBaseIn - (Optional) The new time base as a double
%   Outputs
%   dTBaseOut - The old time base as a double, or a number <= 0 if an error.

if (nargin == 1 || nargin == 2) % always get the old time bas
    dTBaseOut = calllib('ceds64int', 'S64GetTimeBase', fhand);
else
    dTBaseOut = -22;
end

% has there been an error?
if dTBaseOut < 0
    return;
end

% if not set the new time bas if we're given one
if (nargin == 2)
    calllib('ceds64int', 'S64SetTimeBase', fhand, dTBaseIn);
end

end


classdef MexFunctionIDs < double
    %MexFunctionID Enumeration that provides ID number of desired function
    %  Copyright 2017 The MathWorks, Inc.

    enumeration
        OPEN_LIB (0)
        CLOSE_LIB (1)
        GET_JNT_POS (2)
        GET_JNT_VEL (3)
        GET_JNT_TORQUE (4)
        GET_JNT_TEMP (5)
        GET_EE_POSE (6)
        GET_EE_WRENCH (7)
        MOVE_TO_HOME (8)
        SET_POS_CTRL (9)
        SET_DIRECT_TORQUE_CTRL (10)
        RUN_GRAVITY_CALIB (11)
        INIT_FINGERS (12)
        SEND_JNT_POS (13)
        SEND_JNT_FING_POS (14)
        SEND_JNT_VEL (15)
        SEND_JNT_TORQUE (16)
        SEND_FINGER_POS (17)
        SEND_CART_POS (18)
        SEND_CART_VEL (19)
        GET_DOF (20)
        START_FORCE_CONTROL(21)
        STOP_FORCE_CONTROL(22)
        GET_EE_OFFSET(23)
        SET_EE_OFFSET(24)
        GET_PROTECT_ZONE(25)
        ERR_PROTECT_ZONES(26)
        SET_PROTECT_ZONE(27)
        GET_GLOB_TRAJECTORY (28)
    end

    methods
    end

end


function planC = deriveProstateCTVfromPTV(ptvStructNum, planC)
% function deriveProstateCTVfromPTV(ptvStructNum, planC)
%
% APA, 08/24/2012

if ~exist('planC','var')
    global planC
end

global stateS

indexS = planC{end};

scanNum = getStructureAssociatedScan(ptvStructNum,planC);

gtvStructNum = length(planC{indexS.structures}) + 1;

gtvStructS = newCERRStructure(scanNum, planC, gtvStructNum);

rasterSegments = [];

for slcNum = 1:length(planC{indexS.scan}(scanNum).scanInfo)

    % Calculate centroid for this slice
    [rasterSegs, planC] = getRasterSegments(ptvStructNum,planC,slcNum);

    if ~isempty(rasterSegs)

        meanY = mean(rasterSegs(:,2));

        %rasterSegmentsPosterior = rasterSegs(rasterSegs(:,2) <= meanY,:);
        %rasterSegmentsAnterior  = rasterSegs(rasterSegs(:,2) > meanY,:);

        rasterSegmentsShrinked_10_6 = structMargin(rasterSegs, [10 6]/10, scanNum, planC);
        rasterSegmentsShrinked_10_6 = structDiff(rasterSegs,rasterSegmentsShrinked_10_6, scanNum, planC);

        rasterSegmentsShrinked_10 = structMargin(rasterSegs, 10/10, scanNum, planC);
        rasterSegmentsShrinked_10 = structDiff(rasterSegs,rasterSegmentsShrinked_10, scanNum, planC);

        patientPosition = planC{indexS.scan}.scanInfo(1).DICOMHeaders.PatientPosition;
        if isequal(patientPosition,'HFP') || isequal(patientPosition,'FFP')
            rasterSegmentsAnterior = rasterSegmentsShrinked_10(rasterSegmentsShrinked_10(:,2) <= meanY, :);
            rasterSegmentsPosterior = rasterSegmentsShrinked_10_6(rasterSegmentsShrinked_10_6(:,2) > meanY, :);
        else
            rasterSegmentsPosterior = rasterSegmentsShrinked_10_6(rasterSegmentsShrinked_10_6(:,2) <= meanY, :);
            rasterSegmentsAnterior = rasterSegmentsShrinked_10(rasterSegmentsShrinked_10(:,2) > meanY, :);
        end

        rasterSegments = [rasterSegments; rasterSegmentsAnterior; rasterSegmentsPosterior];

    end

end

gtvStructS.rasterSegments = rasterSegments;
gtvStructS.rasterized = 1;

contourS = rasterToPoly(rasterSegments, scanNum, planC);

gtvStructS.contour = contourS;

gtvStructS.structureName = 'PROSTATE_CTV';

planC{indexS.structures} = dissimilarInsert(planC{indexS.structures}, gtvStructS, gtvStructNum);

planC = updateStructureMatrices(planC, gtvStructNum);

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


%SAVE2PDF Saves a figure as a properly cropped pdf
%
%   save2pdf(pdfFileName,handle,dpi)
%
%   - pdfFileName: Destination to write the pdf to.
%   - handle:  (optional) Handle of the figure to write to a pdf.  If
%              omitted, the current figure is used.  Note that handles
%              are typically the figure number.
%   - dpi: (optional) Integer value of dots per inch (DPI).  Sets
%          resolution of output pdf.  Note that 150 dpi is the Matlab
%          default and this function's default, but 600 dpi is typical for
%          production-quality.
%
%   Saves figure as a pdf with margins cropped to match the figure size.

%   (c) Gabe Hoffmann, gabe.hoffmann@gmail.com
%   Written 8/30/2007
%   Revised 9/22/2007
%   Revised 1/14/2007

function save2pdf(pdfFileName,handle,dpi)

% Verify correct number of arguments
error(nargchk(0,3,nargin));

% If no handle is provided, use the current figure as default
if nargin<1
    [fileName,pathName] = uiputfile('*.pdf','Save to PDF file:');
    if fileName == 0; return; end
    pdfFileName = [pathName,fileName];
end
if nargin<2
    handle = gcf;
end
if nargin<3
    dpi = 150;
end

% Backup previous settings
prePaperType = get(handle,'PaperType');
prePaperUnits = get(handle,'PaperUnits');
preUnits = get(handle,'Units');
prePaperPosition = get(handle,'PaperPosition');
prePaperSize = get(handle,'PaperSize');

% Make changing paper type possible
set(handle,'PaperType','<custom>');

% Set units to all be the same
set(handle,'PaperUnits','inches');
set(handle,'Units','inches');

% Set the page size and position to match the figure's dimensions
paperPosition = get(handle,'PaperPosition');
position = get(handle,'Position');
set(handle,'PaperPosition',[0,0,position(3:4)]);
set(handle,'PaperSize',position(3:4));

% Save the pdf (this is the same method used by "saveas")
print(handle,'-dpdf',pdfFileName,sprintf('-r%d',dpi))

% Restore the previous settings
set(handle,'PaperType',prePaperType);
set(handle,'PaperUnits',prePaperUnits);
set(handle,'Units',preUnits);
set(handle,'PaperPosition',prePaperPosition);
set(handle,'PaperSize',prePaperSize);


function test_suite = test_survival_coxph

%   Run specific demo, save values and compare the results to the expected.
%   Works for both xUnit Test Framework package by Steve Eddins and for
%   the built-in Unit Testing Framework (as of Matlab version 2013b).
%
%   See also
%     TEST_ALL, DEMO_SURVIVAL_COXPH
%
% Copyright (c) 2011-2012 Ville Tolvanen
% Copyright (c) 2014 Tuomas Sivula

% This software is distributed under the GNU General Public
% License (version 3 or later); please refer to the file
% License.txt, included with the software, for details.

  % Check which package to use
  if exist('initTestSuite', 'file')
    initTestSuite_path = which('initTestSuite');
  else
    initTestSuite_path = '';
  end
  if ~isempty(initTestSuite_path) && ...
     exist([initTestSuite_path(1:end-15) 'runtests'], 'file')
    % xUnit package
    initTestSuite;
  else
    % Built-in package
    % Use all functions except the @setup
    tests = localfunctions;
    tests = tests(~cellfun(@(x)strcmp(func2str(x),'setup'),tests));
    test_suite = functiontests(tests);
  end
end

% -------------
%     Tests
% -------------

function testRunDemo(testCase)
  % Run the correspondin demo and save the values. Note this test has to
  % be run at lest once before the other test may succeed.
  rundemo(getName())
end

function testPredictionsCoxph(testCase)
  verifyVarsEqual(testCase, getName(), {'Ef','Varf'}, ...
    'AbsTol', 0.1)
end

% ------------------------
%     Helper functions
% ------------------------

function testCase = setup
  % Helper function to suply empty array into variable testCase as an
  % argument for each test function, if using xUnit package. Not to be
  % used with built-in test framework.
  testCase = [];
end

function name = getName
  % Helperfunction that returns the name of the demo, e.g. 'binomial1'.
  name = mfilename;
  name = name(6:end);
end


%% Filter the best QRS detections according to the *m* quality criterion
% This is an auxiliar function for the ECGtask_QRS_detections_post_process.
% Lead selection strategy based on the best (quality metric) *m*.
% This is a lead selection algorithm, so this algorithm filters the
% first ranked lead of all the algorithms outputs included in the
% payload struct. In contrast with other postprocessing algorithm, this
% doesn't create a new detection serie, it is just a filter.
%
%   all_detections = best_m_lead(struct_in, ECG_header, start_end_this_segment)
%
% Arguments:
%
%      + struct_in: the structure which results from loading the result of
%                   the QRS detection task:
%
%                   cached_filenames = ECG_w.GetCahchedFileName('QRS_detection');
%                   struct_in = load(cached_filenames{1});
%
%      +ECG_header: [struct] OPTIONAL.
%
%             Description of the ECG typically available in the
%             ECG_header. Structure with fields:
%
%               -freq: Sampling rate in Hz. (1)
%
%               -nsig: Number of ECG leads. (size(ECG,2))
%
%               -nsamp: Number of ECG samples. (size(ECG,1))
%
%      + start_end_this_segment: an array with the first and last sample
%                                indexes.
%
% Output:
%
%      + all_detections: struct with the detections  of the best lead
%                        according to the *m* quality metric.
%
%
% Example
%
%     ECG_w.ECGtaskHandle = 'QRS_detections_post_process';
%     % Mix QRS detections strategy function
%     ECG_w.ECGtaskHandle.post_proc_func = 'calculate_artificial_QRS_detections';
%     ECG_w.ECGtaskHandle.payload = load(cached_filenames{1});
%     ECG_w.ECGtaskHandle.CalculatePerformance = true;
%     ECG_w.Run;
%
% See also ECGtask_QRS_detections_post_process, mixartif, wavedetmix, qrs_detection_and_correction
%
% Author: Mariano Llamedo Soria llamedom@electron.frba.utn.edu.ar
% Birthdate: 12/05/2018
% Last update: 12/05/2018
% Copyright 2008-2018
%
function all_detections = best_m_lead(struct_in, ECG_header, start_end_this_segment)

    all_detections = [];

    if( nargin < 3 || isempty(start_end_this_segment) )
        start_end_this_segment = [1 ECG_header.nsamp];
    end

    if( isfield(struct_in, 'series_quality') )
        % choose the best ranked automatic detection
        best_detections_idx = max_index(struct_in.series_quality.ratios);
        best_det_name = struct_in.series_quality.AnnNames{best_detections_idx,1};
        all_detections.(best_det_name) = struct_in.(best_det_name);

    else
        disp_string_framed(2, 'Could not identify QRS detections in the input payload.');
    end


function pos=read_pascal(cls,year,keypoints,segmentations)

% Read pascal to generate filtered instances of classes

globals;
dataset_fg = 'trainval';
conf.year=year;
conf.dev_kit=[BASE_DIR 'data/VOC2011/VOCdevkit'];
VOCopts    = get_voc_opts(conf);

% Setup keypoints and segmentations voc_id
kps_voc_id = cellfun(@(x,y) [x, '_', y], keypoints.voc_image_id, arrayfun(@num2str, keypoints.voc_rec_id, 'Uniform', false), 'UniformOutput', false);
segs_voc_id = cellfun(@(x,y) [x, '_', y], segmentations.voc_image_id, arrayfun(@num2str, segmentations.voc_rec_id, 'Uniform', false), 'UniformOutput', false);

  % Positive examples from the foreground dataset
  ids      = textread(sprintf(VOCopts.imgsetpath, dataset_fg), '%s');
  pos      = [];
  numpos   = 0;
  for i = 1:length(ids);
    if(mod(i,500)==0)
        fprintf('Parsed ids %d/%d\n',i,length(ids));
    end
    % Parse record and exclude difficult examples
    rec           = PASreadrecord(sprintf(VOCopts.annopath, ids{i}));
    clsinds       = strmatch(cls, {rec.objects(:).class}, 'exact');
    count         = length(clsinds(:));
    % Skip if there are no objects in this image
    if count == 0
      continue;
    end

    diff          = [rec.objects(clsinds).difficult];
    trunc         = [rec.objects(clsinds).truncated];
    if(~isempty(clsinds) && isfield(rec.objects(clsinds(1)),'occluded'))
        occl = [rec.objects(clsinds).occluded];
    else
        occl = true(size(clsinds));
    end

    clsinds2del = diff | trunc | occl; % Change this for different datasets
    %clsinds2del = diff;
    clsinds(clsinds2del)=[];

    count         = length(clsinds(:));
    % Skip if there are no objects in this image
    if count == 0
      continue;
    end

    % Create one entry per bounding box in the pos array
    for j = clsinds(:)'
      numpos = numpos + 1;
      j_voc_id = [rec.filename(1:end-4) '_' num2str(j)];
      ki = find(ismember(kps_voc_id,j_voc_id));
      si = find(ismember(segs_voc_id,j_voc_id));
      bbox   = rec.objects(j).bbox;
      pos(numpos).imsize = [rec.size.height rec.size.width];
      pos(numpos).voc_image_id = rec.filename(1:end-4);
      pos(numpos).voc_rec_id = j;
      %pos(numpos).im      = [VOCopts.datadir rec.imgname];
      pos(numpos).pascal_bbox   = [bbox(1:2) bbox(3)-bbox(1)+1 bbox(4)-bbox(2)+1]; %(x,y,w,h)
      pos(numpos).view    = rec.objects(j).view;
      pos(numpos).kps     = squeeze(keypoints.coords(ki,:,:));
      pos(numpos).part_names  = keypoints.labels;
      pos(numpos).bbox        = keypoints.bbox(ki,:);
      pos(numpos).poly_x      = segmentations.poly_x{si};
      pos(numpos).poly_y      = segmentations.poly_y{si};
      pos(numpos).class       = cls;
    end
  end
  pascal_data= pos;
  fname = [cachedir 'pascal_filt_data/pascal_' cls '_filt.mat'];
  save(fname,'pascal_data');
end

function VOCopts = get_voc_opts(conf)
% cache VOCopts from VOCinit
persistent voc_opts;

key = conf.year;
if isempty(voc_opts) || ~voc_opts.isKey(key)
  if isempty(voc_opts)
    voc_opts = containers.Map();
  end
  tmp = pwd;
  cd(conf.dev_kit);
  addpath([cd '/VOCcode']);
  VOCinit;
  cd(tmp);
  voc_opts(key) = VOCopts;
end
VOCopts = voc_opts(key);
end


%                                                y = makereal(x,K,cpx)
% MAKEREAL  Converts matrix in MATLAB-complex format to internal
%  SeDuMi format.
%
% ******************** INTERNAL FUNCTION OF SEDUMI ********************
%
% See also sedumi

function y = makereal(x,K,cpx) %#ok
%
% 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
%

disp('The SeDuMi binaries are not installed.')
disp('In Matlab, launch "install_sedumi" in the folder you put the SeDuMi files.')
disp('For more information see the file Install.txt.')
error(' ')


function img = plimage2double( plimg )
%PLIMAGE2DOUBLE Summary of this function goes here
%   Detailed explanation goes here
img = reshape(plimg.toDouble(), [plimg.mRow, plimg.mCol, plimg.mLen]);

end


function [matchIndsReal, matchIndsEst, meanDist] = computeCentroidMatches(realCellParams, estCellParams, varargin)
    % Matches real and estimates cells based on centroid location.
    % Lacey Kitsch & Biafra Ahanonu
    % started: updating 2016.04.24
    % inputs
        %
    % outputs
        %

    [matchIndsReal, matchIndsEst, meanDist] = ciapkg.image.computeCentroidMatches(realCellParams, estCellParams,'passArgs', varargin);
end


function x = sq(x, varargin)

if numel(varargin)>0
    idims = varargin{1};
    xsiz = size(x);
    idims = idims(xsiz(idims)==1);
    xsiz(idims) = [];
    x = reshape(x, xsiz);
else
    x = squeeze(x);
end


function [obs,nobs]=scan_obs_ppk(rtk,obsr)

global gls
nobs0=size(obsr,1); nobs=0; opt=rtk.opt; SNRMASK=32; %#ok
obs=repmat(gls.obs_tmp,nobs0,1);

for i=1:nobs0
    if obsr(i).S(1)~=0&&obsr(i).S(1)/4.0<SNRMASK
        continue;
    end

    obs(nobs+1)=obsr(i);
    nobs=nobs+1;
end

if nobs==0,obs=NaN;return;end
if nobs<nobs0
    obs(nobs+1:end,:)=[];
end

return


function varargout = min(varargin)
% Converts first or second inputs to double if they are char
if nargin>=1 && ischar(varargin{1})
    varargin{1} = double(varargin{1});
end
if nargin>=2 && ischar(varargin{2})
    varargin{2} = double(varargin{2});
end
if nargout==0
    nargout = 1; % if called without outputs: produce 1 output
end
varargout = cell(1,nargout);
if ~any(cellfun(@(x) isa(x,'sym'), varargin))
    [varargout{:}] = builtin('min', varargin{:});
else
    [varargout{:}] = builtin('@sym/min', varargin{:});
end
end


function resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,w,nHistories)
% matRad function to bypass dij calculation for MC dose calculation
% matRad dose calculation wrapper for MC dose calculation algorithms
% bypassing dij calculation for MC dose calculation algorithms.
%
% call
%   resultGUI = matRad_calcDoseDirecMC(ct,stf,pln,cst)
%   resultGUI = matRad_calcDoseDirecMC(ct,stf,pln,cst,w)
%   resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,nHistories)
%   resultGUI = matRad_calcDoseDirectMC(ct,stf,pln,cst,w,nHistories)
%
% input
%   ct:         ct cube
%   stf:        matRad steering information struct
%   pln:        matRad plan meta information struct
%   cst:        matRad cst struct
%   w:          (optional, if no weights available in stf): bixel weight
%               vector
%   nHistories: (optional) number of histories
%
% output
%   resultGUI:  matRad result struct
%
% References
%   -
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Copyright 2019 the matRad development team.
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

matRad_cfg =  MatRad_Config.instance();

calcDoseDirect = true;

if nargin < 6 || ~exist('nHistories')
  nHistories = matRad_cfg.propMC.direct_defaultHistories;
  matRad_cfg.dispInfo('Using default number of Histories: %d\n',nHistories);
end

% check if weight vector is available, either in function call or in stf - otherwise dose calculation not possible
if ~exist('w','var') && ~isfield([stf.ray],'weight')
     matRad_cfg.dispError('No weight vector available. Please provide w or add info to stf');
end

% copy bixel weight vector into stf struct
if exist('w','var')
    if sum([stf.totalNumOfBixels]) ~= numel(w)
        matRad_cfg.dispError('weighting does not match steering information');
    end
    counter = 0;
    for i = 1:size(stf,2)
        for j = 1:stf(i).numOfRays
            for k = 1:stf(i).numOfBixelsPerRay(j)
                counter = counter + 1;
                stf(i).ray(j).weight(k) = w(counter);
            end
        end
    end
else % weights need to be in stf!
    w = NaN*ones(sum([stf.totalNumOfBixels]),1);
    counter = 0;
    for i = 1:size(stf,2)
        for j = 1:stf(i).numOfRays
            for k = 1:stf(i).numOfBixelsPerRay(j)
                counter = counter + 1;
                w(counter) = stf(i).ray(j).weight(k);
            end
        end
    end
end

% dose calculation
if strcmp(pln.radiationMode,'protons')
  dij = matRad_calcParticleDoseMC(ct,stf,pln,cst,nHistories,calcDoseDirect);
else
    matRad_cfg.dispError('Forward MC only implemented for protons.');
end

% hack dij struct
dij.numOfBeams = 1;
dij.beamNum = 1;

% calculate cubes; use uniform weights here, weighting with actual fluence
% already performed in dij construction
resultGUI    = matRad_calcCubes(sum(w),dij);

% remember original fluence weights
resultGUI.w  = w;


function varargout = process_fix_headcoils(varargin)
% PROCESS_FIX_HEADCOILS: Estimate the position of a missing or bad head localization coil.

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

eval(macro_method);
end

function sProcess = GetDescription() %#ok<DEFNU>
    % Description of the process
    sProcess.Comment     = 'Fix bad head coil (CTF)';
    sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/HeadMotion#Dealing_with_a_missing_or_bad_head_coil';
    sProcess.Category    = 'Custom';
    sProcess.SubGroup    = {'Import', 'Channel file'};
    sProcess.Index       = 54;
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'raw', 'data'};
    sProcess.OutputTypes = {'raw', 'data'};
    sProcess.nInputs     = 2;
    sProcess.nOutputs    = 1;
    sProcess.nMinFiles   = 1;
    sProcess.isSeparator = 0;
    sProcess.isPaired    = 1;
    sProcess.FileTag     = 'fix';

    % Options
    % === Sensor types
    sProcess.options.sensortypes.Comment = 'Sensor types or names (empty=all): ';
    sProcess.options.sensortypes.Type    = 'text';
    sProcess.options.sensortypes.Value   = {'HLU'};
    sProcess.options.sensortypes.Hidden  = 1;
    %     sProcess.options.read_all.Comment    = ''; % Automatically added to filter-type processes.
    %     sProcess.options.read_all.Type       = 'checkbox';
    %     sProcess.options.read_all.Value      = 1;
    %     sProcess.options.read_all.Hidden     = 1;

    sProcess.processDim = 2; % Only split by column blocks, if needed.

    sProcess.options.BadCoil.Type    = 'radio_line';
    sProcess.options.BadCoil.Comment = {'Nasion', 'Left ear', 'Right ear', 'Bad coil: '};
    sProcess.options.BadCoil.Value   = 1;
    %     sProcess.options.label1.Type    = 'label';
    %     sProcess.options.label1.Comment = 'How will you provide a "good and similar" head position?';
    %     sProcess.options.GoodSource.Type    = 'radio';
    %     sProcess.options.GoodSource.Comment = {'Recording already in Brainstorm', 'Raw recording file', 'Manual entry of dewar coordinates'};
    %     sProcess.options.GoodSource.Value   = 1;
    %     sProcess.options.display.Type    = 'checkbox';
    %     sProcess.options.display.Comment = 'Display "before" and "after" alignment figures.';
    %     sProcess.options.display.Value   = 0;

end

function Comment = FormatComment(sProcess)
    Comment = [sProcess.Comment, ': ', ...
        sProcess.options.BadCoil.Comment{sProcess.options.BadCoil.Value}];

end

function OutputFiles = Run(sProcess, sInputA, sInputB)

    % If called from ProcessFilter
    if isfield(sProcess, 'FromProcessFilter') && sProcess.FromProcessFilter
            OutputFiles = [];
            ChannelMat = in_bst_channel(sInputA.ChannelFile);

           % Check the input is CTF.
            DataMat = in_bst_data(sInputA.FileName, 'Device');
            if ~strcmp(DataMat.Device, 'CTF')
                bst_report('Error', sProcess, sInputA, ...
                    'Fix bad head coil is currently only available for CTF data.');
                return;
            end

            % Difficult to find the initial head sample in each epoch. And
            % if wrong, fixed HLU channel will have offset "steps". So
            % process redundant samples, which we would need to recreate
            % anyway.
            %             % Load head coil locations, in m.
            %             bst_progress('text', 'Loading head coil locations...');
            %             [Locations, HeadSamplePeriod] = process_evt_head_motion('LoadHLU', sInputA, [], false); % [nChannels, nSamples, nEpochs]
            %             if isempty(Locations)
            %                 % No HLU channels. Error already reported. Skip this file.
            %                 return;
            %             end
            bst_progress('text', 'Correcting head position...');
            % Sort in case coils were swapped.
            [Unused, iSortHlu] = sort({ChannelMat.Channel(strcmp({ChannelMat.Channel.Type}, 'HLU')).Name});

            % Compute
            GoodAxis = sInputA.A(iSortHlu(sProcess.Ext.iGood(1:3)), :, :) - sInputA.A(iSortHlu(sProcess.Ext.iGood(4:6)), :, :); %[3, nS, nE]
            GoodAxis = bsxfun(@rdivide, GoodAxis, sqrt(sum(GoodAxis.^2, 1)));
            FixedCoil = (sInputA.A(iSortHlu(sProcess.Ext.iGood(4:6)), :, :) + sInputA.A(iSortHlu(sProcess.Ext.iGood(1:3)), :, :)) ./ 2 + ...
                GoodAxis * sProcess.Ext.BadAlongGoodAxis + bsxfun(@cross, GoodAxis, sProcess.Ext.Normal) * sProcess.Ext.BadFromGoodAxis;
            % If a collection was aborted, the channels will be filled with
            % zeros. The "fixed" data will contain NaN, change them back to 0.
            FixedCoil(isnan(FixedCoil)) = 0;

            %             % Re-upsample (duplicate) and save new data.
            %             [n3, nHeadSamples, nEpochs] = size(FixedCoil);
            %             FixedCoil = reshape(permute(FixedCoil, [2, 3, 1]), [], 1);
            %             FixedCoil = permute(reshape(FixedCoil(:, ones(1,HeadSamplePeriod))', [], nEpochs, n3), [3, 1, 2]);

            %             [nChan, nSamples, nEpochs] = size(sInputA.A);
            %             sInputA.A(iSortHlu(sProcess.Ext.iBad), :, :) = FixedCoil(:, 1:nSamples, 1:nEpochs);
            sInputA.A(iSortHlu(sProcess.Ext.iBad), :, :) = FixedCoil;
            OutputFiles = sInputA;

    % Initial call from pipeline
    else
        OutputFiles = {sInputA.FileName};
        % Flag to indicate Run will be called by ProcessFilter.
        sProcess.FromProcessFilter = 1;

        % Bad coil
        sProcess.Ext.iBad = (1:3) + (sProcess.options.BadCoil.Value - 1) * 3;
        % Good coils
        sProcess.Ext.iGood = setdiff(1:9, sProcess.Ext.iBad);

        bst_progress('start', 'Fix bad head coil', ' ', 0, numel(sInputA));

        % Process each input file pair.
        for iFile = 1:length(sInputA)
            % [Possibly to add: options to load from external file or prompt for manual entry, showing current (bad) dewar coordinates.]
            % Get good head coil Dewar coordinates from second channel file.
            ChannelMatB = in_bst_channel(sInputB(iFile).ChannelFile);
            sProcess.Ext.RefLoc = process_adjust_coordinates('ReferenceHeadLocation', ChannelMatB);
            % Sort in case coils were swapped.
            [Unused, iSortHlu] = sort({ChannelMatB.Channel(strcmp({ChannelMatB.Channel.Type}, 'HLU')).Name});

            % Get relative coordinates of the bad coil from the external source.
            sProcess.Ext.CoilA = sProcess.Ext.RefLoc(iSortHlu(sProcess.Ext.iGood(1:3)));
            sProcess.Ext.CoilB = sProcess.Ext.RefLoc(iSortHlu(sProcess.Ext.iGood(4:6)));
            sProcess.Ext.Orig = (sProcess.Ext.CoilA + sProcess.Ext.CoilB) / 2;
            sProcess.Ext.GoodAxis = sProcess.Ext.CoilA - sProcess.Ext.CoilB;
            sProcess.Ext.GoodAxis = sProcess.Ext.GoodAxis ./ norm(sProcess.Ext.GoodAxis);
            sProcess.Ext.BadFromO = sProcess.Ext.RefLoc(iSortHlu(sProcess.Ext.iBad)) - sProcess.Ext.Orig;
            sProcess.Ext.BadAlongGoodAxis = sProcess.Ext.BadFromO' * sProcess.Ext.GoodAxis;
            sProcess.Ext.BadFromGoodAxis = norm(sProcess.Ext.BadFromO - sProcess.Ext.BadAlongGoodAxis * sProcess.Ext.GoodAxis);
            sProcess.Ext.Normal = cross(sProcess.Ext.BadFromO, sProcess.Ext.GoodAxis);
            sProcess.Ext.Normal = sProcess.Ext.Normal ./ norm(sProcess.Ext.Normal);
            % Capture process crashes
            try
                OutputFiles{iFile} = bst_process('ProcessFilter', sProcess, sInputA(iFile));
                bst_progress('inc', 1);
            catch
                strError = bst_error();
                bst_report('Error', sProcess, [sInputA(iFile), sInputB(iFile)], strError);
                continue;
            end

        end
        bst_progress('stop');

        % Attempt to adjust the initial/reference head position.
        %             ChannelMat = in_bst_channel(sInputA(iFile).ChannelFile);
        %             sOutput = in_bst_data(
        OutputFiles = bst_process('CallProcess', 'process_adjust_coordinates', OutputFiles, [], ...
            'reset', 0, 'head', 1, 'bad', 1, 'points', 0, 'remove', 0, 'display', 1);
        %             [ChannelMat, Failed] = process_adjust_coordinates('AdjustHeadPosition', ...
        %                 ChannelMat, OutputFiles(iFile), sProcess);
        %             if ~Failed
        %                 % Save channel file.
        %                 bst_save(file_fullpath(OutputFiles(iFile).ChannelFile), ChannelMat, 'v7');
        %                 % Show new alignment.
        %                 channel_align_manual(OutputFiles(iFile).ChannelFile, 'MEG', 0);
        %             % else already noted in report.
        %             end
    end % "called from" if
end


%SHOW	show all parameters of LINK object
%
%	SHOW(link)

% Ryan Steindl based on Robotics Toolbox for MATLAB (v6 and v9)
%
% Copyright (C) 1993-2011, by Peter I. Corke
%
% This file is part of The Robotics Toolbox for MATLAB (RTB).
%
% RTB is free software: you can redistribute it and/or modify
% it under the terms of the GNU Lesser General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% RTB is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU Lesser General Public License for more details.
%
% You should have received a copy of the GNU Leser General Public License
% along with RTB.  If not, see <http://www.gnu.org/licenses/>.
%
% http://www.petercorke.com

function show(l)

	llab = 6;
	for n =fieldnames(l)'
		v = getfield(l, char(n));
		name = char(n);
		spaces = char(' '*ones(1,llab-length(name)));
		val = num2str(v);
		label = [name spaces ' = '];
		if numrows(val) > 1,
			pad = {label; char(' '*ones(numrows(val)-1,1))};
		else
			pad = label;
		end
		disp([char(pad) val]);
	end


function y = cvx_isaffine( x, full ) %#ok
narginchk(1,2);
if nargin == 1,
    y = true;
else
    y = true( size( x ) );
end

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


function ensure(condition)

%   ensure.m
%   $Id: ensure.m 800 2008-02-26 22:32:04Z mpf $
%
%   ----------------------------------------------------------------------
%   This file is part of SPGL1 (Spectral Projected Gradient for L1).
%
%   Copyright (C) 2007 Ewout van den Berg and Michael P. Friedlander,
%   Department of Computer Science, University of British Columbia, Canada.
%   All rights reserved. E-mail: <{ewout78,mpf}@cs.ubc.ca>.
%
%   SPGL1 is free software; you can redistribute it and/or modify it
%   under the terms of the GNU Lesser General Public License as
%   published by the Free Software Foundation; either version 2.1 of the
%   License, or (at your option) any later version.
%
%   SPGL1 is distributed in the hope that it will be useful, but WITHOUT
%   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
%   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
%   Public License for more details.
%
%   You should have received a copy of the GNU Lesser General Public
%   License along with SPGL1; if not, write to the Free Software
%   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
%   USA
%   ----------------------------------------------------------------------

   if ~condition
      [ST,I] = dbstack;
      msg = 'Assertion failed';
      if length(ST) > 1
         msg = sprintf('%s in %s; function %s line %d', ...
                       msg, ST(2).file, ST(2).name, ST(2).line);
      end

      errstr.message = msg;
      error(errstr);
   end

end % function ensure


%> @brief struct_overlay Overlay default fields with input fields
%> @author Eric Cousineau <eacousineau@gmail.com>
%> @note From optOverlay() on Mathworks, modified
function [opts] = struct_overlay(opts_default, opts_in, options)

    % Simple cases
    if iscell(opts_default)
        opts_default = struct(opts_default{:});
    end
    if isempty(opts_in)
        opts = opts_default;
        return;
    end
    if iscell(opts_in)
        opts_in = struct(opts_in{:});
    end

	is_valid = @(o) isstruct(o) && length(o) == 1;
	assert(is_valid(opts_default), 'opts_default must be scalar struct or structable cell array');
	assert(is_valid(opts_in), 'opts_in must be scalar struct or structable cell array');

    persistent func_opts_default;
    if isempty(func_opts_default)
        func_opts_default = struct('Recursive', true, 'AllowNew', false);
    end
    if nargin < 3
        options = func_opts_default;
    else
        if iscell(options)
            options = struct(options{:});
        end
        options = struct_overlay(func_opts_default, options);
    end

    %TODO Tackle struct arrays?
    new_fields = fieldnames(opts_in);
    options_index = find(strcmp(new_fields, 'overlay_options'));
    indices = 1:length(new_fields);
    if ~isempty(options_index)
        options = struct_overlay(options, opts_in.overlay_options);
        indices(options_index) = [];
    end

    if ~options.AllowNew
        % Check to see if there are bad fields
        fields = fieldnames(opts_default);
        unoriginal = setdiff(new_fields(indices), fields);
        if ~isempty(unoriginal)
            error(['Fields are not in original: ', implode(unoriginal, ', ')]);
        end
    end

    opts = opts_default;

    for i = 1:length(indices)
        index = indices(i);
        field = new_fields{index};
        new_value = opts_in.(field);
        % Apply recursion
        if options.Recursive && isfield(opts, field)
            cur_value = opts.(field);
            % Both values must be proper option structs
            if is_valid(cur_value) && is_valid(new_value)
                new_value = struct_overlay(cur_value, new_value, options);
            end
        end
        opts.(field) = new_value;
    end
end


function varargout = process_generate_head( varargin )
% PROCESS_GENERATE_HEAD: Generate s head surface from an MRI.

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

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
    % Description the process
    sProcess.Comment     = 'Generate head surface';
    sProcess.Category    = 'Custom';
    sProcess.SubGroup    = {'Import', 'Import anatomy'};
    sProcess.Index       = 20;
    sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/LabelFreeSurfer#The_head_surface_looks_bad';
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'import'};
    sProcess.OutputTypes = {'import'};
    sProcess.nInputs     = 1;
    sProcess.nMinFiles   = 0;
    % Option: Subject name
    sProcess.options.subjectname.Comment = 'Subject name:';
    sProcess.options.subjectname.Type    = 'subjectname';
    sProcess.options.subjectname.Value   = 'NewSubject';
    % Option: Number of vertices
    sProcess.options.nvertices.Comment = 'Number of vertices [integer]: ';
    sProcess.options.nvertices.Type    = 'value';
    sProcess.options.nvertices.Value   = {10000, '', 0};
    % Option: Erode factor
    sProcess.options.erodefactor.Comment = 'Erode factor [0,1,2,3]: ';
    sProcess.options.erodefactor.Type    = 'value';
    sProcess.options.erodefactor.Value   = {0, '', 0};
    % Option: Fill factor
    sProcess.options.fillfactor.Comment = 'Fill holes factor [0,1,2,3]: ';
    sProcess.options.fillfactor.Type    = 'value';
    sProcess.options.fillfactor.Value   = {2, '', 0};
end

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

%% ===== RUN =====
function OutputFiles = Run(sProcess, sInputs) %#ok<DEFNU>
    OutputFiles = {};

    % ===== GET OPTIONS =====
    % Get subject name
    SubjectName = file_standardize(sProcess.options.subjectname.Value);
    if isempty(SubjectName)
        bst_report('Error', sProcess, [], 'Subject name is empty.');
        return;
    end
    % Number of vertices
    nVertices   = sProcess.options.nvertices.Value{1};
    erodeFactor = sProcess.options.erodefactor.Value{1};
    fillFactor  = sProcess.options.fillfactor.Value{1};
    if isempty(nVertices) || isempty(erodeFactor) || isempty(fillFactor)
        bst_report('Error', sProcess, [], 'Invalid values.');
        return;
    end

    % ===== GET SUBJECT =====
    % Get subject
    [sSubject, iSubject] = bst_get('Subject', SubjectName);
    if isempty(iSubject)
        bst_report('Error', sProcess, [], ['Subject "' SubjectName '" does not exist.']);
        return
    end
    % Check if a MRI is available for the subject
    if isempty(sSubject.Anatomy)
        bst_report('Error', sProcess, [], ['No MRI available for subject "' SubjectName '".']);
        return
    end

    % ===== GENERATE HEAD =====
    % Generate head surface
    HeadFile = tess_isohead(iSubject, nVertices, erodeFactor, fillFactor);
    % Error handling
    if isempty(HeadFile)
        bst_report('Error', sProcess, [], 'An error occurred in tess_isohead function.');
        return;
    end

    OutputFiles = {'import'};
end