function [input] = ft_apply_montage(input, montage, varargin)

% FT_APPLY_MONTAGE changes the montage (i.e. linear combination) of a set of
% electrode or gradiometer channels. A montage can be used for EEG rereferencing, MEG
% synthetic gradients, MEG planar gradients or unmixing using ICA. This function not
% only applies the montage to the EEG or MEG data, but also applies the montage to
% the input EEG or MEG sensor array, which can subsequently be used for forward
% computation and source reconstruction of the data.
%
% Use as
%   [sens]    = ft_apply_montage(sens,     montage,  ...)
%   [data]    = ft_apply_montage(data,     montage,  ...)
%   [freq]    = ft_apply_montage(freq,     montage,  ...)
%   [montage] = ft_apply_montage(montage1, montage2, ...)
%
% A montage is specified as a structure with the fields
%   montage.tra      = MxN matrix
%   montage.labelold = Nx1 cell-array
%   montage.labelnew = Mx1 cell-array
%
% As an example, a bipolar montage could look like this
%   bipolar.labelold  = {'1',   '2',   '3',   '4'}
%   bipolar.labelnew  = {'1-2', '2-3', '3-4'}
%   bipolar.tra       = [
%     +1 -1  0  0
%      0 +1 -1  0
%      0  0 +1 -1
%   ];
%
% The montage can optionally also specify the channel type and unit of the input
% and output data with
%   montage.chantypeold = Nx1 cell-array
%   montage.chantypenew = Mx1 cell-array
%   montage.chanunitold = Nx1 cell-array
%   montage.chanunitnew = Mx1 cell-array
%
% Additional options should be specified in key-value pairs and can be
%   'keepunused'    = string, 'yes' or 'no' (default = 'no')
%   'inverse'       = string, 'yes' or 'no' (default = 'no')
%   'balancename'   = string, name of the montage (default = '')
%   'feedback'      = string, see FT_PROGRESS (default = 'text')
%   'warning'       = boolean, whether to show warnings (default = true)
%
% If the first input is a montage, then the second input montage will be
% applied to the first. In effect, the output montage will first do
% montage1, then montage2.
%
% See also FT_READ_SENS, FT_DATATYPE_SENS

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

if iscell(input)
  % this represents combined EEG, ECoG and/or MEG
  for i=1:numel(input)
    input{i} = ft_apply_montage(input{i}, montage, varargin{:});
  end
  return
end

% use "old/new" instead of "org/new"
montage = fixoldorg(montage);
input   = fixoldorg(input); % the input might be a montage or a sensor array

% get optional input arguments
keepunused    = ft_getopt(varargin, 'keepunused',  'no');
inverse       = ft_getopt(varargin, 'inverse',     'no');
feedback      = ft_getopt(varargin, 'feedback',    'text');
showwarning   = ft_getopt(varargin, 'warning',     true);
bname         = ft_getopt(varargin, 'balancename', '');

if istrue(showwarning)
  warningfun = @warning;
else
  warningfun = @nowarning;
end

% ensure that the input montage is correct, see https://github.com/fieldtrip/fieldtrip/issues/1718
assert(length(unique(montage.labelold))==length(montage.labelold), 'the montage is invalid');
assert(length(unique(montage.labelnew))==length(montage.labelnew), 'the montage is invalid');

% these are optional, at the end we will clean up the output in case they did not exist
if ~istrue(inverse)
  haschantype = (isfield(input, 'chantype') || isfield(input, 'chantypenew')) && all(isfield(montage, {'chantypeold', 'chantypenew'}));
  haschanunit = (isfield(input, 'chanunit') || isfield(input, 'chanunitnew')) && all(isfield(montage, {'chanunitold', 'chanunitnew'}));
else
  haschantype = (isfield(input, 'chantype') || isfield(input, 'chantypeold')) && all(isfield(montage, {'chantypeold', 'chantypenew'}));
  haschanunit = (isfield(input, 'chanunit') || isfield(input, 'chanunitold')) && all(isfield(montage, {'chanunitold', 'chanunitnew'}));
end

% make sure they always exist to facilitate the remainder of the code
if ~isfield(montage, 'chantypeold')
  montage.chantypeold = repmat({'unknown'}, size(montage.labelold));
  if isfield(input, 'chantype') && ~istrue(inverse)
    ft_warning('copying input chantype to montage');
    [sel1, sel2] = match_str(montage.labelold, input.label);
    montage.chantypeold(sel1) = input.chantype(sel2);
  end
end

if ~isfield(montage, 'chantypenew')
  montage.chantypenew = repmat({'unknown'}, size(montage.labelnew));
  if isfield(input, 'chantype') && istrue(inverse)
    ft_warning('copying input chantype to montage');
    [sel1, sel2] = match_str(montage.labelnew, input.label);
    montage.chantypenew(sel1) = input.chantype(sel2);
  end
end

if ~isfield(montage, 'chanunitold')
  montage.chanunitold = repmat({'unknown'}, size(montage.labelold));
  if isfield(input, 'chanunit') && ~istrue(inverse)
    ft_warning('copying input chanunit to montage');
    [sel1, sel2] = match_str(montage.labelold, input.label);
    montage.chanunitold(sel1) = input.chanunit(sel2);
  end
end

if ~isfield(montage, 'chanunitnew')
  montage.chanunitnew = repmat({'unknown'}, size(montage.labelnew));
  if isfield(input, 'chanunit') && istrue(inverse)
    ft_warning('copying input chanunit to montage');
    [sel1, sel2] = match_str(montage.labelnew, input.label);
    montage.chanunitnew(sel1) = input.chanunit(sel2);
  end
end

if ~isfield(input, 'label') && isfield(input, 'labelnew')
  % the input data structure is also a montage
  inputlabel = input.labelnew;
  if isfield(input, 'chantypenew')
    inputchantype = input.chantypenew;
  else
    inputchantype = repmat({'unknown'}, size(input.labelnew));
  end
  if isfield(input, 'chanunitnew')
    inputchanunit = input.chanunitnew;
  else
    inputchanunit = repmat({'unknown'}, size(input.labelnew));
  end
else
  % the input should describe the channel labels, and optionally the type and unit
  inputlabel = input.label;
  if isfield(input, 'chantype')
    inputchantype = input.chantype;
  else
    inputchantype = repmat({'unknown'}, size(input.label));
  end
  if isfield(input, 'chanunit')
    inputchanunit = input.chanunit;
  else
    inputchanunit = repmat({'unknown'}, size(input.label));
  end
end

% check the consistency of the montage
if ~iscell(montage.labelold) || ~iscell(montage.labelnew)
  ft_error('montage labels need to be specified in cell-arrays');
end

% check the consistency of the montage
if ~all(isfield(montage, {'tra', 'labelold', 'labelnew'}))
  ft_error('the second input argument does not correspond to a montage');
end

% check the consistency of the montage
if size(montage.tra,1)~=length(montage.labelnew)
  ft_error('the number of channels in the montage is inconsistent');
elseif size(montage.tra,2)~=length(montage.labelold)
  ft_error('the number of channels in the montage is inconsistent');
end

% use a default unit transfer from sensors to channels if not otherwise specified
if ~isfield(input, 'tra') && isfield(input, 'label')
  if     isfield(input, 'elecpos') && length(input.label)==size(input.elecpos, 1)
    nchan = length(input.label);
    input.tra = eye(nchan);
  elseif isfield(input, 'coilpos') && length(input.label)==size(input.coilpos, 1)
    nchan = length(input.label);
    input.tra = eye(nchan);
  elseif isfield(input, 'chanpos') && length(input.label)==size(input.chanpos, 1)
    nchan = length(input.label);
    input.tra = eye(nchan);
  end
end

if istrue(inverse)
  % swap the role of the old and new channels
  tmp.labelnew    = montage.labelold;
  tmp.labelold    = montage.labelnew;
  tmp.chantypenew = montage.chantypeold;
  tmp.chantypeold = montage.chantypenew;
  tmp.chanunitnew = montage.chanunitold;
  tmp.chanunitold = montage.chanunitnew;
  % apply the inverse montage, this can be used to undo a previously applied montage
  tmp.tra = full(montage.tra);
  if rank(tmp.tra) < length(tmp.tra)
    warningfun('the linear projection for the montage is not full-rank, the resulting data will have reduced dimensionality');
    tmp.tra = pinv(tmp.tra);
  else
    tmp.tra = inv(tmp.tra);
  end
  montage = tmp;
end

% keep only the columns that are not empty
selcol = ~all(montage.tra==0, 1);
if istrue(keepunused)
  for i=find(selcol==false)
    % don't remove the column if it corresponds to one of the output channels
    selcol(i) = any(strcmp(montage.labelnew, montage.labelold{i}));
  end
end
montage.tra         = montage.tra(:,selcol);
montage.labelold    = montage.labelold(selcol);
montage.chantypeold = montage.chantypeold(selcol);
montage.chanunitold = montage.chanunitold(selcol);
clear selcol

% keep only the channels that are present in the input data
remove = setdiff(montage.labelold, intersect(montage.labelold, inputlabel));
selcol = match_str(montage.labelold, remove);
% we cannot just remove the colums, all rows that depend on it should also be removed
selrow = false(length(montage.labelnew),1);
for i=1:length(selcol)
  selrow = selrow & (montage.tra(:,selcol(i))~=0);
end
% convert from indices to logical vector
selcol = indx2logical(selcol, length(montage.labelold));
% remove rows and columns
montage.labelold    = montage.labelold(~selcol);
montage.labelnew    = montage.labelnew(~selrow);
montage.chantypeold = montage.chantypeold(~selcol);
montage.chantypenew = montage.chantypenew(~selrow);
montage.chanunitold = montage.chanunitold(~selcol);
montage.chanunitnew = montage.chanunitnew(~selrow);
montage.tra         = montage.tra(~selrow, ~selcol);
clear remove selcol selrow

% add columns for channels present in the input data but that are not specified in the montage, stick to the original order in the data
[dum, ix]   = setdiff(inputlabel, montage.labelold);
addlabel    = inputlabel(sort(ix));
addchantype = inputchantype(sort(ix));
addchanunit = inputchanunit(sort(ix));
m = size(montage.tra,1);
n = size(montage.tra,2);
k = length(addlabel);

% % check for NaNs in unused channels; these will be mixed in with the rest
% % of the channels and result in NaNs in the output even when multiplied
% % with zeros or identity
% if k > 0 && isfield(input, 'trial') % check for raw data now only
%   cfg = [];
%   cfg.channel = addlabel;
%   cfg.showcallinfo = showcallinfo;
%   data_unused = ft_selectdata(cfg, input);
%   % use an anonymous function to test for the presence of NaNs in the input data
%   hasnan = @(x) any(isnan(x(:)));
%   if any(cellfun(hasnan, data_unused.trial))
%     ft_error('FieldTrip:NaNsinInputData', ['Your input data contains NaNs in channels that are unused '...
%       'in the supplied montage. This would result in undesired NaNs in the '...
%       'output data. Please remove these channels from the input data (using '...
%       'ft_selectdata) before attempting to apply the montage.']);
%   end
% end

if istrue(keepunused)
  % add the channels that are not rereferenced to the input and output of the montage
  montage.tra((m+(1:k)),(n+(1:k))) = eye(k);
  montage.labelold    = cat(1, montage.labelold(:), addlabel(:));
  montage.labelnew    = cat(1, montage.labelnew(:), addlabel(:));
  montage.chantypeold = cat(1, montage.chantypeold(:), addchantype(:));
  montage.chantypenew = cat(1, montage.chantypenew(:), addchantype(:));
  montage.chanunitold = cat(1, montage.chanunitold(:), addchanunit(:));
  montage.chanunitnew = cat(1, montage.chanunitnew(:), addchanunit(:));
else
  % add the channels that are not rereferenced to the input of the montage only
  montage.tra(:,(n+(1:k))) = zeros(m,k);
  montage.labelold    = cat(1, montage.labelold(:), addlabel(:));
  montage.chantypeold = cat(1, montage.chantypeold(:), addchantype(:));
  montage.chanunitold = cat(1, montage.chanunitold(:), addchanunit(:));
end
clear addlabel addchantype addchanunit m n k

% determine whether all channels are unique
m = size(montage.tra,1);
n = size(montage.tra,2);
if length(unique(montage.labelnew))~=m
  ft_error('not all output channels of the montage are unique');
end
if length(unique(montage.labelold))~=n
  ft_error('not all input channels of the montage are unique');
end

% determine whether all channels that have to be rereferenced are available
if length(intersect(inputlabel, montage.labelold))~=length(montage.labelold)
  ft_error('not all channels that are required in the montage are available in the data');
end

% reorder the columns of the montage matrix
[selinput, selmontage] = match_str(inputlabel, montage.labelold);
montage.tra            = montage.tra(:,selmontage);
montage.labelold       = montage.labelold(selmontage);
montage.chantypeold    = montage.chantypeold(selmontage);
montage.chanunitold    = montage.chanunitold(selmontage);

% ensure that the montage is double precision
montage.tra = double(montage.tra);

% making the tra matrix sparse will speed up subsequent multiplications, but should not result in sparse output data
% this only makes sense for matrices with a lot of zero elements; for dense matrices it is faster to keep it full
if size(montage.tra,1)>1 && nnz(montage.tra)/numel(montage.tra) < 0.3
  montage.tra = sparse(montage.tra);
else
  montage.tra = full(montage.tra);
end

% update the channel scaling if the input has different units than the montage expects
if isfield(input, 'chanunit') && ~isequal(input.chanunit, montage.chanunitold)
  scale = ft_scalingfactor(input.chanunit, montage.chanunitold);
  montage.tra = montage.tra * diag(scale);
  montage.chanunitold = input.chanunit;
elseif isfield(input, 'chanunitnew') && ~isequal(input.chanunitnew, montage.chanunitold)
  scale = ft_scalingfactor(input.chanunitnew, montage.chanunitold);
  montage.tra = montage.tra * diag(scale);
  montage.chanunitold = input.chanunitnew;
end

if isfield(input, 'chantype') && ~isequal(input.chantype, montage.chantypeold)
  ft_error('inconsistent chantype in data and montage');
elseif isfield(input, 'chantypenew') && ~isequal(input.chantypenew, montage.chantypeold)
  ft_error('inconsistent chantype in data and montage');
end

if isfield(input, 'labelold') && isfield(input, 'labelnew')
  inputtype = 'montage';
elseif isfield(input, 'tra')
  inputtype = 'sens';
elseif ft_datatype(input, 'raw')
  inputtype = 'raw';
elseif ft_datatype(input, 'timelock')
  inputtype = 'timelock';
elseif ft_datatype(input, 'freq') && isfield(input, 'fourierspctrm')
  inputtype = 'freq';
elseif ft_datatype(input, 'freq') && isfield(input, 'crsspctrm')
  inputtype = 'freq_crsspctrm';

  % attempt to convert to a chan-chan representation
  input     = ft_checkdata(input, 'cmbstyle', 'full');
else
  inputtype = 'unknown';
end

switch inputtype
  case 'montage'
    % apply the montage on top of the other montage
    if isa(input.tra, 'single')
      % sparse matrices and single precision do not match
      input.tra = full(montage.tra) * input.tra;
    else
      input.tra = montage.tra * input.tra;
    end
    input.labelnew    = montage.labelnew;
    input.chantypenew = montage.chantypenew;
    input.chanunitnew = montage.chanunitnew;

  case 'sens'
    % apply the montage to an electrode or gradiometer description
    sens = input;
    clear input

    % apply the montage to the input
    if isa(sens.tra, 'single')
      % sparse matrices and single precision do not match
      sens.tra = full(montage.tra) * sens.tra;
    else
      sens.tra = montage.tra * sens.tra;
    end

    % The montage operates on the coil weights in sens.tra, but the output channels can be different.
    % If possible, we want to keep the old channel positions and orientations.
    [sel1, sel2] = match_str(montage.labelnew, inputlabel);
    keepchans = isequal(sel1(:)', 1:numel(montage.labelnew));

    posweight = abs(montage.tra);
    posweight = diag(1./sum(posweight,2)) * posweight;

    if isfield(sens, 'chanpos')
      if keepchans
        sens.chanpos = sens.chanpos(sel2,:);
      else
        if ~isfield(sens, 'chanposold')
          % add a chanposold only if it is not there yet
          sens.chanposold  = sens.chanpos;
          %  also keep the old label, type and unit for reference
          sens.labelold    = inputlabel;
          sens.chantypeold = inputchantype;
          sens.chanunitold = inputchanunit;
        end
        % compute the new channel positions as a weighted sum of the old ones
        sens.chanpos = posweight * sens.chanpos;
      end
    end

    if isfield(sens, 'chanori')
      if keepchans
        sens.chanori = sens.chanori(sel2,:);
      else
        if ~isfield(sens, 'chanoriold')
          sens.chanoriold = sens.chanori;
        end
        % compute the new channel orientations as a weighted sum of the old ones
        sens.chanori = posweight * sens.chanori;
      end
    end

    sens.label    = montage.labelnew;
    sens.chantype = montage.chantypenew;
    sens.chanunit = montage.chanunitnew;

    % keep track of the order of the balancing and which one is the current one
    if istrue(inverse)
      if isfield(sens, 'balance')% && isfield(sens.balance, 'previous')
        if isfield(sens.balance, 'previous') && numel(sens.balance.previous)>=1
          sens.balance.current  = sens.balance.previous{1};
          sens.balance.previous = sens.balance.previous(2:end);
        elseif isfield(sens.balance, 'previous')
          sens.balance.current  = 'none';
          sens.balance          = rmfield(sens.balance, 'previous');
        else
          sens.balance.current  = 'none';
        end
      end

    elseif ~istrue(inverse) && ~isempty(bname)
      if isfield(sens, 'balance')
        % check whether a balancing montage with name bname already exist, and if so, how many
        mnt = fieldnames(sens.balance);
        sel = strmatch(bname, mnt);
        if numel(sel)==0
          % bname can stay the same
        elseif numel(sel)==1
          % the original should be renamed to 'bname1' and the new one should be 'bname2'
          sens.balance.([bname, '1']) = sens.balance.(bname);
          sens.balance                = rmfield(sens.balance, bname);
          if isfield(sens.balance, 'current') && strcmp(sens.balance.current, bname)
            sens.balance.current = [bname, '1'];
          end
          if isfield(sens.balance, 'previous')
            sel2 = strmatch(bname, sens.balance.previous);
            if ~isempty(sel2)
              sens.balance.previous{sel2} = [bname, '1'];
            end
          end
          bname = [bname, '2'];
        else
          bname = [bname, num2str(length(sel)+1)];
        end
      end

      if isfield(sens, 'balance') && isfield(sens.balance, 'current')
        if ~isfield(sens.balance, 'previous')
          sens.balance.previous = {};
        end
        sens.balance.previous = [{sens.balance.current} sens.balance.previous];
        sens.balance.current  = bname;
        sens.balance.(bname)  = montage;
      end
    end

    % rename the output variable
    input = sens;
    clear sens

  case 'raw'
    % apply the montage to the raw input data
    data = input;
    clear input

    % there are two challenges to deal with
    % 1) the input data can be single, and sparse(1) * single(0) fails
    % 2) the input data can contain nans, and 0*nan returns a nan
    % see http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=3035 and https://github.com/fieldtrip/fieldtrip/issues/2169

    Ntrials = numel(data.trial);
    ft_progress('init', feedback, 'processing trials');
    for i=1:Ntrials
      ft_progress(i/Ntrials, 'processing trial %d from %d\n', i, Ntrials);

      if     ~isa(data.trial{i}, 'single') && ~any(isnan(data.trial{i}(:)))
        data.trial{i} = montage.tra * data.trial{i};
      elseif ~isa(data.trial{i}, 'single') &&  any(isnan(data.trial{i}(:)))
        % do not multiply 0 in the montage with nan in the data
        tmp = zeros(size(montage.tra,1), size(data.trial{i}, 2));
        for j=1:size(montage.tra,1)
          sel = montage.tra(j,:)~=0;
          tmp(j,:) = montage.tra(j,sel) * data.trial{i}(sel,:);
        end
        data.trial{i} = tmp;
      elseif  isa(data.trial{i}, 'single') && ~any(isnan(data.trial{i}(:)))
        % sparse matrices and single precision do not match
        data.trial{i} = full(montage.tra) * data.trial{i};
      elseif  isa(data.trial{i}, 'single') &&  any(isnan(data.trial{i}(:)))
        % sparse matrices and single precision do not match
        % do not multiply 0 in the montage with nan in the data, see http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=3035
        tmp = zeros(size(montage.tra,1), size(data.trial{i}, 2));
        for j=1:size(montage.tra,1)
          sel = montage.tra(j,:)~=0;
          tmp(j,:) = full(montage.tra(j,sel)) * data.trial{i}(sel,:);
        end
        data.trial{i} = tmp;
      end
    end % for Ntrials
    ft_progress('close');

    data.label    = montage.labelnew;
    data.chantype = montage.chantypenew;
    data.chanunit = montage.chanunitnew;

    % rename the output variable
    input = data;
    clear data

  case 'timelock'
    % apply the montage to averaged data
    timelock = input;
    clear input

    fn = {'avg', 'trial', 'individual', 'cov'};
    for i=1:numel(fn)
      if isfield(timelock, fn{i})
        switch getdimord(timelock, fn{i})
          case 'chan_time'
            timelock.(fn{i}) = montage.tra * timelock.(fn{i});
          case 'rpt_chan_time'
            siz    = getdimsiz(timelock, fn{i});
            nrpt   = siz(1);
            nchan  = siz(2);
            ntime  = siz(3);
            output = zeros(nrpt, size(montage.tra,1), ntime);
            for rptlop=1:nrpt
              output(rptlop,:,:) = montage.tra * reshape(timelock.(fn{i})(rptlop,:,:), [nchan ntime]);
            end
            timelock.(fn{i}) = output; % replace the original field
          case 'chan_chan'
            timelock.(fn{i}) = montage.tra * timelock.(fn{i}) * montage.tra';
          case 'rpt_chan_chan'
            siz    = getdimsiz(timelock, fn{i});
            nrpt   = siz(1);
            nchan  = siz(2);
            output = zeros(nrpt, size(montage.tra,1), size(montage.tra,1));
            for rptlop=1:nrpt
              output(rptlop,:,:) = montage.tra * reshape(timelock.(fn{i})(rptlop,:,:), [nchan nchan]) * montage.tra';
            end
            timelock.(fn{i}) = output; % replace the original field
          otherwise
            ft_error('unsupported dimord for %s', fn{i});
        end % switch
      end % if
    end % for

    timelock.label    = montage.labelnew;
    timelock.chantype = montage.chantypenew;
    timelock.chanunit = montage.chanunitnew;

    % rename the output variable
    input = timelock;
    clear timelock

  case 'freq'
    % apply the montage to the spectrally decomposed data
    freq = input;
    clear input

    switch getdimord(freq, 'fourierspctrm')
      case 'rpttap_chan_freq'
        siz    = [getdimsiz(freq, 'fourierspctrm') 1];
        nrpt   = siz(1);
        nchan  = siz(2);
        nfreq  = siz(3);
        output = zeros(nrpt, size(montage.tra,1), nfreq);
        for foilop=1:nfreq
          output(:,:,foilop) = freq.fourierspctrm(:,:,foilop) * montage.tra';
        end
        freq.fourierspctrm = output; % replace the original Fourier spectrum

      case 'rpttap_chan_freq_time'
        siz    = getdimsiz(freq, 'fourierspctrm');
        nrpt   = siz(1);
        nchan  = siz(2);
        nfreq  = siz(3);
        ntime  = siz(4);
        output = zeros(nrpt, size(montage.tra,1), nfreq, ntime);
        for foilop=1:nfreq
          for toilop = 1:ntime
            output(:,:,foilop,toilop) = freq.fourierspctrm(:,:,foilop,toilop) * montage.tra';
          end
        end
        freq.fourierspctrm = output; % replace the original Fourier spectrum

      otherwise
        ft_error('unsupported dimord for fourierspctrm');
    end % switch

    freq.label    = montage.labelnew;
    freq.chantype = montage.chantypenew;
    freq.chanunit = montage.chanunitnew;

    % rename the output variable
    input = freq;
    clear freq
  case 'freq_crsspctrm'
    % input freq data has a chan-chan crsspctrm, montage needs te be
    % applied to both ends

    freq = input;
    clear input
    if contains(getdimord(freq, 'crsspctrm'), 'rpt')
      % first dimension is rpt-like, so the square is dimensions 2 and 3
      siz   = [getdimsiz(freq, 'crsspctrm') 1];
      nrpt  = siz(1);
      nchan = siz(2);
      nrest = prod(siz(4:end));
      output = zeros([nrpt size(montage.tra,1).*[1 1] siz(4:end)]);
      for rptlop = 1:nrpt
        for restlop = 1:nrest
          output(rptlop,:,:,restlop) = montage.tra*reshape(freq.crsspctrm(rptlop,:,:,restlop),[nchan nchan])*montage.tra';
        end
      end
      freq.crsspctrm = output;
    else
      % the square is dimensions 1 and 2
      siz   = [getdimsiz(freq, 'crsspctrm') 1];
      nrest = prod(siz(4:end));
      output = zeros([size(montage.tra,1).*[1 1] siz(4:end)]);
      for restlop = 1:nrest
        output(:,:,restlop) = montage.tra*freq.crsspctrm(:,:,restlop)*montage.tra';
      end
      freq.crsspctrm = output;
    end

    freq.label    = montage.labelnew;
    freq.chantype = montage.chantypenew;
    freq.chanunit = montage.chanunitnew;

    % rename the output variable
    input = freq;
    clear freq
  otherwise
    ft_error('unrecognized input');
end % switch inputtype

% only retain the chantype and/or chanunit if they were present in the input
if ~haschantype
  input = removefields(input, {'chantype', 'chantypeold', 'chantypenew'});
end
if ~haschanunit
  input = removefields(input, {'chanunit', 'chanunitold', 'chanunitnew'});
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% HELPER FUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function y = indx2logical(x, n)
y = false(1,n);
y(x) = true;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% HELPER FUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function nowarning(varargin)
return


function vis_topo(varargin)
% handles:
% handles.ics(1-8) has which ICs to plot

% get the updated stream buffer
W = evalin('base','W');
sphere = evalin('base','sphere');
Winv = inv(W*sphere);
handles = varargin{3};

it = mod(get(varargin{1},'TasksExecuted')-1,8)+1;
hstr = ['axesIC' int2str(it)];
hand = get(handles.(hstr),'children');
[map, cmin, cmax] = topoplotUpdate(Winv(:,handles.ics(it)), handles.chanlocs,'electrodes','off','gridscale',32);
set(hand(end),'CData',map);
set(handles.(hstr),'CLim',[cmin cmax]);


function image = motionCompMeanImage(view, scans, frames)

%    function image = motionCompMeanImage(view, [scans], [frames])
%
% gb 01/22/05
%
% Finds the image closest to the mean image across frames for all scans
% Default : scans = current scan
%           frames = all frames

% Initilizes arguments and variables

global dataTYPES
curType = viewGet(view, 'curdatatype');

if ieNotDefined('scans')
    scans = 1:length(dataTYPES(curType).scanParams);
end
if ieNotDefined('frames')
    frames = 1:length(dataTYPES(curType).scanParams(scans(1)).nFrames);
end

% Computes the mean image
meanImage = motionCompComputeMean(view,scans,frames);

% Finds the image closest to the mean image
[scan,frame,mn] = motionCompNearestImage(view, scans, frames, meanImage);

% Loads this image
frame = frames(frame);
image = motionCompLoadImages(view,scan,frame);


function [u,perm,gjc,g] = urotorder(u,K, maxu,permIN) %#ok
% [u,perm,gjc,g] = urotorder(u,K, maxu,permIN)
%
% UROTORDER  Stable reORDERing of triu U-factor by Givens ROTations.
%
% **********  INTERNAL FUNCTION OF SEDUMI **********
%
% See also sedumi

% 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

%Indicate to the user Matlab cannot find the SeDuMi binaries
sedumi_binary_error();


function [throttle_factor, counter_out] = manage_clutchgearshift(trigger_gearshift, counter_in, throttle_shiftbreak_s, tSPhysics)
%% Documentation
%
% Author:       Leonhard Hermansdorfer (leo.hermansdorfer@tum.de)
%
% Start Date:   15.09.2021
%
% Description:  this function applies throttle lifting if a gear shift happend.
%               This aims at increasing the vehicle stability during shifting.
%
% Inputs:
%   - trigger_gearshift         gear shift trigger (1 if shift happend, otherwise 0)
%   - counter_in                same value as counter_out from last timestep
%   - throttle_shiftbreak_s     time duration of throttle lift after gear shift
%
% Outputs:
%   - throttle_factor           multiply factor for throttle signal (either 1 or 0)
%   - counter_out               counter which is fed back into the function in the next timestep

    % detect gear shift (direction independent)
    trigger_gearshift = min(1, abs(trigger_gearshift));

    % start counter if a gear shift has happend
    if trigger_gearshift == 1
        counter_in = throttle_shiftbreak_s;
    end

    % count down to zero
    counter_out = counter_in - tSPhysics;

    % calculate throttle factor
    if counter_out > 0
        throttle_factor = 0;
    else
        throttle_factor = 1;
    end

end


function u = tora_control(x)

load('tora_data', 'K', 'U', 'domain');

p = [x(3) x(4)];
u = tpcontroller(p, x, K, U, domain);


% The COBRAToolbox: testOptGene.m
%
% Purpose:
%     - test the optGene function
%
% Authors:
%     - Jacek Wachowiak

% Check requirements

requiredToolboxes = {'gads_toolbox'};  % This is the Global optimization toolbox
solvers = prepareTest('needsLP', true, 'needsMILP', true, 'toolboxes', requiredToolboxes);
% If we have more than one solver per type (LP/MILP), only use those that
% are available for both.
if numel(solvers.LP) > 1 && numel(solvers.MILP) > 1
    commonSolvers = intersect(solvers.LP, solvers.MILP);
    if ~isempty(commonSolvers)  % If there is any such solver
        solvers.MILP = commonSolvers;
        solvers.LP = commonSolvers;
    else  % Use only one solver. otherwise we can get into troubles.
        solvers.LP = solvers.LP(1);
        solvers.MILP = solvers.MILP(1);
    end
end
% save the current path
currentDir = pwd;

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

% test variables
model = getDistributedModel('ecoli_core_model.mat');
targetRxn = model.rxns{39};  % Succinate
fructose_substrateRxn = model.rxns{26};  % Fructose, even though this has no incluence whatsoever.
generxnList = model.rxns(setdiff([1:95], [11, 13, 26, 39]));  % Everything besides the ATP Maintenance, The biomass reaction and the substrate and target reactions.

klt=min([length(solvers.LP),length(solvers.MILP)]);
for k = 1:klt
    changeCobraSolver(solvers.LP{k}, 'LP', 0);
    changeCobraSolver(solvers.MILP{k}, 'MILP', 0);
    fprintf(' -- Running testOptGene using the solver interfaces: LP: %s ; MILP: %s... ', solvers.LP{k}, solvers.MILP{k});
    basicsolution = optimizeCbModel(model);
    % function outputs
    % requires Global Optimization Toolbox
    % Set the rng, for reproducability
    rng(0);
    [x, population, scores, optGeneSol] = optGene(model, targetRxn, fructose_substrateRxn, generxnList, 'StallTimeLimit', 15, 'TimeLimit', 30);
    % Check, that we get the expected solution from a previous run.
    optSols = population((optGeneSol.scores == min(optGeneSol.scores)), :);  % Get the set of optimal Solutions.
    optReacs = optSols(1, :);
    model2 = model;
    model2.lb(ismember(model2.rxns, generxnList(optReacs))) = 0;
    model2.ub(ismember(model2.rxns, generxnList(optReacs))) = 0;
    sol = optimizeCbModel(model2);
    % Lets only assert, that we have some improvement.
    assert(sol.v(39) - basicsolution.v(39) > 0);

end
% close the open windows
close all

% Remove the output, to keep the toolbox updateable.
delete([fileDir filesep 'MILPProblem.mat']);

fprintf('Done.\n');
% change to old directory
cd(currentDir);


function [hdr] = read_bucn_nirshdr(filename)

% READ_BUCN_NIRSHDR reads the header information of ASCII-formatted NIRS
% data acquired with the UCL-BIRKBECK machine and postprocessed by the
% Paris group. The first line contains the channel labels and the rest of
% the file contains per line a time sample. The first column specifies the
% time axis.
%
% Use as
%   [hdr] = read_bucn_nirshdr(filename)
%
% See also read_bucn_nirsdata, READ_BUCN_NIRSEVENT

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

fid = fopen_or_error(filename, 'r');

% read the first line
line1 = textscan(fid, '%[^\n]',1);

% field delimiter can be space or tab
labelspc = textscan(line1{1}{1}, '%[^ ]');
labeltab = textscan(line1{1}{1}, '%[^\t]');

% let tab as a delimiter prevail
if numel(labeltab{1})>1
  label = labeltab{1};
else
  label = labelspc{1};
end
nchan = numel(label);
Fs    = str2num(strtok(strtok(label{1},'#Time.'),'Hz'));

% test whether the channel labels are non-numeric
labelnumber = cellfun(@str2num, label, 'UniformOutput', false);
labelstring = cellfun(@isempty, labelnumber, 'UniformOutput', true);
if ~any(labelstring)
  ft_error('channel labels were not found in the first line of the file');
end

% read the rest
dat = textscan(fid, '%f');
fclose(fid);

dat  = reshape(dat{1}, nchan, []);
nsmp = size(dat,2);

% create the output
hdr          = [];
hdr.Fs       = Fs;
hdr.label    = label;
hdr.nTrials  = 1;
hdr.nSamples = nsmp;
hdr.nSamplesPre = 0;
hdr.nChans   = nchan;
hdr.time     = dat(1,:); % events in the raw event file have both a sample and a time stamp


function resetTimeTicks(h, num, format)
% reset function for setTimeTicks
% SEE ALSO: setTimeTicks

%--------------------------------------------------------------------------
%               ___ ___ ___
%     __ _ ___ / __| _ | __|
%    / _` / _ \ (_ |  _|__ \
%    \__, \___/\___|_| |___/
%    |___/                    v 1.0RC1
%
%--------------------------------------------------------------------------
%  Copyright (C) 2021 Geomatics Research & Development srl (GReD)
%  Written by:       Andrea Gatti
%  Contributors:     Andrea Gatti, ...
%  A list of all the historical goGPS contributors is in CREDITS.nfo
%--------------------------------------------------------------------------
%
%    This program is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    This program is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with this program.  If not, see <http://www.gnu.org/licenses/>.
%
%--------------------------------------------------------------------------
% 01100111 01101111 01000111 01010000 01010011
%--------------------------------------------------------------------------

    if (nargin == 1)
        num = 4;
        format = 'yyyy/mm/dd HH:MM';
    end
    if isa(h, 'matlab.ui.Figure')
        h = get(h,'Children');
    end
    for i = 1 : numel(h)
        try
            h(i).TickLength = [0.005 0.01];
            ax = double(axis(h(i)));

            step = (ax(2)-ax(1))/(num);
            time_span = ax(2)-ax(1);

            % depending on the time_span round ticks
            round_val = 1 / 86400; % one second

            if time_span > 8                 % greater than 20days
                round_val =  1;               % round on 1 day
            elseif time_span > 4              % greater than 5 days
                round_val =  12 / 24;         % round on 12 hour
            elseif time_span > 1              % greater than a day
                round_val =  1 / 24;          % round on 1 hour
            elseif time_span > 12 / 24        % greater than 12 hours
                round_val =  30 / (24 * 60);  % round on 30 minutes
            elseif time_span > 5 / 24         % greater than 5 hours
                round_val =  15 / (24 * 60);  % round on 15 minutes
            elseif time_span > 1 / 24         % greater than 1 hour
                round_val =  5 / (24 * 60);   % round on 5 minutes
            elseif time_span > 10 / (24 * 60) % greater than 10 minutes
                round_val =  1 / (24 * 60);   % round on 1 minute
            elseif time_span > 5 / (24 * 60)  % greater than 5 minutes
                round_val =  30 / 86400;      % round on 30 seconds
            elseif time_span > 1 / (24 * 60)  % greater than 1 minutes
                round_val =  5 / 86400;       % round on 5 seconds
            end

            tick_pos = (ax(1) + (step/2)) : step : (ax(2) - (step/2));

            c_out = 0;
            if round_val >= 1 / (24 * 60) % rounding is greater than 1 minute
                c_out = 3;                % do not show seconds
            elseif round_val >= 1 / 24    % rounding is greater than 1 hour
                c_out = 6;                % do not show minutes
            elseif round_val >= 1         % greater than 1 day
                c_out = 9;                % do not show hours
            end

            tick_pos = round(tick_pos ./ round_val) .* round_val; % round ticks
            tick_pos((tick_pos < ax(1)) | (tick_pos > ax(2))) = []; % delete ticks outside figure;
            tick_pos = unique(tick_pos);

            if all(mod(tick_pos, 1) == 0)
                round_val = 1;
            end
            set(h(i), 'XTick', tick_pos);
            if strcmp(format, 'auto')
                last_date = [0 0 0 0 0 0];
                h.XTickLabel = cell(size(tick_pos));

                x_labels = cell(size(tick_pos));
                for l = 1 : numel(tick_pos)
                    str_time_format = 'HH:MM:SS';
                    new_date = datevec(double(tick_pos(l)));
                    if last_date(1) ~= new_date(1)        % if the year is different
                        str_time_format = [char(32 * ones(1, floor(c_out/2) + 5)) str_time_format(1:end - c_out) '-newlinemmm dd, yyyy'];
                    elseif last_date(2) ~= new_date(2)    % if the month is different
                        str_time_format = [char(32 * ones(1, floor(c_out/2) + 5)) str_time_format(1:end - c_out) '-newlinemmm dd, yyyy'];
                    elseif last_date(3) ~= new_date(3)    % if the day is different
                        str_time_format = [char(32 * ones(1, floor(c_out/2) + 5)) str_time_format(1:end - c_out) '-newlinemmm dd, yyyy'];
                    elseif last_date(4) ~= new_date(4)    % if the hour is different
                        str_time_format = str_time_format(1:end - c_out);
                    else % if last_date(5) ~= new_date(5)    % if the hour is different
                        str_time_format = str_time_format(1:end - c_out);
                    end
                    if isempty(str_time_format)
                        h.XTickLabel{l} = '';
                    else
                        if round_val == 1
                            tmp = datestr(tick_pos(l), regexp(str_time_format, '(?<=line).*','match', 'once'));
                        else
                            tmp = datestr(tick_pos(l), str_time_format);
                            tmp(tmp == '-') = '\';
                        end
                        h.XTickLabel{l} = tmp;
                    end
                    last_date = new_date;
                end
            else
                datetick(h(i),'x',format,'keepticks');
            end
            axis(h(i),ax);
        catch ex
            Core_Utils.printEx(ex);
        end
    end
end


function all_sal = computeSaliency(options)

    if( ~exist( options.outfolder, 'dir' ) ), mkdir( options.outfolder ), end;
    if( ~exist( fullfile( options.outfolder, 'intra-frame_saliency'), 'dir' ) )
        mkdir(fullfile( options.outfolder, 'intra-frame_saliency'));
    end
    if( ~exist( fullfile( options.outfolder, 'inter-frame_saliency'), 'dir' ) )
        mkdir(fullfile( options.outfolder, 'inter-frame_saliency'));
    end
    if( ~exist( fullfile( options.outfolder, 'final_saliency'), 'dir' ) )
        mkdir(fullfile( options.outfolder, 'final_saliency'));
    end
    % Cache all frames in memory
    [data.frames,data.names,height,width,nframe ]= readAllFrames( options );
     % Load optical flow (or compute if file is not found)
    data.flow = loadFlow( options );
    if( isempty( data.flow ) )
        data.flow = computeOpticalFlow( options, data.frames );
    end
    % Load superpixels (or compute if not found)
    data.superpixels = loadSuperpixels( options );
    if( isempty( data.superpixels ) )
        data.superpixels = computeSuperpixels(  options, data.frames );
    end
    % Load Boundary (or compute if not found)
    % computeBoundary
    %

    [ superpixels, ~, bounds, labels ] = makeSuperpixelIndexUnique( data.superpixels );
    [ colours, centres, ~ ] = getSuperpixelStats( data.frames(1:nframe-1), superpixels, double(labels) );%
    valLAB = [];
    for index = 1:nframe-1
        valLAB = [valLAB;data.superpixels{index}.Sup1, data.superpixels{index}.Sup2, data.superpixels{index}.Sup3];
    end

    k =1:6:nframe-1;
    k(end) = [];
    global_saliency_track = zeros(height,width);
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%computing global and local location cues %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    for i = 1:size(k,2)
        all_flowmagnitude = zeros(height,width);
        if i > 1
            for index = k(i):k(i)+5
                flowgradient = getFlowGradient( data.flow{index} );
                flowmagnitude = getMagnitude( flowgradient );
                flowmagnitude = (0.5+saliency_trac).*reshape(flowmagnitude(:),height,width);
                all_flowmagnitude = max(all_flowmagnitude,flowmagnitude);
            end
        else
            for index = k(i):k(i)+5
                flowgradient = getFlowGradient( data.flow{index} );
                flowmagnitude = getMagnitude( flowgradient );
                flowmagnitude = reshape(flowmagnitude(:),height,width);
                all_flowmagnitude = max(all_flowmagnitude,flowmagnitude);
            end
        end

        all_flowmagnitude = imresize(all_flowmagnitude,0.1,'bilinear');
        [h,w] = size(all_flowmagnitude);
        all_flowmagnitudex = all_flowmagnitude(:);
        all_flowmagnitudex(h*w+1)=0;
        all_Label = zeros(h,w);
        all_Label(:) = 1:size(all_flowmagnitude,1)*size(all_flowmagnitude,2);
        all_Labelx = zeros(h+2,w+2);
        all_Labelx(2:end-1,2:end-1) = all_Label;
        all_Labelx(1,:)=h*w+1;
        all_Labelx(end,:)=h*w+1;
        all_Labelx(:,1)=h*w+1;
        all_Labelx(:,end)=h*w+1;
        [ConSPix,~]= find_connect_superpixel(all_Labelx, h*w+1, h+2 ,w+2 );
        ConSPix = ConSPix + eye(size(ConSPix,1));
        all_fDistM = squareform(pdist(all_flowmagnitudex(:)));
        bdIds = GetBndPatchIds(all_Label,1);
        clipVal = EstimateDynamicParas(ConSPix,all_fDistM);
        geoDist = GeodesicSaliency(ConSPix, double(bdIds), all_fDistM, clipVal,true,[]);
        geoDist = geoDist(1:end-1);
        saliency_trac = reshape(geoDist,h,w);
        saliency_trac = imresize(saliency_trac,[height,width],'bilinear');
        global_saliency_track = max(global_saliency_track,saliency_trac);
    end
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%computing saliency via intra-frame graph %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    if( options.vocal )
          disp( 'compute saliency via intra-frame graph:');
    end
    all_sal = cell(1,nframe-1);
    for index = 1:nframe-1
        if( options.vocal )
                fprintf( 'Processing frame: %i of %i... \n', ...
                    index, nframe-1);
        end
        FGR = [];
        frame = data.frames{index};
        frameName = data.names{index};
        nLabel = double(max(data.superpixels{index}.Label(:)));
        Label = data.superpixels{index}.Label;
        G = edge_detect(imfilter(uint8(frame),fspecial('average',3),'same','replicate'));
        flowgradient = getFlowGradient( data.flow{index} );
        flowmagnitude = getMagnitude( flowgradient );
        gradBoundary = 1 - exp( -flowmagnitude);
        flowmagnitude = G.*( gradBoundary +0.1);
        flowmagnitude = reshape(flowmagnitude(:),height,width);
        for  i = 1:nLabel
             flowmagnitude_R = flowmagnitude(Label==i);
             [flowmagnitude_R,~] = sort(flowmagnitude_R, 'descend');
             FGR(i)=mean(flowmagnitude_R(1:10));
             flowmagnitude(Label==i) = FGR(i);
        end

        [ConSPix,~]= find_connect_superpixel( Label, nLabel, height ,width );
        ConSPix = ConSPix + eye(nLabel);
        FGRM = squareform(pdist(FGR'));
        bdIds = GetBndPatchIds(Label,1);
        clipVal = EstimateDynamicParas(ConSPix,FGRM);
        geoDist = GeodesicSaliency(ConSPix, double(bdIds), FGRM, clipVal,true,[]);
        geo_sal = geoDist(data.superpixels{index}.Label);
        all_sal{index}=geo_sal.*(global_saliency_track+0.3);
        all_sal{index}=all_sal{index}/max(all_sal{index}(:));

        imwrite(all_sal{index}, [options.outfolder '\intra-frame_saliency\' frameName  '.bmp']);
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%computing saliency via inter-frame graph %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    if( options.vocal )
          disp( 'compute saliency via inter-frame graph:');
    end
    all_geoDist = cell(1,nframe-1);
    for index = 1:nframe-2
        if( options.vocal )
                fprintf( 'Processing frame: %i of %i... \n', ...
                    index, nframe-1);
        end
        nLabel = double(max(data.superpixels{index}.Label(:)));

        colDistM = squareform(pdist(valLAB( bounds(index):bounds(index+2)-1,:)));
        Conedge = [];
        Label_1 = data.superpixels{index}.Label;
        [~,conedge]= find_connect_superpixel( Label_1, max(Label_1(:)), height ,width );
        Conedge = [Conedge;conedge];

        [x,y] = meshgrid(1:bounds(index+1)-bounds(index),1:bounds(index+2)-bounds(index+1));
        conedge = [x(:),y(:)+bounds(index+1)-bounds(index)];
        connect = sum((centres(conedge(:,1)+bounds(index)-1,:) - centres(conedge(:,2)+bounds(index)-1,:)).^2,2 );
        cross_po_dis = conedge(connect<800,:);
        Conedge = [Conedge;cross_po_dis];

        Label_2 = data.superpixels{index+1}.Label;
        [~,conedge]= find_connect_superpixel( Label_2, max(Label_2(:)), height ,width );
        Conedge = [Conedge;conedge + bounds(index+1)-bounds(index)];
        ConSPix=sparse([Conedge(:,1);Conedge(:,2)],[Conedge(:,2);Conedge(:,1)], ...
         [ones(size(Conedge(:,1)));ones(size(Conedge(:,1)))],bounds(index+2)-bounds(index),bounds(index+2)-bounds(index));
        ConSPix = full(ConSPix);
        ConSPix = ConSPix +eye(size(ConSPix));

        firstmap = all_sal{index}>mean(all_sal{index}(:));
        str = strel('disk',1);  firstmap = imdilate(firstmap,str);
        fd = int32(firstmap).*data.superpixels{index}.Label;
        fd = unique(fd(:));
        fd(fd==0) = [];
        bd = setdiff(unique(data.superpixels{index}.Label(:)),fd);

        bdIds = bd;

        secondmap = all_sal{index+1}>mean(all_sal{index+1}(:));
        secondmap = ~((~secondmap).*(~firstmap));
        str = strel('disk',1);  secondmap = imdilate(secondmap,str);
        fd = int32(secondmap).*data.superpixels{index+1}.Label;
        fd = unique(fd(:));
        fd(fd==0) = [];
        bd = setdiff(unique(data.superpixels{index+1}.Label(:)),fd);
        bdIds = [bdIds;bd+bounds(index+1)-bounds(index)];

        clipVal = EstimateDynamicParas(ConSPix,colDistM);
        geoDist = GeodesicSaliency(ConSPix, double(bdIds), colDistM, clipVal, false,[]);
        geoDist_1 = geoDist(1:bounds(index+1)-bounds(index));
        geoDist_2 = geoDist(bounds(index+1)-bounds(index)+1:end);

        tmp = sort(geoDist_1, 'descend');
        pos = round(options.topRate * length(tmp));
        maxVal = tmp(pos);
        geoDist_1 = geoDist_1 / maxVal;
        geoDist_1(geoDist_1 > 1) = 1;

        tmp = sort(geoDist_2, 'descend');
        pos = round(options.topRate * length(tmp));
        maxVal = tmp(pos);
        geoDist_2 = geoDist_2 / maxVal;
        geoDist_2(geoDist_2 > 1) = 1;

        all_geoDist{index} = geoDist_1;
        all_geoDist{index+1} = geoDist_2;
        geo_sal = geoDist_1(data.superpixels{index}.Label);

        all_sal{index} = geo_sal*0.5+all_sal{index}*0.5;
        all_sal{index} = all_sal{index}./max( all_sal{index}(:));

        L{1} = uint32(data.superpixels{index}.Label);
        S{1} = repmat(all_sal{index},[1 3]);
        [ R, ~, ~ ] = getSuperpixelStats(S(1:1),L, nLabel );
        R = double(R(:,1));
        sR = sort(R);
        t = sum(sR(end-9:end))/10;
        R = (R-min(R))/(t-min(R));
        R(R>1)=1;
        all_geoDist{index} = R';

        geo_sal = geoDist_2(data.superpixels{index+1}.Label);

        all_sal{index+1} = geo_sal*0.5+all_sal{index+1}*0.5;
        all_sal{index+1} = all_sal{index+1}./max( all_sal{index+1}(:));

        nLabel = double(max(data.superpixels{index+1}.Label(:)));
        L{1} = uint32(data.superpixels{index+1}.Label);
        S{1} = repmat(all_sal{index+1},[1 3]);
        [ R, ~, ~ ] = getSuperpixelStats(S(1:1),L, nLabel );
        R = double(R(:,1));
        sR = sort(R);
        t = sum(sR(end-9:end))/10;
        R = (R-min(R))/(t-min(R));
        R(R>1)=1;
        all_geoDist{index+1} = R';

    end

    RegionSal = [];
    for index = 1:nframe-1
        nLabel = double(max(data.superpixels{index}.Label(:)));
        all_sal{index} = all_sal{index}.*(global_saliency_track+0.1);
        all_sal{index} = all_sal{index}/max(all_sal{index}(:));

        sal = reshape( all_sal{index},height*width,1);
        L{1} = uint32(data.superpixels{index}.Label);
        S{1} = repmat(sal,[1 3]);
        [ R, ~, ~ ] = getSuperpixelStats(S(1:1),L, nLabel );
        R = double(R(:,1));
        sR = sort(R);
        t = sum(sR(end-9:end))/10;
        R = (R-min(R))/(t-min(R));
        R(R>1)=1;
        all_geoDist{index} = R';
        RegionSal = [RegionSal;all_geoDist{index}(:)];
        all_sal{index} = double(R(data.superpixels{index}.Label));
        imwrite(all_sal{index}, [options.outfolder '\inter-frame_saliency\' data.names{index} '.bmp']);
    end
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Spatio-temporal consistance optimization %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%you can uncomment this part for computation efficiency %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    if( options.vocal )
          disp( 'spatio-temporal consistance optimization');
    end
    Conedge = [];
    for index = 1:nframe-1
        Label = data.superpixels{index}.Label;
        [~,conedge]= find_connect_superpixel( Label, max(Label(:)), height ,width );
        Conedge = [Conedge;conedge + bounds(index)-1];
    end
    intralength = size(Conedge,1);
    for index = 1:nframe-2
        [x,y] = meshgrid(1:bounds(index+1)-bounds(index),1:bounds(index+2)-bounds(index+1));
        conedge = [x(:)+bounds(index)-1,y(:)+bounds(index+1)-1];
        connect = sum((centres(conedge(:,1),:) - centres(conedge(:,2),:)).^2,2 );
        Conedge = [Conedge;conedge(connect<800,:)];
    end
    valDistances=sqrt(sum((valLAB(Conedge(:,1),:)-valLAB(Conedge(:,2),:)).^2,2));
    valDistances(intralength+1:end)=valDistances(intralength+1:end)/5;
    valDistances=normalize(valDistances);
    weights=exp(-options.valScale*valDistances)+ 1e-5;
    weights=sparse([Conedge(:,1);Conedge(:,2)],[Conedge(:,2);Conedge(:,1)], ...
    [weights;weights],labels,labels);
    E = sparse(1:labels,1:labels,ones(labels,1)); iD = sparse(1:labels,1:labels,1./sum(weights));
    P = iD*weights;
    RegionSal = (E-P+10*options.alpha*E)\RegionSal;
    for index = 1:nframe-1
        R = RegionSal(bounds(index):bounds(index+1)-1);
        sR = sort(R);
        t = sum(sR(end-9:end))/10;
        R = (R-min(R))/(t-min(R));
        R(R>1)=1;
        all_geoDist{index} = R';
        all_sal{index} = 0.6*double(R(data.superpixels{index}.Label))+0.4*all_sal{index};
        imwrite(all_sal{index}, [options.outfolder '\final_saliency\' data.names{index}  '.bmp']);
    end


function outstruct=memmapstream(bytes, format, varargin)
%
%    outstruct=memmapstream(bytes, format)
%
%    Map a byte-array (in char array or uint8/int8 array) into a structure
%    using a dictionary (format is compatible with memmapfile in MATLAB)
%
%    This function is compatible with both MATLAB and GNU Octave.
%
%    author: Qianqian Fang (q.fang <at> neu.edu)
%
%    input:
%        bytes: a char, int8 or uint8 vector or array
%        format: a 3-column cell array in the format compatible with the
%              'Format' parameter of memmapfile in MATLAB. It has the
%              following structure
%
%             column 1: data type string, it can be one of the following
%                'int8','int16','int32','int64',
%                'uint8','uint16','uint32','uint64',
%                'single','double','logical'
%             column 2: an integer vector denoting the size of the data
%             column 3: a string denoting the fieldname in the output struct
%
%             For example format={'int8',[1,8],'key'; 'float',[1,1],'value'}
%             reads the first 8 bytes from 'bytes' as the first subfield
%             'key' and the following 4 bytes as the floating point 'value'
%             subfield.
%
%    output:
%        outstruct: a structure containing the required field
%
%    example:
%        bytestream=['Andy' 5 'JT'];
%        format={'uint8', [1,4], 'name',
%              'uint8', [1,1], 'age',
%              'uint8', [1,2], 'school'};
%        data=memmapstream(bytestream,format);
%
%    this file is part of JNIfTI specification: https://github.com/fangq/jnifti
%
%    License: Apache 2.0, see https://github.com/fangq/jnifti for details
%

if(nargin<2)
   error('must provide bytes and format as inputs');
end

if(~ischar(bytes) && ~isa(bytes,'int8') && ~isa(bytes,'uint8') || isempty(bytes))
   error('first input, bytes, must be a char-array or uint8/int8 vector');
end

if(~iscell(format) || size(format,2)<3 || size(format,1)==0 || ~ischar(format{1,1}))
   error('second input, format, must be a 3-column cell array, in a format described by the memmapfile Format field.');
end

bytes=bytes(:)';

datatype=struct('int8',1,'int16',2,'int32',4,'int64',8,'uint8',1,'uint16',2,'uint32',4,'uint64',8,'single',4,'double',8);

opt=varargin2struct(varargin{:});
opt.usemap=jsonopt('usemap',0,opt) && exist('containers.Map');

if(opt.usemap)
    outstruct=containers.Map();
else
    outstruct=struct();
end
len=1;
for i=1:size(format,1)
    bytelen=datatype.(format{i,1})*prod(format{i,2});
    if(opt.usemap)
        outstruct(format{i,3})=reshape(typecast(uint8(bytes(len:bytelen+len-1)),format{i,1}),format{i,2});
    else
        outstruct.(format{i,3})=reshape(typecast(uint8(bytes(len:bytelen+len-1)),format{i,1}),format{i,2});
    end
    len=len+bytelen;
    if(len>length(bytes))
        break;
    end
end


% ----------------------------------------------------------------------------------------------------------------
function net = vid_create_net(varargin)
% Very similar to vanilla AlexNet from MatConvNet examples,
% but with smaller stride at conv1 and no padding
% Used to generate the network described in  the paper
% "Fully-Convolutional Siamese Networks for Object Tracking"
% ----------------------------------------------------------------------------------------------------------------
    opts.exemplarSize = [127 127];
    opts.instanceSize = [255 255];
    opts.scale = 1 ;
    opts.initBias = 0.1 ;
    opts.weightDecay = 1 ;
    %opts.weightInitMethod = 'xavierimproved' ;
    opts.weightInitMethod = 'gaussian';
    opts.batchNormalization = false ;
    opts.networkType = 'simplenn' ;
    opts.strides = [2, 2, 1, 2] ;
    opts.cudnnWorkspaceLimit = 1024*1024*1024 ; % 1GB
    opts = vl_argparse(opts, varargin) ;

    if numel(opts.exemplarSize) == 1
        opts.exemplarSize = [opts.exemplarSize, opts.exemplarSize];
    end
    if numel(opts.instanceSize) == 1
        opts.instanceSize = [opts.instanceSize, opts.instanceSize];
    end

    net = modified_alexnet(struct(), opts) ;

    % Meta parameters
    net.meta.normalization.interpolation = 'bicubic' ;
    net.meta.normalization.averageImage = [] ;
    net.meta.normalization.keepAspect = true ;
    net.meta.augmentation.rgbVariance = zeros(0,3) ;
    net.meta.augmentation.transformation = 'stretch' ;

    % Fill in default values
    net = vl_simplenn_tidy(net) ;

    % Switch to DagNN if requested
    switch lower(opts.networkType)
      case 'simplenn'
        % done
      case 'dagnn'
        net = dagnn.DagNN.fromSimpleNN(net, 'canonicalNames', true) ;
        net.addLayer('top1err', dagnn.Loss('loss', 'classerror'), ...
                     {'prediction','label'}, 'top1err') ;
        net.addLayer('top5err', dagnn.Loss('loss', 'topkerror', ...
                                           'opts', {'topK',5}), ...
                     {'prediction','label'}, 'top5err') ;
      otherwise
        assert(false) ;
    end

end

% --------------------------------------------------------------------
function net = add_block(net, opts, id, h, w, in, out, stride, pad, init_bias)
% --------------------------------------------------------------------
info = vl_simplenn_display(net) ;
fc = (h == info.dataSize(1,end) && w == info.dataSize(2,end)) ;
if fc
  name = 'fc' ;
else
  name = 'conv' ;
end
convOpts = {'CudnnWorkspaceLimit', opts.cudnnWorkspaceLimit} ;
net.layers{end+1} = struct('type', 'conv', 'name', sprintf('%s%s', name, id), ...
                           'weights', {{init_weight(opts, h, w, in, out, 'single'), zeros(out, 1, 'single')}}, ...
                           'stride', stride, ...
                           'pad', pad, ...
                           'learningRate', [1 2], ...
                           'weightDecay', [opts.weightDecay 0], ...
                           'opts', {convOpts}) ;
if opts.batchNormalization
  net.layers{end+1} = struct('type', 'bnorm', 'name', sprintf('bn%s',id), ...
                             'weights', {{ones(out, 1, 'single'), zeros(out, 1, 'single'), zeros(out, 2, 'single')}}, ...
                             'learningRate', [2 1 0.05], ...
                             'weightDecay', [0 0]) ;
end
net.layers{end+1} = struct('type', 'relu', 'name', sprintf('relu%s',id)) ;
end

% --------------------------------------------------------------------
function net = add_block_conv_only(net, opts, id, h, w, in, out, stride, pad, init_bias)
% --------------------------------------------------------------------
info = vl_simplenn_display(net) ;
fc = (h == info.dataSize(1,end) && w == info.dataSize(2,end)) ;
if fc
  name = 'fc' ;
else
  name = 'conv' ;
end
convOpts = {'CudnnWorkspaceLimit', opts.cudnnWorkspaceLimit} ;
net.layers{end+1} = struct('type', 'conv', 'name', sprintf('%s%s', name, id), ...
                           'weights', {{init_weight(opts, h, w, in, out, 'single'), zeros(out, 1, 'single')}}, ...
                           'stride', stride, ...
                           'pad', pad, ...
                           'learningRate', [1 2], ...
                           'weightDecay', [opts.weightDecay 0], ...
                           'opts', {convOpts}) ;
end

% --------------------------------------------------------------------
function net = add_norm(net, opts, id)
% --------------------------------------------------------------------
if ~opts.batchNormalization
  net.layers{end+1} = struct('type', 'normalize', ...
                             'name', sprintf('norm%s', id), ...
                             'param', [5 1 0.0001/5 0.75]) ;
end
end

% --------------------------------------------------------------------
function net = add_dropout(net, opts, id)
% --------------------------------------------------------------------
if ~opts.batchNormalization
  net.layers{end+1} = struct('type', 'dropout', ...
                             'name', sprintf('dropout%s', id), ...
                             'rate', 0.5) ;
end
end

% --------------------------------------------------------------------
function net = modified_alexnet(net, opts)
% --------------------------------------------------------------------

    strides = ones(1, 7);
    strides(1:numel(opts.strides)) = opts.strides(:);

    net.layers = {} ;

    net = add_block(net, opts, '1', 11, 11, 3, 96, strides(1), 0) ;
    net = add_norm(net, opts, '1') ;
    net.layers{end+1} = struct('type', 'pool', 'name', 'pool1', ...
                               'method', 'max', ...
                               'pool', [3 3], ...
                               'stride', strides(2), ...
                               'pad', 0) ;

    net = add_block(net, opts, '2', 5, 5, 48, 256, strides(3), 0) ;
    net = add_norm(net, opts, '2') ;
    net.layers{end+1} = struct('type', 'pool', 'name', 'pool2', ...
                               'method', 'max', ...
                               'pool', [3 3], ...
                               'stride', strides(4), ...
                               'pad', 0) ;

    net = add_block(net, opts, '3', 3, 3, 256, 384, strides(5), 0) ;
    net = add_block(net, opts, '4', 3, 3, 192, 384, strides(6), 0) ;
    net = add_block_conv_only(net, opts, '5', 3, 3, 192, 256, strides(7), 0) ;

    % Check if the receptive field covers full image

    [ideal_exemplar, ~] = ideal_size(net, opts.exemplarSize);
    [ideal_instance, ~] = ideal_size(net, opts.instanceSize);
    assert(sum(opts.exemplarSize==ideal_exemplar)==2, 'exemplarSize is not ideal.');
    assert(sum(opts.instanceSize==ideal_instance)==2, 'instanceSize is not ideal.');
end


classdef TankMassConstraint < AbstractConstraint
    %TankMassConstraint Summary of this class goes here
    %   Detailed explanation goes here

    properties
        normFact = 1;
        tank LaunchVehicleTank
        event LaunchVehicleEvent
        eventNode(1,1) ConstraintStateComparisonNodeEnum = ConstraintStateComparisonNodeEnum.FinalState;

        lb(1,1) double = 0;
        ub(1,1) double = 0;

        evalType(1,1) ConstraintEvalTypeEnum = ConstraintEvalTypeEnum.FixedBounds;
        stateCompType(1,1) ConstraintStateComparisonTypeEnum = ConstraintStateComparisonTypeEnum.Equals;
        stateCompEvent LaunchVehicleEvent
        stateCompNode(1,1) ConstraintStateComparisonNodeEnum = ConstraintStateComparisonNodeEnum.FinalState;
    end

    methods
        function obj = TankMassConstraint(tank, event, lb, ub)
            obj.tank = tank;
            obj.event = event;
            obj.lb = lb;
            obj.ub = ub;

             obj.id = rand();
        end

        function [lb, ub] = getBounds(obj)
            lb = obj.lb;
            ub = obj.ub;
        end

        function [c, ceq, value, lwrBnd, uprBnd, type, eventNum, valueStateComp] = evalConstraint(obj, stateLog, celBodyData)
            type = obj.getConstraintType();

            switch obj.eventNode
                case ConstraintStateComparisonNodeEnum.FinalState
                    stateLogEntry = stateLog.getLastStateLogForEvent(obj.event);

                case ConstraintStateComparisonNodeEnum.InitialState
                    stateLogEntry = stateLog.getFirstStateLogForEvent(obj.event);

                otherwise
                    error('Unknown event node.');
            end

            value = lvd_TankMassTasks(stateLogEntry, 'tankMass', obj.tank);

            if(obj.evalType == ConstraintEvalTypeEnum.StateComparison)
                switch obj.stateCompNode
                    case ConstraintStateComparisonNodeEnum.FinalState
                        stateLogEntryStateComp = stateLog.getLastStateLogForEvent(obj.stateCompEvent);

                    case ConstraintStateComparisonNodeEnum.InitialState
                        stateLogEntryStateComp = stateLog.getFirstStateLogForEvent(obj.stateCompEvent);

                    otherwise
                        error('Unknown event node.');
                end

                valueStateComp = lvd_TankMassTasks(stateLogEntryStateComp, 'tankMass', obj.tank);
            else
                valueStateComp = NaN;
            end

            [c, ceq] = obj.computeCAndCeqValues(value, valueStateComp);

            lwrBnd = obj.lb;
            uprBnd = obj.ub;

            eventNum = obj.event.getEventNum();
        end

        function sF = getScaleFactor(obj)
            sF = obj.normFact;
        end

        function setScaleFactor(obj, sF)
            obj.normFact = sF;
        end

        function tf = usesStage(obj, stage)
            tf = false;
        end

        function tf = usesEngine(obj, engine)
            tf = false;
        end

        function tf = usesTank(obj, tank)
            tf = obj.tank == tank;
        end

        function tf = usesEngineToTankConn(obj, engineToTank)
            tf = false;
        end

        function tf = usesEvent(obj, event)
            tf = obj.event == event;
            if(obj.evalType == ConstraintEvalTypeEnum.StateComparison)
                tf = tf || obj.stateCompEvent == event;
            end
        end

        function tf = usesStopwatch(obj, stopwatch)
            tf = false;
        end

        function tf = usesExtremum(obj, extremum)
            tf = false;
        end

        function tf = usesGroundObj(obj, grdObj)
            tf = false;
        end

        function tf = canUseSparseOutput(obj)
            tf = true;
        end

        function event = getConstraintEvent(obj)
            event = obj.event;
        end

        function type = getConstraintType(obj)
            type = 'Tank Mass';
        end

        function [unit, lbLim, ubLim, usesLbUb, usesCelBody, usesRefSc] = getConstraintStaticDetails(obj)
            unit = 'mT';
            lbLim = 0;
            ubLim = Inf;
            usesLbUb = true;
            usesCelBody = false;
            usesRefSc = false;
        end

        function addConstraintTf = openEditConstraintUI(obj, lvdData)
            [~, tanks] = lvdData.launchVehicle.getTanksListBoxStr();
            if(numel(tanks) >= 1)
%                 addConstraintTf = lvd_EditTankConstraintGUI(obj, lvdData);

                output = AppDesignerGUIOutput({false});
                lvd_EditTankConstraintGUI_App(obj, lvdData, output);
                addConstraintTf = output.output{1};
            else
                errordlg('There are currently no tanks assigned to the launch vehicle in this scenario.  Add at least one tank first.');

                addConstraintTf = false;
            end
        end
    end

    methods(Static)
        function constraint = getDefaultConstraint(~, ~)
            constraint = TankMassConstraint(LaunchVehicleTank.empty(1,0), LaunchVehicleEvent.empty(1,0), 0, 0);
        end
    end
end


function [gKern, gVarmeans, gVarcovars, gInd] = rbfardjitVardistPsi1Gradient(rbfard2Kern, vardist, Z, covGrad, learnInducing)

% RBFARDJITVARDISTPSI1GRADIENT description.

% VARGPLVM

if nargin < 5
    learnInducing = 1;
end

 [gKern, gVarmeans, gVarcovars, gInd] = rbfard2VardistPsi1Gradient(rbfard2Kern, vardist, Z, covGrad, learnInducing);


%% Feature homography based planar tracking
%
% Example of using features2d framework for interactive video homography
% matching. ORB features and FLANN matcher are used. The actual tracking is
% implemented by |PlaneTracker| class.
%
% Inspired by <http://www.youtube.com/watch?v=-ZNYoL8rzPY>
%
% Video: <http://www.youtube.com/watch?v=FirtmYcC0Vc>
%
% Select a textured planar object to track by drawing a box with a mouse.
%
% Sources:
%
% * <https://github.com/opencv/opencv/blob/3.3.1/samples/python/feature_homography.py>
%

function feature_homography_track_demo(vid)
    % video file, and a default target to track [x,y,w,h]
    win = [];
    if nargin < 1
        vid = fullfile(mexopencv.root(), 'test', 'blais.mp4');
        assert(exist(vid, 'file') == 2, 'Missing video file');
        if true
            win = [135 165 285 175];  % face
        else
            win = [136 0 366 433];    % book
        end
    elseif isempty(vid)
        vid = 0;
    end

    % open video feed, and get first frame
    cap = cv.VideoCapture(vid);
    pause(1);
    assert(cap.isOpened(), 'Failed to open video');
    frame = cap.read();
    assert(~isempty(frame), 'Failed to read frames');

    % prepare plot
    paused = false;
    tframe = zeros(size(frame), class(frame));  % target frame + drawings
    hImg = imshow([frame, tframe]);

    % create and initialize tracker
    tracker = PlaneTracker();
    if ~isempty(win)
        onRect(win);
    end

    % create ROI region selector
    if ~mexopencv.isOctave()
        onHelp();
        roi = RectSelector(hImg);
        roi.clip = true;
        roi.callback = @onRect;
    else
        %HACK: RectSelector not Octave compatible
        %HACK: function handle to nested function not supported in Octave
        roi = struct('isDragging',@()false);
    end

    % listen to keyboard input
    if ~mexopencv.isOctave()
        %HACK: function handle to nested function not supported in Octave
        set(ancestor(hImg,'figure'), 'WindowKeyPressFcn',@onType);
    end

    % main loop
    while ishghandle(hImg)
        playing = ~paused && ~roi.isDragging();
        if playing
            % read new frame
            frame = cap.read();
            if isempty(frame), break; end
        end
        out = [frame, tframe];

        % track and draw keypoints and boundary of target in new frame
        if playing
            tracked = tracker.track(frame);
            if ~isempty(tracked)
                tr = tracked(1);
                out = cv.circle(out, tr.pt1, 2, 'Color',[255 0 0]);
                out = cv.polylines(out, tr.quad, 'Closed',true, ...
                    'Color',[0 255 0], 'Thickness',2);
                % draw matches
                out = cv.line(out, ...
                    bsxfun(@plus, tr.pt0, [size(frame,2) 0]), tr.pt1, ...
                    'Color',[0 0 255]);
            end
        end

        % display result
        set(hImg, 'CData',out);
        if playing
            drawnow;
        else
            pause(0.1);  % slow down a bit if paused
        end
    end
    cap.release();
    if isobject(roi), delete(roi); end

    % --- Callback functions ---

    function onRect(rect)
        %ONRECT  Callback for ROI selector
        %
        %     onRect(rect)
        %
        % ## Input
        % * __rect__ selected rectangle [x,y,w,h], or empty
        %

        if isempty(rect), return; end

        % selection must be made in left image (current frame)
        rect = cv.Rect.intersect(rect, [0 0 size(frame,2) size(frame,1)]);
        if cv.Rect.area(rect) < 1, return; end

        % track new target
        disp('New target...')
        tracker.clear();
        tracker.addTarget(frame, rect);

        % draw keypoints and boundary in target frame
        if ~isempty(tracker.targets)
            t = tracker.targets(1);
            tframe = cv.drawKeypoints(t.image, t.kpts, 'Color',[255 0 0]);
            tframe = cv.rectangle(tframe, t.rect(1:2), t.rect(3:4), ...
                'Color',[0 255 0], 'Thickness',2);
        else
            tframe(:) = 0;
        end

        % un-pause
        paused = false;
    end

    function onType(hfig, e)
        %ONTYPE  Event handler for key press on figure

        switch e.Key
            case {'q', 'escape'}
                close(hfig);

            case 'h'
                onHelp();

            case {'space', 'p'}
                disp('Toggle pause...');
                paused = ~paused;

            case {'c', 'r'}
                disp('Clearing tracker...');
                tracker.clear();
                tframe(:) = 0;
        end
    end

    function onHelp()
        %ONHELP  Display usage help dialog

        h = helpdlg({
            'Select object(s) to track using the mouse.'
            'Hot keys:'
            '  q - quit'
            '  h - help'
            '  p - pause'
            '  c - clear targets'
        });

        % wait for user to accept dialog
        set(h, 'WindowStyle','modal');
        waitfor(h);
    end
end


%  internal function

%  'xform_nii.m' is an internal function called by "load_nii.m", so
%  you do not need run this program by yourself. It does simplified
%  NIfTI sform/qform affine transform, and supports some of the
%  affine transforms, including translation, reflection, and
%  orthogonal rotation (N*90 degree).
%
%  For other affine transforms, e.g. any degree rotation, shearing
%  etc. you will have to use the included 'reslice_nii.m' program
%  to reslice the image volume. 'reslice_nii.m' is not called by
%  any other program, and you have to run 'reslice_nii.m' explicitly
%  for those NIfTI files that you want to reslice them.
%
%  Since 'xform_nii.m' does not involve any interpolation or any
%  slice change, the original image volume is supposed to be
%  untouched, although it is translated, reflected, or even
%  orthogonally rotated, based on the affine matrix in the
%  NIfTI header.
%
%  However, the affine matrix in the header of a lot NIfTI files
%  contain slightly non-orthogonal rotation. Therefore, optional
%  input parameter 'tolerance' is used to allow some distortion
%  in the loaded image for any non-orthogonal rotation or shearing
%  of NIfTI affine matrix. If you set 'tolerance' to 0, it means
%  that you do not allow any distortion. If you set 'tolerance' to
%  1, it means that you do not care any distortion. The image will
%  fail to be loaded if it can not be tolerated. The tolerance will
%  be set to 0.1 (10%), if it is default or empty.
%
%  Because 'reslice_nii.m' has to perform 3D interpolation, it can
%  be slow depending on image size and affine matrix in the header.
%
%  After you perform the affine transform, the 'nii' structure
%  generated from 'xform_nii.m' or new NIfTI file created from
%  'reslice_nii.m' will be in RAS orientation, i.e. X axis from
%  Left to Right, Y axis from Posterior to Anterior, and Z axis
%  from Inferior to Superior.
%
%  NOTE: This function should be called immediately after load_nii.
%
%  Usage: [ nii, idem? ] = xform_nii(nii, [tolerance], [preferredForm])
%
%  nii	- NIFTI structure (returned from load_nii)
%
%  tolerance (optional) - distortion allowed for non-orthogonal rotation
%	or shearing in NIfTI affine matrix. It will be set to 0.1 (10%),
%	if it is default or empty.
%
%  preferredForm (optional)  -  selects which transformation from voxels
%	to RAS coordinates; values are s,q,S,Q.  Lower case s,q indicate
%	"prefer sform or qform, but use others if preferred not present".
%	Upper case indicate the program is forced to use the specificied
%	transform or fail loading.  'preferredForm' will be 's', if it is
%	default or empty.	- Jeff Gunter
%
%  NIFTI data format can be found on: http://nifti.nimh.nih.gov
%
%  - Jimmy Shen (jimmy@rotman-baycrest.on.ca)
%
function [nii, idem] = xform_nii(nii, tolerance, preferredForm)

%  save a copy of the header as it was loaded.  This is the
%  header before any sform, qform manipulation is done.
%
nii.original.hdr = nii.hdr;

if ~exist('tolerance','var') | isempty(tolerance)
    tolerance = 0.1;
elseif(tolerance<=0)
    tolerance = eps;
end

if ~exist('preferredForm','var') | isempty(preferredForm)
    preferredForm= 's';				% Jeff
end

%  if scl_slope field is nonzero, then each voxel value in the
%  dataset should be scaled as: y = scl_slope * x + scl_inter
%  I bring it here because hdr will be modified by change_hdr.
%
if nii.hdr.dime.scl_slope ~= 0 && ...
        ismember(nii.hdr.dime.datatype, [2,4,8,16,64,256,512,768]) && ...
        (nii.hdr.dime.scl_slope ~= 1 | nii.hdr.dime.scl_inter ~= 0)

    nii.img = ...
        nii.hdr.dime.scl_slope * double(nii.img) + nii.hdr.dime.scl_inter;

    if nii.hdr.dime.datatype == 64

        nii.hdr.dime.datatype = 64;
        nii.hdr.dime.bitpix = 64;
    else
        nii.img = single(nii.img);

        nii.hdr.dime.datatype = 16;
        nii.hdr.dime.bitpix = 32;
    end

    nii.hdr.dime.glmax = max(double(nii.img(:)));
    nii.hdr.dime.glmin = min(double(nii.img(:)));

    %  set scale to non-use, because it is applied in xform_nii
    %
    nii.hdr.dime.scl_slope = 0;

end

%  However, the scaling is to be ignored if datatype is DT_RGB24.

%  If datatype is a complex type, then the scaling is to be applied
%  to both the real and imaginary parts.
%
if nii.hdr.dime.scl_slope ~= 0 && ...
        ismember(nii.hdr.dime.datatype, [32,1792])

    nii.img = ...
        nii.hdr.dime.scl_slope * double(nii.img) + nii.hdr.dime.scl_inter;

    if nii.hdr.dime.datatype == 32
        nii.img = single(nii.img);
    end

    nii.hdr.dime.glmax = max(double(nii.img(:)));
    nii.hdr.dime.glmin = min(double(nii.img(:)));

    %  set scale to non-use, because it is applied in xform_nii
    %
    nii.hdr.dime.scl_slope = 0;

end

%  There is no need for this program to transform Analyze data
%
if nii.filetype == 0 && exist([nii.fileprefix '.mat'],'file')
    load([nii.fileprefix '.mat']);	% old SPM affine matrix
    R=M(1:3,1:3);
    T=M(1:3,4);
    T=R*ones(3,1)+T;
    M(1:3,4)=T;
    nii.hdr.hist.qform_code=0;
    nii.hdr.hist.sform_code=1;
    nii.hdr.hist.srow_x=M(1,:);
    nii.hdr.hist.srow_y=M(2,:);
    nii.hdr.hist.srow_z=M(3,:);
elseif nii.filetype == 0
    nii.hdr.hist.rot_orient = [];
    nii.hdr.hist.flip_orient = [];
    return;				% no sform/qform for Analyze format
end

hdr = nii.hdr;

[hdr,orient]=change_hdr(hdr,tolerance,preferredForm);

%  flip and/or rotate image data
%
if ~isequal(orient, [1 2 3])
    idem=false;
    old_dim = hdr.dime.dim([2:4]);

    %  More than 1 time frame
    %
    if ndims(nii.img) > 3
        pattern = 1:prod(old_dim);
    else
        pattern = [];
    end

    if ~isempty(pattern)
        pattern = reshape(pattern, old_dim);
    end

    %  calculate for rotation after flip
    %
    rot_orient = mod(orient + 2, 3) + 1;

    %  do flip:
    %
    flip_orient = orient - rot_orient;

    for i = 1:3
        if flip_orient(i)
            if ~isempty(pattern)
                pattern = flipdim(pattern, i);
            else
                nii.img = flipdim(nii.img, i);
            end
        end
    end

    %  get index of orient (rotate inversely)
    %
    [tmp rot_orient] = sort(rot_orient);

    new_dim = old_dim;
    new_dim = new_dim(rot_orient);
    hdr.dime.dim([2:4]) = new_dim;

    new_pixdim = hdr.dime.pixdim([2:4]);
    new_pixdim = new_pixdim(rot_orient);
    hdr.dime.pixdim([2:4]) = new_pixdim;

    %  re-calculate originator
    %
    tmp = hdr.hist.originator([1:3]);
    tmp = tmp(rot_orient);
    flip_orient = flip_orient(rot_orient);

    for i = 1:3
        if flip_orient(i) && ~isequal(tmp(i), 0)
            tmp(i) = new_dim(i) - tmp(i) + 1;
        end
    end

    hdr.hist.originator([1:3]) = tmp;
    hdr.hist.rot_orient = rot_orient;
    hdr.hist.flip_orient = flip_orient;

    %  do rotation:
    %
    if ~isempty(pattern)
        pattern = permute(pattern, rot_orient);
        pattern = pattern(:);

        if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 | ...
                hdr.dime.datatype == 128 | hdr.dime.datatype == 511

            tmp = reshape(nii.img(:,:,:,1), [prod(new_dim) hdr.dime.dim(5:8)]);
            tmp = tmp(pattern, :);
            nii.img(:,:,:,1) = reshape(tmp, [new_dim       hdr.dime.dim(5:8)]);

            tmp = reshape(nii.img(:,:,:,2), [prod(new_dim) hdr.dime.dim(5:8)]);
            tmp = tmp(pattern, :);
            nii.img(:,:,:,2) = reshape(tmp, [new_dim       hdr.dime.dim(5:8)]);

            if hdr.dime.datatype == 128 | hdr.dime.datatype == 511
                tmp = reshape(nii.img(:,:,:,3), [prod(new_dim) hdr.dime.dim(5:8)]);
                tmp = tmp(pattern, :);
                nii.img(:,:,:,3) = reshape(tmp, [new_dim       hdr.dime.dim(5:8)]);
            end

        else
            nii.img = reshape(nii.img, [prod(new_dim) hdr.dime.dim(5:8)]);
            nii.img = nii.img(pattern, :);
            nii.img = reshape(nii.img, [new_dim       hdr.dime.dim(5:8)]);
        end
    else
        if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 | ...
                hdr.dime.datatype == 128 | hdr.dime.datatype == 511

            nii.img(:,:,:,1) = permute(nii.img(:,:,:,1), rot_orient);
            nii.img(:,:,:,2) = permute(nii.img(:,:,:,2), rot_orient);

            if hdr.dime.datatype == 128 | hdr.dime.datatype == 511
                nii.img(:,:,:,3) = permute(nii.img(:,:,:,3), rot_orient);
            end
        else
            nii.img = permute(nii.img, rot_orient);
        end
    end
else
    idem=true;
    hdr.hist.rot_orient = [];
    hdr.hist.flip_orient = [];

end

nii.hdr = hdr;

return;					% xform_nii

%-----------------------------------------------------------------------
function [hdr, orient] = change_hdr(hdr, tolerance, preferredForm)

orient = [1 2 3];
affine_transform = 1;

%  NIFTI can have both sform and qform transform. This program
%  will check sform_code prior to qform_code by default.
%
%  If user specifys "preferredForm", user can then choose the
%  priority.					- Jeff
%
useForm=[];					% Jeff

if isequal(preferredForm,'S')
    if isequal(hdr.hist.sform_code,0)
        error('User requires sform, sform not set in header');
    else
        useForm='s';
    end
end						% Jeff

if isequal(preferredForm,'Q')
    if isequal(hdr.hist.qform_code,0)
        error('User requires qform, qform not set in header');
    else
        useForm='q';
    end
end						% Jeff

if isequal(preferredForm,'s')
    if hdr.hist.sform_code > 0
        useForm='s';
    elseif hdr.hist.qform_code > 0
        useForm='q';
    end
end						% Jeff

if isequal(preferredForm,'q')
    if hdr.hist.qform_code > 0
        useForm='q';
    elseif hdr.hist.sform_code > 0
        useForm='s';
    end
end						% Jeff

if isequal(useForm,'s')
    R = [hdr.hist.srow_x(1:3)
        hdr.hist.srow_y(1:3)
        hdr.hist.srow_z(1:3)];

    T = [hdr.hist.srow_x(4)
        hdr.hist.srow_y(4)
        hdr.hist.srow_z(4)];

    if det(R) == 0 || ~isequal(R(find(R)), sum(R)')
        hdr.hist.old_affine = [ [R;[0 0 0]] [T;1] ];
        R_sort = sort(abs(R(:)));
        if tolerance ==1
            R(2:3,1) = 0; R([1,3],2) = 0; R(1:2,3) = 0;
        else
            R( find( abs(R) < tolerance*min(R_sort(end-2:end)) ) ) = 0;
        end
        hdr.hist.new_affine = [ [R;[0 0 0]] [T;1] ];

        if det(R) == 0 || ~isequal(R(find(R)), sum(R)')
            msg = [char(10) char(10) '   Non-orthogonal rotation or shearing '];
            msg = [msg 'found inside the affine matrix' char(10)];
            msg = [msg '   in this NIfTI file. You have 3 options:' char(10) char(10)];
            msg = [msg '   1. Using included ''reslice_nii.m'' program to reslice the NIfTI' char(10)];
            msg = [msg '      file. I strongly recommand this, because it will not cause' char(10)];
            msg = [msg '      negative effect, as long as you remember not to do slice' char(10)];
            msg = [msg '      time correction after using ''reslice_nii.m''.' char(10) char(10)];
            msg = [msg '   2. Using included ''load_untouch_nii.m'' program to load image' char(10)];
            msg = [msg '      without applying any affine geometric transformation or' char(10)];
            msg = [msg '      voxel intensity scaling. This is only for people who want' char(10)];
            msg = [msg '      to do some image processing regardless of image orientation' char(10)];
            msg = [msg '      and to save data back with the same NIfTI header.' char(10) char(10)];
            msg = [msg '   3. Increasing the tolerance to allow more distortion in loaded' char(10)];
            msg = [msg '      image, but I don''t suggest this.' char(10) char(10)];
            msg = [msg '   To get help, please type:' char(10) char(10) '   help reslice_nii.m' char(10)];
            msg = [msg '   help load_untouch_nii.m' char(10) '   help load_nii.m'];
            error(msg);
        end
    end

elseif isequal(useForm,'q')
    b = hdr.hist.quatern_b;
    c = hdr.hist.quatern_c;
    d = hdr.hist.quatern_d;

    if 1.0-(b*b+c*c+d*d) < 0
        if abs(1.0-(b*b+c*c+d*d)) < 1e-5
            a = 0;
        else
            error('Incorrect quaternion values in this NIFTI data.');
        end
    else
        a = sqrt(1.0-(b*b+c*c+d*d));
    end

    qfac = hdr.dime.pixdim(1);
    if qfac==0, qfac = 1; end
    i = hdr.dime.pixdim(2);
    j = hdr.dime.pixdim(3);
    k = qfac * hdr.dime.pixdim(4);

    R = [a*a+b*b-c*c-d*d     2*b*c-2*a*d        2*b*d+2*a*c
        2*b*c+2*a*d         a*a+c*c-b*b-d*d    2*c*d-2*a*b
        2*b*d-2*a*c         2*c*d+2*a*b        a*a+d*d-c*c-b*b];

    T = [hdr.hist.qoffset_x
        hdr.hist.qoffset_y
        hdr.hist.qoffset_z];

    %  qforms are expected to generate rotation matrices R which are
    %  det(R) = 1; we'll make sure that happens.
    %
    %  now we make the same checks as were done above for sform data
    %  BUT we do it on a transform that is in terms of voxels not mm;
    %  after we figure out the angles and squash them to closest
    %  rectilinear direction. After that, the voxel sizes are then
    %  added.
    %
    %  This part is modified by Jeff Gunter.
    %
    if det(R) == 0 | ~isequal(R(find(R)), sum(R)')

        %  det(R) == 0 is not a common trigger for this ---
        %  R(find(R)) is a list of non-zero elements in R; if that
        %  is straight (not oblique) then it should be the same as
        %  columnwise summation. Could just as well have checked the
        %  lengths of R(find(R)) and sum(R)' (which should be 3)
        %
        hdr.hist.old_affine = [ [R * diag([i j k]);[0 0 0]] [T;1] ];
        R_sort = sort(abs(R(:)));
        if tolerance ==1
            R(2:3,1) = 0; R([1,3],2) = 0; R(1:2,3) = 0;
        else
            R( find( abs(R) < tolerance*min(R_sort(end-2:end)) ) ) = 0;
        end
        R = R * diag([i j k]);
        hdr.hist.new_affine = [ [R;[0 0 0]] [T;1] ];

        if det(R) == 0 | ~isequal(R(find(R)), sum(R)')
            msg = [char(10) char(10) '   Non-orthogonal rotation or shearing '];
            msg = [msg 'found inside the affine matrix' char(10)];
            msg = [msg '   in this NIfTI file. You have 3 options:' char(10) char(10)];
            msg = [msg '   1. Using included ''reslice_nii.m'' program to reslice the NIfTI' char(10)];
            msg = [msg '      file. I strongly recommand this, because it will not cause' char(10)];
            msg = [msg '      negative effect, as long as you remember not to do slice' char(10)];
            msg = [msg '      time correction after using ''reslice_nii.m''.' char(10) char(10)];
            msg = [msg '   2. Using included ''load_untouch_nii.m'' program to load image' char(10)];
            msg = [msg '      without applying any affine geometric transformation or' char(10)];
            msg = [msg '      voxel intensity scaling. This is only for people who want' char(10)];
            msg = [msg '      to do some image processing regardless of image orientation' char(10)];
            msg = [msg '      and to save data back with the same NIfTI header.' char(10) char(10)];
            msg = [msg '   3. Increasing the tolerance to allow more distortion in loaded' char(10)];
            msg = [msg '      image, but I don''t suggest this.' char(10) char(10)];
            msg = [msg '   To get help, please type:' char(10) char(10) '   help reslice_nii.m' char(10)];
            msg = [msg '   help load_untouch_nii.m' char(10) '   help load_nii.m'];
            error(msg);
        end

    else
        R = R * diag([i j k]);
    end					% 1st det(R)

else
    affine_transform = 0;	% no sform or qform transform
end

if affine_transform == 1
    voxel_size = abs(sum(R,1));
    inv_R = inv(R);
    originator = inv_R*(-T)+1;
    orient = get_orient(inv_R);

    %  modify pixdim and originator
    %
    %hdr.dime.pixdim(2:4) = voxel_size;
    hdr.hist.originator(1:3) = originator;

    %  set sform or qform to non-use, because they have been
    %  applied in xform_nii
    %
    hdr.hist.qform_code = 0;
    hdr.hist.sform_code = 0;
end

%  apply space_unit to pixdim if not 1 (mm)
%
space_unit = get_units(hdr);

if space_unit ~= 1
    hdr.dime.pixdim(2:4) = hdr.dime.pixdim(2:4) * space_unit;

    %  set space_unit of xyzt_units to millimeter, because
    %  voxel_size has been re-scaled
    %
    hdr.dime.xyzt_units = char(bitset(hdr.dime.xyzt_units,1,0));
    hdr.dime.xyzt_units = char(bitset(hdr.dime.xyzt_units,2,1));
    hdr.dime.xyzt_units = char(bitset(hdr.dime.xyzt_units,3,0));
end

hdr.dime.pixdim = abs(hdr.dime.pixdim);

return;					% change_hdr

%-----------------------------------------------------------------------
function orient = get_orient(R)

orient = [];

for i = 1:3
    switch find(R(i,:)) * sign(sum(R(i,:)))
        case 1
            orient = [orient 1];		% Left to Right
        case 2
            orient = [orient 2];		% Posterior to Anterior
        case 3
            orient = [orient 3];		% Inferior to Superior
        case -1
            orient = [orient 4];		% Right to Left
        case -2
            orient = [orient 5];		% Anterior to Posterior
        case -3
            orient = [orient 6];		% Superior to Inferior
    end
end

return;					% get_orient

%-----------------------------------------------------------------------
function [space_unit, time_unit] = get_units(hdr)

switch bitand(hdr.dime.xyzt_units, 7)	% mask with 0x07
    case 1
        space_unit = 1e+3;		% meter, m
    case 3
        space_unit = 1e-3;		% micrometer, um
    otherwise
        space_unit = 1;			% millimeter, mm
end

switch bitand(hdr.dime.xyzt_units, 56)	% mask with 0x38
    case 16
        time_unit = 1e-3;			% millisecond, ms
    case 24
        time_unit = 1e-6;			% microsecond, us
    otherwise
        time_unit = 1;			% second, s
end

return;					% get_units