function [source] = ft_dipolefitting(cfg, data)

% FT_DIPOLEFITTING perform grid search and non-linear fit with one or multiple
% dipoles and try to find the location where the dipole model is best able
% to explain the measured EEG or MEG topography.
%
% This function will initially scan the whole brain with a single dipole on
% a regular coarse grid, and subsequently start at the most optimal location
% with a non-linear search. Alternatively you can specify the initial
% location of the dipole(s) and the non-linear search will start from there.
%
% Use as
%   [source] = ft_dipolefitting(cfg, data)
%
% The configuration has the following general fields
%   cfg.numdipoles  = number, default is 1
%   cfg.symmetry    = 'x', 'y' or 'z' symmetry for two dipoles, can be empty (default = [])
%   cfg.channel     = Nx1 cell-array with selection of channels (default = 'all'),
%                     see FT_CHANNELSELECTION for details
%   cfg.gridsearch  = 'yes' or 'no', perform global search for initial
%                     guess for the dipole parameters (default = 'yes')
%   cfg.nonlinear   = 'yes' or 'no', perform nonlinear search for optimal
%                     dipole parameters (default = 'yes')
%
% If you start with a grid search, the complete grid with dipole positions and
% optionally precomputed leadfields is constructed using FT_PREPARE_SOURCEMODEL. It
% can be specified as as a regular 3-D grid that is aligned with the axes of the head
% coordinate system using
%   cfg.xgrid               = vector (e.g. -20:1:20) or 'auto' (default = 'auto')
%   cfg.ygrid               = vector (e.g. -20:1:20) or 'auto' (default = 'auto')
%   cfg.zgrid               = vector (e.g.   0:1:20) or 'auto' (default = 'auto')
%   cfg.resolution          = number (e.g. 1 cm) for automatic grid generation
% If the source model destribes a triangulated cortical sheet, it is described as
%   cfg.sourcemodel.pos     = N*3 matrix with the vertex positions of the cortical sheet
%   cfg.sourcemodel.tri     = M*3 matrix that describes the triangles connecting the vertices
% Alternatively the position of a few dipoles at locations of interest can be
% user-specified, for example obtained from an anatomical or functional MRI
%   cfg.sourcemodel.pos     = N*3 matrix with position of each source
%   cfg.sourcemodel.inside  = N*1 vector with boolean value whether grid point is inside brain (optional)
%   cfg.sourcemodel.dim     = [Nx Ny Nz] vector with dimensions in case of 3-D grid (optional)
%
% If you do not start with a grid search, you have to give a starting location
% for the nonlinear search
%   cfg.dip.pos     = initial dipole position, matrix of Ndipoles x 3
%
% The conventional approach is to fit dipoles to event-related averages, which
% within FieldTrip can be obtained from the FT_TIMELOCKANALYSIS or from
% the FT_TIMELOCKGRANDAVERAGE function. This has the additional options
%   cfg.latency     = [begin end] in seconds or 'all' (default = 'all')
%   cfg.model       = 'moving' or 'regional'
% A moving dipole model has a different position (and orientation) for each
% timepoint, or for each component. A regional dipole model has the same
% position for each timepoint or component, and a different orientation.
%
% You can also fit dipoles to the spatial topographies of an independent
% component analysis, obtained from the FT_COMPONENTANALYSIS function.
% This has the additional options
%   cfg.component   = array with numbers (can be empty -> all)
%
% You can also fit dipoles to the spatial topographies that are present
% in the data in the frequency domain, which can be obtained using the
% FT_FREQANALYSIS function. This has the additional options
%   cfg.frequency   = single number (in Hz)
%
% Low level details of the fitting can be specified in the cfg.dipfit structure
%   cfg.dipfit.display      = level of display, can be 'off', 'iter', 'notify' or 'final' (default = 'iter')
%   cfg.dipfit.optimfun     = function to use, can be 'fminsearch' or 'fminunc' (default is determined automatic)
%   cfg.dipfit.maxiter      = maximum number of function evaluations allowed (default depends on the optimfun)
%   cfg.dipfit.checkinside  = boolean, check that the dipole remains in the source compartment (default = false)
%
% Optionally, you can modify the leadfields by reducing the rank, i.e. remove the weakest orientation
%   cfg.reducerank      = 'no', or number (default = 3 for EEG, 2 for MEG)
%   cfg.normalize       = 'no', 'yes' or 'column'
%   cfg.normalizeparam  = parameter for depth normalization (default = 0.5)
%   cfg.weight          = number or 1xN vector, weight for each dipole position to compensate for the size of the corresponding patch (default = 1)
%   cfg.backproject     = 'yes' (default) or 'no', in the case of a rank reduction this parameter determines whether the result will be backprojected onto the original subspace
%
% The volume conduction model of the head should be specified as
%   cfg.headmodel     = structure with volume conduction model, see FT_PREPARE_HEADMODEL
%
% The EEG or MEG sensor positions can be present in the data or can be specified as
%   cfg.elec          = structure with electrode positions or filename, see FT_READ_SENS
%   cfg.grad          = structure with gradiometer definition or filename, see FT_READ_SENS
%
% To facilitate data-handling and distributed computing you can use
%   cfg.inputfile   =  ...
%   cfg.outputfile  =  ...
% If you specify one of these (or both) the input data will be read from a *.mat
% file on disk and/or the output data will be written to a *.mat file. These mat
% files should contain only a single variable, corresponding with the
% input/output structure.
%
% See also FT_SOURCEANALYSIS, FT_PREPARE_LEADFIELD, FT_PREPARE_HEADMODEL

% TODO change the output format, more suitable would be something like:
% dip.label
% dip.time
% dip.avg (instead of Vdata)
% dip.dip.pos
% dip.dip.mom
% dip.dip.model, or dip.dip.avg
% dip.dimord

% Undocumented local options:
%   cfg.dipfit.constr   = Source model constraints, depends on cfg.symmetry
% Optionally, you can include a noise covariance structure to sphere the data (is useful when using both
% magnetometers and gradiometers to fit your dipole)
%   cfg.dipfit.noisecov       = noise covariance matrix, see e.g. FT_TIMELOCK_ANALYSIS

% Copyright (C) 2004-2013, 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$

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

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

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

% check if the input data is valid for this function
data = ft_checkdata(data, 'datatype', {'comp', 'timelock', 'freq'}, 'feedback', 'yes');

% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'renamed', {'elecfile', 'elec'});
cfg = ft_checkconfig(cfg, 'renamed', {'gradfile', 'grad'});
cfg = ft_checkconfig(cfg, 'renamed', {'optofile', 'opto'});
cfg = ft_checkconfig(cfg, 'renamed', {'hdmfile', 'headmodel'});
cfg = ft_checkconfig(cfg, 'renamed', {'vol',     'headmodel'});
cfg = ft_checkconfig(cfg, 'renamed', {'grid',    'sourcemodel'});

% get the defaults
cfg.channel         = ft_getopt(cfg, 'channel', 'all');
cfg.component       = ft_getopt(cfg, 'component');        % for comp input
cfg.frequency       = ft_getopt(cfg, 'frequency');        % for freq input
cfg.latency         = ft_getopt(cfg, 'latency', 'all');   % for timeclock input
cfg.feedback        = ft_getopt(cfg, 'feedback', 'text');
cfg.gridsearch      = ft_getopt(cfg, 'gridsearch', 'yes');
cfg.nonlinear       = ft_getopt(cfg, 'nonlinear', 'yes');
cfg.symmetry        = ft_getopt(cfg, 'symmetry');
cfg.dipfit          = ft_getopt(cfg, 'dipfit', []);     % the default for this is handled below
% the following options are for on-the-fly leadfield computation
cfg.reducerank      = ft_getopt(cfg, 'reducerank', []); % the default for this is handled below
cfg.normalize       = ft_getopt(cfg, 'normalize');      % this is better not used in dipole fitting
cfg.normalizeparam  = ft_getopt(cfg, 'normalizeparam'); % this is better not used in dipole fitting
cfg.backproject     = ft_getopt(cfg, 'backproject');    % this is better not used in dipole fitting
cfg.weight          = ft_getopt(cfg, 'weight');         % this is better not used in dipole fitting

cfg = ft_checkconfig(cfg, 'renamed', {'tightgrid', 'tight'}); % this is moved to cfg.sourcemodel.tight by the subsequent createsubcfg
cfg = ft_checkconfig(cfg, 'renamed', {'sourceunits', 'unit'}); % this is moved to cfg.sourcemodel.unit by the subsequent createsubcfg

% put the low-level options pertaining to the sourcemodel in their own field
cfg = ft_checkconfig(cfg, 'createsubcfg', {'sourcemodel'});
% move some fields from cfg.sourcemodel back to the top-level configuration
cfg = ft_checkconfig(cfg, 'createtopcfg', {'sourcemodel'});

% the default for this depends on the data type
if ~isfield(cfg, 'model')
  if ~isempty(cfg.component)
    % each component is fitted independently
    cfg.model = 'moving';
  elseif ~isempty(cfg.frequency)
    % fit the data with a dipole at one location
    cfg.model = 'regional';
  elseif ~isempty(cfg.latency)
    % fit the data with a dipole at one location
    cfg.model = 'regional';
  end
end

if ~isfield(cfg, 'numdipoles')
  if isfield(cfg, 'dip')
    cfg.numdipoles = size(cfg.dip(1).pos,1);
  else
    cfg.numdipoles = 1;
  end
end

% set up the symmetry constraints
if ~isempty(cfg.symmetry)
  if cfg.numdipoles~=2
    ft_error('symmetry constraints are only supported for two-dipole models');
  elseif strcmp(cfg.symmetry, 'x')
    % this structure is passed onto the low-level ft_dipole_fit function
    cfg.dipfit.constr.reduce = [1 2 3];         % select the parameters [x1 y1 z1]
    cfg.dipfit.constr.expand = [1 2 3 1 2 3];   % repeat them as [x1 y1 z1 x1 y1 z1]
    cfg.dipfit.constr.mirror = [1 1 1 -1 1 1];  % multiply each of them with 1 or -1, resulting in [x1 y1 z1 -x1 y1 z1]
  elseif strcmp(cfg.symmetry, 'y')
    % this structure is passed onto the low-level ft_dipole_fit function
    cfg.dipfit.constr.reduce = [1 2 3];         % select the parameters [x1 y1 z1]
    cfg.dipfit.constr.expand = [1 2 3 1 2 3];   % repeat them as [x1 y1 z1 x1 y1 z1]
    cfg.dipfit.constr.mirror = [1 1 1 1 -1 1];  % multiply each of them with 1 or -1, resulting in [x1 y1 z1 x1 -y1 z1]
  elseif strcmp(cfg.symmetry, 'z')
    % this structure is passed onto the low-level ft_dipole_fit function
    cfg.dipfit.constr.reduce = [1 2 3];         % select the parameters [x1 y1 z1]
    cfg.dipfit.constr.expand = [1 2 3 1 2 3];   % repeat them as [x1 y1 z1 x1 y1 z1]
    cfg.dipfit.constr.mirror = [1 1 1 1 1 -1];  % multiply each of them with 1 or -1, resulting in [x1 y1 z1 x1 y1 -z1]
  else
    ft_error('unrecognized symmetry constraint');
  end
elseif ~isfield(cfg, 'dipfit') || ~isfield(cfg.dipfit, 'constr')
  % no symmetry constraints have been specified
  cfg.dipfit.constr = [];
end

if ft_getopt(cfg.dipfit.constr, 'sequential', false) && strcmp(cfg.model, 'moving')
  ft_error('the moving dipole model does not combine with the sequential constraint')
  % see http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=3119
end

if isfield(data, 'topolabel')
  % this looks like a component analysis
  iscomp = 1;
  % transform the data into a representation on which the timelocked dipole fit can perform its trick
  data = comp2timelock(cfg, data);
else
  iscomp = 0;
end

if isfield(data, 'freq')
  % this looks like a frequency analysis
  isfreq = 1;
  % transform the data into a representation on which the timelocked dipole fit can perform its trick
  data = freq2timelock(cfg, data);
else
  isfreq = 0;
end

% prepare the volume conduction model and the sensor array
% this updates the configuration with the appropriate fields
[headmodel, sens, cfg] = prepare_headmodel(cfg, data);

% set the default for reducing the rank of the leadfields
if isempty(cfg.reducerank)
  if ft_senstype(sens, 'eeg')
    cfg.reducerank = 'no';    % for EEG
  elseif ft_senstype(sens, 'meg') && ft_headmodeltype(headmodel, 'infinite')
    cfg.reducerank = 'no';    % for MEG with a magnetic dipole, e.g. a HPI coil
  elseif ft_senstype(sens, 'meg')
    cfg.reducerank = 'yes';   % for MEG with a current dipole in a volume conductor
  end
end

% construct the options for the leadfield computation, the same options are also passed to DIPOLE_FIT
leadfieldopt = {};
leadfieldopt = ft_setopt(leadfieldopt, 'reducerank',     cfg.reducerank);
leadfieldopt = ft_setopt(leadfieldopt, 'normalize',      cfg.normalize);
leadfieldopt = ft_setopt(leadfieldopt, 'normalizeparam', cfg.normalizeparam);
leadfieldopt = ft_setopt(leadfieldopt, 'weight',         cfg.weight);
leadfieldopt = ft_setopt(leadfieldopt, 'backproject',    cfg.backproject);

% select the desired channels, ordered according to the sensor structure
[selsens, seldata] = match_str(sens.label, data.label);
% take the selected channels from the data structure
Vdata = data.avg(seldata, :);

% sphere the date using the noise covariance matrix supplied, if any
% this affects both the gridsearch and the nonlinear optimization
noisecov = ft_getopt(cfg.dipfit, 'noisecov');
if ~isempty(noisecov)
  [u, s] = svd(noisecov);
  tol = max(size(noisecov)) * eps(norm(s, inf));
  s = diag(s);
  r1 = sum(s > tol) + 1;
  s(1:(r1 - 1)) = 1 ./ sqrt(s(1:(r1 - 1)));
  s(r1:end)     = 0;
  sphere = diag(s) * u';
  % apply the sphering to the data
  Vdata = sphere * Vdata;
  % apply the sphering as a pre-multiplication to the sensor definition
  montage = [];
  montage.labelold = cfg.channel;
  montage.labelnew = cfg.channel;
  montage.tra = sphere;
  sens = ft_apply_montage(sens, montage, 'balancename', 'sphering');
end

if iscomp
  % select the desired component topographies
  Vdata = Vdata(:, cfg.component);
elseif isfreq
  % the desired frequencies have already been selected
  Vdata = Vdata(:, :);
else
  % select the desired latencies
  if ischar(cfg.latency) && strcmp(cfg.latency, 'all')
    cfg.latency = data.time([1 end]);
  end
  tbeg = nearest(data.time, cfg.latency(1));
  tend = nearest(data.time, cfg.latency(end));
  cfg.latency = [data.time(tbeg) data.time(tend)];
  Vdata = Vdata(:, tbeg:tend);
end

nchans = size(Vdata,1);
ntime  = size(Vdata,2);
Vmodel = zeros(nchans, ntime);
fprintf('selected %d channels\n', nchans);
fprintf('selected %d topographies\n', ntime);

if nchans<cfg.numdipoles*3
  ft_warning('not enough channels to perform a dipole fit');
end

if ntime<1
  ft_error('no spatial topography selected');
end

% check whether EEG is average referenced
if ft_senstype(sens, 'eeg')
  if any(rv(Vdata, avgref(Vdata))>0.001)
    ft_warning('the EEG data is not average referenced, correcting this');
  end
  Vdata = avgref(Vdata);
end

% set to zeros if no initial dipole was specified
if ~isfield(cfg, 'dip')
  cfg.dip.pos = zeros(cfg.numdipoles, 3);
  cfg.dip.mom = zeros(3*cfg.numdipoles, 1);
end

% set to zeros if no initial dipole position was specified
if ~isfield(cfg.dip, 'pos')
  cfg.dip.pos = zeros(cfg.numdipoles, 3);
end

% set to zeros if no initial dipole moment was specified
if ~isfield(cfg.dip, 'mom')
  cfg.dip.mom = zeros(3*cfg.numdipoles, 1);
end

% check the specified dipole model
if numel(cfg.dip.pos)~=cfg.numdipoles*3 || numel(cfg.dip.mom)~=cfg.numdipoles*3
  ft_error('inconsistent number of dipoles in configuration')
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% perform the dipole scan, this is usefull for generating an initial guess
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if strcmp(cfg.gridsearch, 'yes')
  % test whether we have a valid configuration for dipole scanning
  if cfg.numdipoles==1
    % this is ok
  elseif cfg.numdipoles==2 && ~isempty(cfg.dipfit.constr)
    % this is also ok
  elseif isfield(cfg.sourcemodel, 'pos') && size(cfg.sourcemodel.pos,2)==cfg.numdipoles*3
    % this is also ok
  else
    ft_error('dipole scanning is only possible for a single dipole or a symmetric dipole pair');
  end

  % copy all options that are potentially used in ft_prepare_sourcemodel
  tmpcfg           = keepfields(cfg, {'sourcemodel', 'mri', 'headshape', 'symmetry', 'smooth', 'threshold', 'spheremesh', 'inwardshift', 'xgrid' 'ygrid', 'zgrid', 'resolution', 'tight', 'warpmni', 'template', 'showcallinfo'});
  tmpcfg.headmodel = headmodel;
  if ft_senstype(sens, 'eeg')
    tmpcfg.elec = sens;
  elseif ft_senstype(sens, 'meg')
    tmpcfg.grad = sens;
  end
  % construct the dipole grid on which the gridsearch will be done
  sourcemodel = ft_prepare_sourcemodel(tmpcfg);

  ngrid = size(sourcemodel.pos,1);

  switch cfg.model
    case 'regional'
      sourcemodel.error = nan(ngrid, 1);
    case 'moving'
      sourcemodel.error = nan(ngrid, ntime);
    otherwise
      ft_error('unsupported cfg.model');
  end

  insideindx = find(sourcemodel.inside);
  ft_progress('init', cfg.feedback, 'scanning grid');
  for i=1:length(insideindx)
    ft_progress(i/length(insideindx), 'scanning grid location %d/%d\n', i, length(insideindx));
    thisindx = insideindx(i);
    if isfield(sourcemodel, 'leadfield')
      % reuse the previously computed leadfield
      lf = sourcemodel.leadfield{thisindx};
    else
      lf = ft_compute_leadfield(sourcemodel.pos(thisindx,:), sens, headmodel, leadfieldopt{:});
    end
    % the model is V=lf*mom+noise, therefore mom=pinv(lf)*V estimates the
    % dipole moment this makes the model potential U=lf*pinv(lf)*V and the
    % model error is norm(V-U) = norm(V-lf*pinv(lf)*V) = norm((eye-lf*pinv(lf))*V)
    if any(isnan(lf(:)))
      % this might happen if one of the dipole locations of the grid is
      % outside the brain compartment
      lf(:) = 0;
    end
    switch cfg.model
      case 'regional'
        % sum the error over all latencies
        sourcemodel.error(thisindx,1) = sum(sum(((eye(nchans)-lf*pinv(lf))*Vdata).^2));
      case 'moving'
        % remember the error for each latency independently
        sourcemodel.error(thisindx,:) = sum(((eye(nchans)-lf*pinv(lf))*Vdata).^2);
      otherwise
        ft_error('unsupported cfg.model');
    end % switch model
  end % looping over the grid
  ft_progress('close');

  switch cfg.model
    case 'regional'
      % find the source position with the minimum error
      [err, indx] = min(sourcemodel.error);
      dip.pos = sourcemodel.pos(indx,:);                % note that for a symmetric dipole pair this results in a vector
      dip.pos = reshape(dip.pos,3,cfg.numdipoles)';     % convert to a Nx3 array
      dip.mom = zeros(cfg.numdipoles*3,1);              % set the dipole moment to zero
      if cfg.numdipoles==1
        fprintf('found minimum after scanning on grid point [%g %g %g]\n', dip.pos(1), dip.pos(2), dip.pos(3));
      elseif cfg.numdipoles==2
        fprintf('found minimum after scanning on grid point [%g %g %g; %g %g %g]\n', dip.pos(1), dip.pos(2), dip.pos(3), dip.pos(4), dip.pos(5), dip.pos(6));
      end

    case 'moving'
      for t=1:ntime
        % find the source position with the minimum error
        [err, indx] = min(sourcemodel.error(:,t));
        dip(t).pos = sourcemodel.pos(indx,:);                        % note that for a symmetric dipole pair this results in a vector
        dip(t).pos = reshape(dip(t).pos,3,cfg.numdipoles)';   % convert to a Nx3 array
        dip(t).mom = zeros(cfg.numdipoles*3,1);               % set the dipole moment to zero
        if cfg.numdipoles==1
          fprintf('found minimum after scanning for topography %d on grid point [%g %g %g]\n', t, dip(t).pos(1), dip(t).pos(2), dip(t).pos(3));
        elseif cfg.numdipoles==2
          fprintf('found minimum after scanning for topography %d on grid point [%g %g %g; %g %g %g]\n', t, dip(t).pos(1), dip(t).pos(2), dip(t).pos(3), dip(t).pos(4), dip(t).pos(5), dip(t).pos(6));
        end
      end

    otherwise
      ft_error('unsupported cfg.model');
  end % switch model

elseif strcmp(cfg.gridsearch, 'no')
  % use the initial guess supplied in the configuration for the remainder
  switch cfg.model
    case 'regional'
      dip = struct(cfg.dip);      % ensure that it is a struct, not a config object
    case 'moving'
      for t=1:ntime
        dip(t) = struct(cfg.dip); % ensure that it is a struct, not a config object
      end
    otherwise
      ft_error('unsupported cfg.model');
  end % switch model

end % if gridsearch yes/no
% multiple dipoles can be represented either as a 1x(N*3) vector or as a Nx3 matrix,
% i.e. [x1 y1 z1 x2 y2 z2] or [x1 y1 z1; x2 y2 z2]
switch cfg.model
  case 'regional'
    dip = fixdipole(dip);
  case 'moving'
    for t=1:ntime
      dip(t) = fixdipole(dip(t));
    end
  otherwise
    ft_error('unsupported cfg.model');
end % switch model

% convert the structure with the additional low-level options into key-value pairs
dipfitopt = ft_cfg2keyval(cfg.dipfit);

% add the options for the leadfield computation
dipfitopt = ft_setopt(dipfitopt, 'reducerank',     cfg.reducerank);
dipfitopt = ft_setopt(dipfitopt, 'normalize',      cfg.normalize);
dipfitopt = ft_setopt(dipfitopt, 'normalizeparam', cfg.normalizeparam);
dipfitopt = ft_setopt(dipfitopt, 'weight',         cfg.weight);
dipfitopt = ft_setopt(dipfitopt, 'backproject',    cfg.backproject);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% perform the non-linear fit
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if strcmp(cfg.nonlinear, 'yes')
  switch cfg.model
    case 'regional'
      % perform the non-linear dipole fit for all latencies together
      % catch errors due to non-convergence
      try
        dip = dipole_fit(dip, sens, headmodel, Vdata, dipfitopt{:});
        success = 1;
        if cfg.numdipoles==1
          fprintf('found minimum after non-linear optimization on [%g %g %g]\n', dip.pos(1), dip.pos(2), dip.pos(3));
        elseif cfg.numdipoles==2
          fprintf('found minimum after non-linear optimization on [%g %g %g; %g %g %g]\n', dip.pos(1,1), dip.pos(1,2), dip.pos(1,3), dip.pos(2,1), dip.pos(2,2), dip.pos(2,3));
        end
      catch
        success = 0;
        disp(lasterr);
      end

    case 'moving'
      % perform the non-linear dipole fit for each latency independently
      % instead of using dip(t) = dipole_fit(dip(t),...), I am using temporary variables dipin and dipout
      % to prevent errors like "Subscripted assignment between dissimilar structures"
      dipin = dip;
      for t=1:ntime
        % catch errors due to non-convergence
        try
          dipout(t) = dipole_fit(dipin(t), sens, headmodel, Vdata(:,t), dipfitopt{:});
          success(t) = 1;
          if cfg.numdipoles==1
            fprintf('found minimum after non-linear optimization for topography %d on [%g %g %g]\n', t, dipout(t).pos(1), dipout(t).pos(2), dipout(t).pos(3));
          elseif cfg.numdipoles==2
            fprintf('found minimum after non-linear optimization for topography %d on [%g %g %g; %g %g %g]\n', t, dipout(t).pos(1,1), dipout(t).pos(1,2), dipout(t).pos(1,3), dipout(t).pos(2,1), dipout(t).pos(2,2), dipout(t).pos(2,3));
          end
        catch
          % keep the position and moment according to the initial guess
          dipout(t).pos = dipin(t).pos;
          dipout(t).mom = dipin(t).mom;
          success(t) = 0;
          disp(lasterr);
        end
      end
      dip = dipout;
      clear dipin dipout
    otherwise
      ft_error('unsupported cfg.model');
  end % switch model
end % if nonlinear

if strcmp(cfg.nonlinear, 'no')
  % the optimal dipole positions are either obtained from scanning
  % or from the initial configured specified by the user
  switch cfg.model
    case 'regional'
      success = 1;
    case 'moving'
      success = ones(1,ntime);
    otherwise
      ft_error('unsupported cfg.model');

  end % switch model
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% compute the model potential distribution and the residual variance
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
switch cfg.model
  case 'regional'
    if success
      % re-compute the leadfield in order to compute the model potential and dipole moment
      lf = ft_compute_leadfield(dip.pos, sens, headmodel, leadfieldopt{:});
      if isfield(dip, 'mom') && isfield(dip, 'ampl')
        % the orientation and amplitude have already been estimated, this applies to the case of a fixed dipole orientation
        dip.pot = (lf * dip.mom) * dip.ampl;
      else
        % compute all details of the final dipole model using linear estimation
        dip.mom = pinv(lf)*Vdata;
        dip.pot = lf*dip.mom;
      end
      dip.rv  = rv(Vdata, dip.pot);
      Vmodel  = dip.pot;
    end
  case 'moving'
    for t=1:ntime
      if success(t)
        % re-compute the leadfield in order to compute the model potential and dipole moment
        lf = ft_compute_leadfield(dip(t).pos, sens, headmodel, leadfieldopt{:});
        % compute all details of the final dipole model
        dip(t).mom = pinv(lf)*Vdata(:,t);
        dip(t).pot = lf*dip(t).mom;
        dip(t).rv  = rv(Vdata(:,t), dip(t).pot);
        Vmodel(:,t) = dip(t).pot;
      end
    end
  otherwise
    ft_error('unsupported cfg.model');
end % switch model

switch cfg.model
  case 'regional'
    if isfreq
      % the matrix with the dipole moment is encrypted and cannot be interpreted straight away
      % reconstruct the frequency representation of the data at the source level
      if isfield(dip, 'mom') && isfield(dip, 'ampl')
        % this applies to the case of a fixed dipole orientation
        [dip.pow, dip.csd, dip.fourier] = timelock2freq(dip.mom * dip.ampl);
      else
        [dip.pow, dip.csd, dip.fourier] = timelock2freq(dip.mom);
      end
    end
  case 'moving'
    if isfreq
      % although this is technically possible so far, it does not make any sense
      ft_warning('a moving dipole model in the frequency domain is not supported');
    end
  otherwise
    ft_error('unsupported cfg.model');
end % switch model

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% collect the results
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
source.label  = cfg.channel; % these channels were used in fitting
source.dip    = dip;
source.Vdata  = Vdata;  % FIXME this should be renamed (if possible w.r.t. EEGLAB)
source.Vmodel = Vmodel; % FIXME this should be renamed (if possible w.r.t. EEGLAB)

% the units of the fitted source are the same as the units of the headmodel and the sensor array
for i=1:length(source.dip)
  source.dip(i).unit = headmodel.unit;
end

% assign a latency, frequeny or component axis to the output
if iscomp
  source.component = cfg.component;
  % FIXME assign Vdata to an output variable, idem for the model potential
elseif isfreq
  source.freq   = cfg.frequency;
  source.dimord = 'chan_freq';
  % FIXME assign Vdata to an output variable, idem for the model potential
else
  tbeg = nearest(data.time, cfg.latency(1));
  tend = nearest(data.time, cfg.latency(end));
  source.time   = data.time(tbeg:tend);
  source.dimord = 'chan_time';
end

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


function [timeqrs,indexes,tol,messages]=qrscandidatesnew(timeqrs1,timeqrs2,timeqrs3,heasig,tol,messages)
% [timeqrs,indexes]=qrscandidates(timeqrs1,timeqrs2,timeqrs3,heasig,tol)
%
% Construction QRS positions candidates vector from 3 single detections vectors
% a QRS is admitted as candidate if:
%   - it was found in 3 leads and the middle mark differ less than tol from the others
%   - it was found in 2 leads and the marks differ less than tol
%
% marks that not fullfill the above are replaced by NaN and a QRS positions
% candidates vector is constructed with the detections in each lead in one
% line, one QRS in each column
%
% INPUT:
% timeqrs1,timeqrs2,timeqrs3  -  line vector swith single lead detections
%                                (in samples)
% heasig - header information
% tol - maximum distance to be admited as same complex (in sec)
%       by default half of the default refractary period tol = 0.275/2 sec
%
% OUTPUT:
% timeqrs - QRS positions candidates vector
% indexes - beats corresponding to the QRS positions candidates vector
%
% Rute Almeida  14.APR.2005
% Last update: 26JUL2011
%
% MATLAB Version R13
if nargin<6
    messages.status=1;
end
if ~isfield(messages,'warnings'), messages.warnings=[]; end
spf=heasig.spf_ecg; %24.MAR.09
if nargin==4
    tol=ceil(0.275/2*heasig.freq*spf); %24.MAR.09
elseif nargin<4
    messages.errors=[messages.errors {'Fatal error in qrscandidatesnew: not enough inputs.'}];
    warning(char(messages.errors(end)))
    messages.errors_desc=[messages.errors_desc 'Mandatory inputs not defined.'];
    messages.status=0;
    return
end
indexes=[];
indexes1=1:length(timeqrs1);
indexes2=1:length(timeqrs2);
indexes3=1:length(timeqrs3);
timeqrs=[];
lengthaux=([length(timeqrs1) length(timeqrs2) length(timeqrs3)]);

for g=1:min(lengthaux)
    [aux auxi]=sort([timeqrs1(g) timeqrs2(g) timeqrs3(g)]);
    if aux(1)>(aux(2)-tol-1)
        if (aux(2)+tol+1)>aux(3)
            timeqrs=[timeqrs [timeqrs1(g);timeqrs2(g);timeqrs3(g)]]; %#ok<AGROW>
            indexes=[indexes [indexes1(g);indexes2(g);indexes3(g)]]; %#ok<AGROW>
        else
            if auxi(3)==1
                timeqrs1(g+1:end+1)=timeqrs1(g:end);
                indexes1(g+1:end+1)= indexes1(g:end);
                indexes1(g)=NaN;
                timeqrs1(g)=NaN;
            else if auxi(3)==2
                    timeqrs2(g+1:end+1)=timeqrs2(g:end);
                    indexes2(g+1:end+1)= indexes2(g:end);
                    timeqrs2(g)=NaN;
                    indexes2(g)=NaN;
                else
                    timeqrs3(g+1:end+1)=timeqrs3(g:end);
                    indexes3(g+1:end+1)= indexes3(g:end);
                    timeqrs3(g)=NaN;
                    indexes3(g)=NaN;
                end
            end
            timeqrs=[timeqrs [timeqrs1(g);timeqrs2(g);timeqrs3(g)]]; %#ok<AGROW>
            indexes=[indexes [indexes1(g);indexes2(g);indexes3(g)]]; %#ok<AGROW>
        end
    else

        if auxi(1)==1
            g1=g+1;
            flag=0;
            while  g1<=length(timeqrs1) && flag==0 && timeqrs1(g1)<(aux(2)+tol+1)
                if (timeqrs1(g1)>(aux(2)-tol-1))
                    flag=1;
                    timeqrs1(g:end)=[timeqrs1(g1:end) NaN*ones(1,(g1-g))];
                    indexes1(g:end)=[indexes1(g1:end) NaN*ones(1,(g1-g))];
                end
                g1=g1+1;
            end
            if flag==0
                if g1>length(timeqrs1)
                    timeqrs1(g:end)= NaN*ones(1,length(timeqrs1(g:end)));
                    indexes1(g:end)= NaN*ones(1,length(timeqrs1(g:end)));
                else
                    timeqrs1(g:end)=[timeqrs1((g1-1):end) NaN*ones(1,(g1-1-g))];
                    indexes1(g:end)=[indexes1((g1-1):end) NaN*ones(1,(g1-1-g))];
                    timeqrs1(g)=NaN;
                    indexes1(g)=NaN;
                end
            end
        elseif auxi(1)==2
            g1=g+1;
            flag=0;
            while  g1<=length(timeqrs2) && flag==0 && timeqrs2(g1)<(aux(2)+tol+1)
                if (timeqrs2(g1)>(aux(2)-tol-1))
                    flag=1;
                    timeqrs2(g:end)=[timeqrs2(g1:end) NaN*ones(1,(g1-g))];
                    indexes2(g:end)=[indexes2(g1:end) NaN*ones(1,(g1-g))];
                end
                g1=g1+1;
            end
            if flag==0
                if g1>length(timeqrs1)
                    timeqrs2(g:end)= NaN*ones(1,length(timeqrs2(g:end)));
                    indexes2(g:end)= NaN*ones(1,length(timeqrs2(g:end)));
                else
                    timeqrs2(g:end)=[timeqrs2((g1-1):end) NaN*ones(1,(g1-1-g))];
                    indexes2(g:end)=[indexes2((g1-1):end) NaN*ones(1,(g1-1-g))];
                    timeqrs2(g)=NaN;
                    indexes2(g)=NaN;
                end
            end
        else
            g1=g+1;
            flag=0;
            while  g1<=length(timeqrs3) && flag==0 && timeqrs3(g1)<(aux(2)+tol+1)
                if (timeqrs3(g1)>(aux(2)-tol-1))
                    flag=1;
                    timeqrs3(g:end)=[timeqrs3(g1:end) NaN*ones(1,(g1-g))];
                    indexes3(g:end)=[indexes3(g1:end) NaN*ones(1,(g1-g))];
                end
                g1=g1+1;
            end
            if flag==0
                if g1>length(timeqrs1)
                    timeqrs3(g:end)= NaN*ones(1,length(timeqrs3(g:end)));
                    indexes3(g:end)= NaN*ones(1,length(timeqrs3(g:end)));
                else
                    timeqrs3(g:end)=[timeqrs3((g1-1):end) NaN*ones(1,(g1-1-g))];
                    indexes3(g:end)=[indexes3((g1-1):end) NaN*ones(1,(g1-1-g))];
                    timeqrs3(g)=NaN;
                    indexes3(g)=NaN;
                end
            end
        end

        if aux(3)-aux(2)>(tol+1)
            if auxi(3)==1
                timeqrs1(g+1:end+1)=timeqrs1(g:end);
                indexes1(g+1:end+1)= indexes1(g:end);
                indexes1(g)=NaN;
                timeqrs1(g)=NaN;
            else if auxi(3)==2
                    timeqrs2(g+1:end+1)=timeqrs2(g:end);
                    indexes2(g+1:end+1)= indexes2(g:end);
                    timeqrs2(g)=NaN;
                    indexes2(g)=NaN;
                else
                    timeqrs3(g+1:end+1)=timeqrs3(g:end);
                    indexes3(g+1:end+1)= indexes3(g:end);
                    timeqrs3(g)=NaN;
                    indexes3(g)=NaN;
                end
            end
        else
            flag=1;
        end
        if flag==1
            timeqrs=[timeqrs [timeqrs1(g);timeqrs2(g);timeqrs3(g)]]; %#ok<AGROW>
            indexes=[indexes [indexes1(g);indexes2(g);indexes3(g)]]; %#ok<AGROW>
        else
            timeqrs1(g)=NaN;indexes1(g)=NaN;
            timeqrs2(g)=NaN;indexes2(g)=NaN;
            timeqrs3(g)=NaN;indexes3(g)=NaN;
        end
    end
end
lengthaux=([length(timeqrs1) length(timeqrs2) length(timeqrs3)]);
if isempty(g)
    g=0;
end
aux1=find(lengthaux-g >0);
%remains beats in all 3 leads
while length(aux1)==3
%     intervalaux=(g+1):(min(lengthaux(aux1)));
    for g=(g+1):(min(lengthaux(aux1)))
        [aux auxi]=sort([timeqrs1(g) timeqrs2(g) timeqrs3(g)]);
        if aux(1)>(aux(2)-tol-1)
            if (aux(2)+tol+1)>aux(3)
                timeqrs=[timeqrs [timeqrs1(g);timeqrs2(g);timeqrs3(g)]];  %#ok<AGROW>
                indexes=[indexes [indexes1(g);indexes2(g);indexes3(g)]];  %#ok<AGROW>
            else
                if auxi(3)==1
                    timeqrs1(g+1:end+1)=timeqrs1(g:end);
                    indexes1(g+1:end+1)= indexes1(g:end);
                    indexes1(g)=NaN;
                    timeqrs1(g)=NaN;
                else if auxi(3)==2
                        timeqrs2(g+1:end+1)=timeqrs2(g:end);
                        indexes2(g+1:end+1)= indexes2(g:end);
                        timeqrs2(g)=NaN;
                        indexes2(g)=NaN;
                    else
                        timeqrs3(g+1:end+1)=timeqrs3(g:end);
                        indexes3(g+1:end+1)= indexes3(g:end);
                        timeqrs3(g)=NaN;
                        indexes3(g)=NaN;
                    end
                end
                timeqrs=[timeqrs [timeqrs1(g);timeqrs2(g);timeqrs3(g)]];  %#ok<AGROW>
                indexes=[indexes [indexes1(g);indexes2(g);indexes3(g)]];  %#ok<AGROW>
            end
        else
            if auxi(1)==1
                g1=g+1;
                flag=0;
                while  g1<=length(timeqrs1) && flag==0 && timeqrs1(g1)<(aux(2)+tol+1)
                    if (timeqrs1(g1)>(aux(2)-tol-1))
                        flag=1;
                        timeqrs1(g:end)=[timeqrs1(g1:end) NaN*ones(1,(g1-g))];
                        indexes1(g:end)=[indexes1(g1:end) NaN*ones(1,(g1-g))];
                    end
                    g1=g1+1;
                end
                if flag==0
                    if g1>length(timeqrs1)
                        timeqrs1(g:end)=NaN*ones(1,length(timeqrs1(g:end)));
                        indexes1(g:end)=NaN*ones(1,length(timeqrs1(g:end)));
                    else
                        timeqrs1(g:end)=[timeqrs1((g1-1):end) NaN*ones(1,(g1-1-g))];
                        indexes1(g:end)=[indexes1((g1-1):end) NaN*ones(1,(g1-1-g))];
                        timeqrs1(g)=NaN;
                        indexes1(g)=NaN;
                    end
                end
            elseif auxi(1)==2
                g1=g+1;
                flag=0;
                while  g1<=length(timeqrs2) && flag==0 && timeqrs2(g1)<(aux(2)+tol+1)
                    if (timeqrs2(g1)>(aux(2)-tol-1))
                        flag=1;
                        timeqrs2(g:end)=[timeqrs2(g1:end) NaN*ones(1,(g1-g))];
                        indexes2(g:end)=[indexes2(g1:end) NaN*ones(1,(g1-g))];
                    end
                    g1=g1+1;
                end
                if flag==0
                    if g1>length(timeqrs1)
                        timeqrs2(g:end)=NaN*ones(1,length(timeqrs2(g:end)));
                        indexes2(g:end)=NaN*ones(1,length(timeqrs2(g:end)));
                    else
                        timeqrs2(g:end)=[timeqrs2((g1-1):end) NaN*ones(1,(g1-1-g))];
                        indexes2(g:end)=[indexes2((g1-1):end) NaN*ones(1,(g1-1-g))];
                        timeqrs2(g)=NaN;
                        indexes2(g)=NaN;
                    end
                end
            else
                g1=g+1;
                flag=0;
                while  g1<=length(timeqrs3) && flag==0 && timeqrs3(g1)<(aux(2)+tol+1)
                    if (timeqrs3(g1)>(aux(2)-tol-1))
                        flag=1;
                        timeqrs3(g:end)=[timeqrs3(g1:end) NaN*ones(1,(g1-g))];
                        indexes3(g:end)=[indexes3(g1:end) NaN*ones(1,(g1-g))];
                    end
                    g1=g1+1;
                end
                if flag==0
                    if g1>length(timeqrs1)
                        timeqrs3(g:end)=NaN*ones(1,length(timeqrs3(g:end)));
                        indexes3(g:end)=NaN*ones(1,length(timeqrs3(g:end)));
                    else
                        timeqrs3(g:end)=[timeqrs3((g1-1):end) NaN*ones(1,(g1-1-g))];
                        indexes3(g:end)=[indexes3((g1-1):end) NaN*ones(1,(g1-1-g))];
                        timeqrs3(g)=NaN;
                        indexes3(g)=NaN;
                    end
                end
            end
            if aux(3)-aux(2)>(tol+1)
                if auxi(3)==1
                    timeqrs1(g+1:end+1)=timeqrs1(g:end);
                    indexes1(g+1:end+1)= indexes1(g:end);
                    indexes1(g)=NaN;
                    timeqrs1(g)=NaN;
                else if auxi(3)==2
                        timeqrs2(g+1:end+1)=timeqrs2(g:end);
                        indexes2(g+1:end+1)= indexes2(g:end);
                        timeqrs2(g)=NaN;
                        indexes2(g)=NaN;
                    else
                        timeqrs3(g+1:end+1)=timeqrs3(g:end);
                        indexes3(g+1:end+1)= indexes3(g:end);
                        timeqrs3(g)=NaN;
                        indexes3(g)=NaN;
                    end
                end
            else
                flag=1;
            end
            if flag==1
                timeqrs=[timeqrs [timeqrs1(g);timeqrs2(g);timeqrs3(g)]];  %#ok<AGROW>
                indexes=[indexes [indexes1(g);indexes2(g);indexes3(g)]];  %#ok<AGROW>
            else
                timeqrs1(g)=NaN;indexes1(g)=NaN;
                timeqrs2(g)=NaN;indexes2(g)=NaN;
                timeqrs3(g)=NaN;indexes3(g)=NaN;
            end
        end
    end
    lengthaux=([length(timeqrs1) length(timeqrs2) length(timeqrs3)]);
    aux1=find(lengthaux-g >0);
end
[min1i,min1]=min(find(~isnan(timeqrs1(end:-1:1)))); %#ok<NASGU,MXFND>
[min2i,min3]=min(find(~isnan(timeqrs2(end:-1:1)))); %#ok<NASGU,MXFND>
[min3i,min3]=min(find(~isnan(timeqrs3(end:-1:1)))); %#ok<NASGU,MXFND>
timeqrs1(length(timeqrs1)-min1i+2:end)=[];
timeqrs2(length(timeqrs2)-min2i+2:end)=[];
timeqrs3(length(timeqrs3)-min3i+2:end)=[];
lengthaux=([length(timeqrs1) length(timeqrs2) length(timeqrs3)]);
aux1=find(lengthaux-g >0);
%remaining beats in 2 leads
if length(aux1)>1
    for intervalauxi=(g+1):(min(lengthaux(aux1)));
        if aux1(1)==1
            if aux1(2)==2
                [aux auxi]=sort([timeqrs1(intervalauxi) timeqrs2(intervalauxi)]);
                if aux(1)>(aux(2)-tol-1)
                    timeqrs=[timeqrs [timeqrs1(intervalauxi);timeqrs2(intervalauxi);NaN]];  %#ok<AGROW>
                    indexes=[indexes [indexes1(intervalauxi);indexes2(intervalauxi);NaN]];  %#ok<AGROW>
                else
                    if auxi(2)==2
                        timeqrs2(intervalauxi+1:end+1)=timeqrs2(intervalauxi:end);
                        indexes2(intervalauxi+1:end+1)= indexes2(intervalauxi:end);
                        timeqrs2(intervalauxi)=NaN;
                        indexes2(intervalauxi)=NaN;
                    elseif auxi(2)==1
                        timeqrs1(intervalauxi+1:end+1)=timeqrs1(intervalauxi:end);
                        indexes1(intervalauxi+1:end+1)= indexes1(intervalauxi:end);
                        timeqrs1(intervalauxi)=NaN;
                        indexes1(intervalauxi)=NaN;
                    end
                end
            elseif aux1(2)==3
                [aux auxi]=sort([timeqrs1(intervalauxi) timeqrs3(intervalauxi)]);
                if aux(1)>(aux(2)-tol-1)
                    timeqrs=[timeqrs [timeqrs1(intervalauxi);NaN;timeqrs3(intervalauxi)]];  %#ok<AGROW>
                    indexes=[indexes [indexes1(intervalauxi);NaN;indexes3(intervalauxi)]];  %#ok<AGROW>
                else
                    if auxi(2)==2 %25SET08
                        timeqrs3(intervalauxi+1:end+1)=timeqrs3(intervalauxi:end);
                        indexes3(intervalauxi+1:end+1)= indexes3(intervalauxi:end);
                        timeqrs3(intervalauxi)=NaN;
                        indexes3(intervalauxi)=NaN;
                    elseif auxi(2)==1
                        timeqrs1(intervalauxi+1:end+1)=timeqrs1(intervalauxi:end);
                        indexes1(intervalauxi+1:end+1)= indexes1(intervalauxi:end);
                        timeqrs1(intervalauxi)=NaN;
                        indexes1(intervalauxi)=NaN;
                    end
                end
            end
        elseif aux1(1)==2
            [aux auxi]=sort([timeqrs2(intervalauxi) timeqrs3(intervalauxi)]);
            if aux(1)>(aux(2)-tol-1)
                timeqrs=[timeqrs [NaN;timeqrs2(intervalauxi);timeqrs3(intervalauxi)]];  %#ok<AGROW>
                indexes=[indexes [NaN;indexes2(intervalauxi);indexes3(intervalauxi)]];  %#ok<AGROW>
            else
                if auxi(2)==1%25SET08
                    timeqrs2(intervalauxi+1:end+1)=timeqrs2(intervalauxi:end);
                    indexes2(intervalauxi+1:end+1)= indexes2(intervalauxi:end);
                    timeqrs2(intervalauxi)=NaN;
                    indexes2(intervalauxi)=NaN;
                elseif auxi(2)==2%25SET08
                    timeqrs3(intervalauxi+1:end+1)=timeqrs3(intervalauxi:end);
                    indexes3(intervalauxi+1:end+1)= indexes3(intervalauxi:end);
                    timeqrs3(intervalauxi)=NaN;
                    indexes3(intervalauxi)=NaN;
                end
            end
        end
    end
    lengthaux=([length(timeqrs1) length(timeqrs2) length(timeqrs3)]);
    if isempty(intervalauxi);
        intervalauxi=g;
    end
    aux1=find(lengthaux-intervalauxi >0);
    %remains beats in 2 leads
    while length(aux1)==2
        intervalauxi=intervalauxi+1;
        if aux1(1)==1
            if aux1(2)==2
                [aux auxi]=sort([timeqrs1(intervalauxi) timeqrs2(intervalauxi)]);
                if aux(1)>(aux(2)-tol-1)
                    timeqrs=[timeqrs [timeqrs1(intervalauxi);timeqrs2(intervalauxi);NaN]];  %#ok<AGROW>
                    indexes=[indexes [indexes1(intervalauxi);indexes2(intervalauxi);NaN]];  %#ok<AGROW>
                else
                    if auxi(2)==2
                        timeqrs2(intervalauxi+1:end+1)=timeqrs2(intervalauxi:end);
                        indexes2(intervalauxi+1:end+1)= indexes2(intervalauxi:end);
                        timeqrs2(intervalauxi)=NaN;
                        indexes2(intervalauxi)=NaN;
                    elseif auxi(2)==1
                        timeqrs1(intervalauxi+1:end+1)=timeqrs1(intervalauxi:end);
                        indexes1(intervalauxi+1:end+1)= indexes1(intervalauxi:end);
                        timeqrs1(intervalauxi)=NaN;
                        indexes1(intervalauxi)=NaN;
                    end
                end
            elseif aux1(2)==3
                [aux auxi]=sort([timeqrs1(intervalauxi) timeqrs3(intervalauxi)]);
                if aux(1)>(aux(2)-tol-1)
                    timeqrs=[timeqrs [timeqrs1(intervalauxi);NaN;timeqrs3(intervalauxi)]];  %#ok<AGROW>
                    indexes=[indexes [indexes1(intervalauxi);NaN;indexes3(intervalauxi)]];  %#ok<AGROW>
                else
                    if auxi(2)==3
                        timeqrs3(intervalauxi+1:end+1)=timeqrs3(intervalauxi:end);
                        indexes3(intervalauxi+1:end+1)= indexes3(intervalauxi:end);
                        timeqrs3(intervalauxi)=NaN;
                        indexes3(intervalauxi)=NaN;
                    elseif auxi(2)==1
                        timeqrs1(intervalauxi+1:end+1)=timeqrs1(intervalauxi:end);
                        indexes1(intervalauxi+1:end+1)= indexes1(intervalauxi:end);
                        timeqrs1(intervalauxi)=NaN;
                        indexes1(intervalauxi)=NaN;
                    end
                end
            end
        elseif aux1(1)==2
            [aux auxi]=sort([timeqrs2(intervalauxi) timeqrs3(intervalauxi)]);
            if aux(1)>(aux(2)-tol-1)
                timeqrs=[timeqrs [NaN; timeqrs2(intervalauxi);timeqrs3(intervalauxi)]];  %#ok<AGROW>
                indexes=[indexes [NaN; indexes2(intervalauxi);indexes3(intervalauxi)]];  %#ok<AGROW>
            else
                if auxi(2)==2
                    timeqrs2(intervalauxi+1:end+1)=timeqrs2(intervalauxi:end);
                    indexes2(intervalauxi+1:end+1)= indexes2(intervalauxi:end);
                    timeqrs2(intervalauxi)=NaN;
                    indexes2(intervalauxi)=NaN;
                elseif auxi(2)==3
                    timeqrs3(intervalauxi+1:end+1)=timeqrs3(intervalauxi:end);
                    indexes3(intervalauxi+1:end+1)= indexes3(intervalauxi:end);
                    timeqrs3(intervalauxi)=NaN;
                    indexes3(intervalauxi)=NaN;
                end
            end
        end
        lengthaux=([length(timeqrs1) length(timeqrs2) length(timeqrs3)]);
        aux1=find(lengthaux-intervalauxi >0);
    end
    lengthaux=([length(timeqrs1) length(timeqrs2) length(timeqrs3)]);
    g=intervalauxi;
    aux1=find(lengthaux-g >0);
end
%NOTE THAT ONLY ALIGNED ONES ARE CONSIDERED!!!!!!!!!!!!!!!!!!
intervalaux=(g+1):(min(lengthaux(aux1)));
if length(aux1)>1
    timeqrs1((min(lengthaux(aux1)))+1:end)=[];
    timeqrs2((min(lengthaux(aux1)))+1:end)=[];
    timeqrs3((min(lengthaux(aux1)))+1:end)=[];
    indexes1((min(lengthaux(aux1)))+1:end)=[];
    indexes2((min(lengthaux(aux1)))+1:end)=[];
    indexes3((min(lengthaux(aux1)))+1:end)=[];
    if ~ismember(1,aux1)
        timeqrs1(intervalaux)=NaN*ones(size(intervalaux));
        indexes1(intervalaux)=NaN*ones(size(intervalaux));
        aux=[timeqrs2(intervalaux); timeqrs3(intervalaux) ];
    end
    if ~ismember(2,aux1)
        timeqrs2(intervalaux)=NaN*ones(size(intervalaux));
        indexes2(intervalaux)=NaN*ones(size(intervalaux));
        aux=[timeqrs1(intervalaux); timeqrs3(intervalaux) ];
    end
    if ~ismember(3,aux1)
        timeqrs3(intervalaux)=NaN*ones(size(intervalaux));
        indexes3(intervalaux)=NaN*ones(size(intervalaux));
        aux=[timeqrs1(intervalaux); timeqrs2(intervalaux) ];
    end
    timeqrs=[timeqrs [timeqrs1(intervalaux(abs(diff(aux))<(tol+1)));timeqrs2(intervalaux(abs(diff(aux))<(tol+1)));timeqrs3(intervalaux(abs(diff(aux))<(tol+1)))]];
    indexes=[indexes [indexes1(intervalaux(abs(diff(aux))<(tol+1)));indexes2(intervalaux(abs(diff(aux))<(tol+1)));indexes3(intervalaux(abs(diff(aux))<(tol+1)))]];
%     indexes1(intervalaux(abs(diff(aux))>=(tol+1) | isnan(diff(aux))))=[];
%     indexes2(intervalaux(abs(diff(aux))>=(tol+1) | isnan(diff(aux))))=[];
%     indexes3(intervalaux(abs(diff(aux))>=(tol+1) | isnan(diff(aux))))=[];
%     timeqrs1(intervalaux(abs(diff(aux))>=(tol+1) | isnan(diff(aux))))=[];
%     timeqrs2(intervalaux(abs(diff(aux))>=(tol+1) | isnan(diff(aux))))=[];
%     timeqrs3(intervalaux(abs(diff(aux))>=(tol+1) | isnan(diff(aux))))=[];
else
%     timeqrs1((min(lengthaux(aux1))):end)=[];
%     timeqrs2((min(lengthaux(aux1))):end)=[];
%     timeqrs3((min(lengthaux(aux1))):end)=[];
%     indexes1((min(lengthaux(aux1))):end)=[];
%     indexes2((min(lengthaux(aux1))):end)=[];
%     indexes3((min(lengthaux(aux1))):end)=[];
end
if ~isempty(indexes) %17ABRIL08
indexes=indexes(:,~isnan(indexes(1,:))| ~isnan(indexes(2,:))|~isnan(indexes(3,:)));
end
timeqrs(:,(size(indexes,2)+1):end)=[];


function [model, directions] = directionalityStats(model, directions, cumNormProbCutoff, printLevel)
% Build Boolean vectors with reaction directionality statistics
%
% USAGE:
%
%    [model, directions] = directionalityStats(model, directions, cumNormProbCutoff, printLevel)
%
% INPUTS:
%    model:                 a structue of boolean vectors with different directionality
%                           assignments where some vectors contain subsets of others with field:
%
%                             * .directions
%
%    directions:            structure with field:
%
%                             * .forwardProbability
%
%                           qualitatively assigned internal reaction direactions
%
%                             * .forwardRecon
%                             * .reverseRecon
%                             * .reversibleRecon
%                             * .equilibriumRecon
%
%                           quantitatively assigned internal reaction direactions
%                           thermodynamic data is lacking
%
%                             * .forwardThermo
%                             * .reverseThermo
%                             * .reversibleThermo
%                             * .uncertainThermo
%                             * .equilibriumThermo
%
% OPTIONAL INPUT
%    cumNormProbCutoff:     {0.2} cutoff for probablity that reaction is
%                           reversible within this cutoff of 0.5
%    printLevel:            -1 to print out to file,
%                           0 to silent,
%                           1 to print out to command window
%
% OUTPUT:
%    directions:            a structue of boolean vectors with different directionality
%                           assignments where some vectors contain subsets of others
%
%                           qualtiative -> quantiative changed reaction directions
%
%                             * .forward2Forward
%                             * .forward2Reverse
%                             * .forward2Reversible
%                             * .forward2Uncertain
%                             * .reversible2Forward
%                             * .reversible2Reverse
%                             * .reversible2Reversible
%                             * .reversible2Uncertain
%                             * .reverse2Forward
%                             * .reverse2Reverse
%                             * .reverse2Reversible
%                             * .reverse2Uncertain
%                             * .tightened
%
%                           subsets of qualtiatively forward  -> quantiatively reversible
%
%                             * .forward2Reversible_bydGt0
%                             * .forward2Reversible_bydGt0LHS
%                             * .forward2Reversible_bydGt0Mid
%                             * .forward2Reversible_bydGt0RHS
%                             * .forward2Reversible_byConc_zero_fixed_DrG0
%                             * .forward2Reversible_byConc_negative_fixed_DrG0
%                             * .forward2Reversible_byConc_positive_fixed_DrG0
%                             * .forward2Reversible_byConc_negative_uncertain_DrG0
%                             * .forward2Reversible_byConc_positive_uncertain_DrG0
%
% .. Author: - Ronan M.T. Fleming

if ~exist('cumNormProbCutoff','var')
    directions.cumNormProbCutoff=0.2;
else
    directions.cumNormProbCutoff=cumNormProbCutoff;
end
%must be symmetric about 50:50 to be logically consistent
cumNormProbFwdUpper=0.5+directions.cumNormProbCutoff;
cumNormProbFwdLower=0.5-directions.cumNormProbCutoff;

if ~exist('printLevel','var')
    printLevel=0;
end
if ~exist('fileName','var')
    fileName='directionalityStats.txt';
end

DrGtMin=model.DrGtMin;
DrGtMax=model.DrGtMax;
if any(DrGtMin>DrGtMax)
    error('DrGtMin greater than DrGtMax');
end

DrGtNaNBool=(isnan(model.DrGtMax) | isnan(model.DrGtMin)) & model.SIntRxnBool;
if any(DrGtNaNBool)
    warning([int2str(nnz(DrGtNaNBool)) ' DrGt are NaN']);
end

nEqualDrGt=nnz(DrGtMin==DrGtMax & DrGtMin~=0);
if any(nEqualDrGt)
    fprintf('%s\n',[num2str(nEqualDrGt) '/' num2str(length(DrGtMin)) ' reactions with DrGtMin=DrGtMax~=0' ]);
end

nZeroDrGt=nnz(DrGtMin==0 & DrGtMax==0);
if any(nZeroDrGt)
    fprintf('%s\n',[num2str(nZeroDrGt) '/' num2str(length(DrGtMin)) ' reactions with DrGtMin=DrGtMax=0' ]);
end

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

% qualitatively assigned directions
forwardRecon=directions.forwardRecon;
reverseRecon=directions.reverseRecon;
reversibleRecon=directions.reversibleRecon;
% quantitatively assigned directions
forwardThermo=directions.forwardThermo;
reverseThermo=directions.reverseThermo;
reversibleThermo=directions.reversibleThermo;
uncertainThermo=directions.uncertainThermo;

%%%%%%CHANGES IN REACTION DIRECTIONS%%%%%%%%%%%
%thermodynamic constraints tightened
tightened=model.lb<model.lb_reconThermo & model.ub_reconThermo<model.ub;

reversible2Forward=reversibleRecon & forwardThermo;
reversible2Reverse=reversibleRecon & reverseThermo;
reversible2Reversible=reversibleRecon & reversibleThermo;
reversible2Uncertain=reversibleRecon & uncertainThermo;

forward2Reverse=forwardRecon & reverseThermo;
forward2Reversible=forwardRecon & reversibleThermo;
forward2Forward=forwardRecon & forwardThermo;
forward2Uncertain=forwardRecon & uncertainThermo;

reverse2Reverse =  reverseRecon & reverseThermo;
reverse2Forward   =  reverseRecon & forwardThermo;
reverse2Reversible =  reverseRecon & reversibleThermo;
reverse2Uncertain  =  reverseRecon & uncertainThermo;

%%%%%%CAUSES TO CHANGES IN REACTION DIRECTIONS%%%%%%%%%%%
% model.DrGtMax = model.DrGt0Max + gasConstant*T*(R'*log(model.concMax) - F'*log(model.concMin));
% model.DrGtMin = model.DrGt0Min + gasConstant*T*(R'*log(model.concMin) - F'*log(model.concMax));
% model.DrGtMaxMeanConc = model.DrGt0Max + gasConstant*T*(R-F)'*log((model.concMax+model.concMin)/2);
% model.DrGtMinMeanConc = model.DrGt0Min + gasConstant*T*(R-F)'*log((model.concMax+model.concMin)/2);

forward2Reversible_bydGt0=forwardRecon & reversibleThermo & model.DrGtMin<0 & model.DrGtMax>0; % dGfGCforward2ReversibleBool_bydGt0

forward2Reversible_byConc_negative_fixed_DrG0 = forwardRecon & reversibleThermo & model.DrGt0Min==model.DrGt0Max & model.DrGtMax<=0; %dGfGCforward2ReversibleBool_byConc_No_dGt0ErrorLHS
forward2Reversible_byConc_positive_fixed_DrG0 = forwardRecon & reversibleThermo & model.DrGt0Min==model.DrGt0Max & model.DrGtMin>0; %dGfGCforward2ReversibleBool_byConc_No_dGt0ErrorRHS

forward2Reversible_bydGt0LHS=forward2Reversible_bydGt0 & directions.forwardProbability>cumNormProbFwdUpper; %dGfGCforward2ReversibleBool_bydGt0LHS
forward2Reversible_bydGt0Mid=forward2Reversible_bydGt0 & directions.forwardProbability>=cumNormProbFwdLower & directions.forwardProbability<=cumNormProbFwdUpper;%dGfGCforward2ReversibleBool_bydGt0Mid
forward2Reversible_bydGt0RHS=forward2Reversible_bydGt0 & directions.forwardProbability<cumNormProbFwdLower; %dGfGCforward2ReversibleBool_bydGt0RHS

forward2Reversible_byConc_negative_uncertain_DrG0 = forwardRecon & reversibleThermo & model.DrGt0Min~=model.DrGt0Max & model.DrGt0Max<0; %dGfGCforward2ReversibleBool_byConcLHS
forward2Reversible_byConc_positive_uncertain_DrG0 = forwardRecon & reversibleThermo & model.DrGt0Min~=model.DrGt0Max & model.DrGt0Min>0; %dGfGCforward2ReversibleBool_byConcRHS

forward2Reversible_byConc_zero_fixed_DrG0 = forwardRecon & reversibleThermo & model.DrGt0Min==0 & model.DrGt0Max==0;%new to v2

if printLevel<0
    fid=fopen(fileName,'w');
else
    fid=1;
end

if printLevel~=0
    fprintf(fid,'%s\n','Qualitative internal reaction directionality:');
    fprintf(fid,'%10s\t%s\n',int2str(nnz(model.SIntRxnBool)),' internal reconstruction reaction directions.');
    fprintf(fid,'%10s\t%s\n',int2str(nnz(forwardRecon)), ' forward reconstruction assignment.');
    fprintf(fid,'%10s\t%s\n',int2str(nnz(reverseRecon)), ' reverse reconstruction assignment.');
    fprintf(fid,'%10s\t%s\n',int2str(nnz(reversibleRecon)), ' reversible reconstruction assignment.');
    fprintf(fid,'\n');

    fprintf(fid,'%s\n','Quantitative internal reaction directionality:');
    fprintf(fid,'%10s\t%s\n',int2str(nnz(model.SIntRxnBool)),' internal reconstruction reaction directions.');
    fprintf(fid,'%10s\t%s\n',int2str(nnz(forwardThermo)+nnz(reverseThermo)+nnz(reversibleThermo)),  ' of which have a thermodynamic assignment.');
    fprintf(fid,'%10s\t%s\n',int2str(nnz(uncertainThermo)),  ' of which have no thermodynamic assignment.');
    fprintf(fid,'%10s\t%s\n',int2str(nnz(forwardThermo)), ' forward thermodynamic only assignment.');
    fprintf(fid,'%10s\t%s\n',int2str(nnz(reverseThermo)), ' reverse thermodynamic only assignment.');
    fprintf(fid,'%10s\t%s\n',int2str(nnz(reversibleThermo)), ' reversible thermodynamic only assignment.');
    fprintf(fid,'\n');

    fprintf(fid,'%s\n','Qualitiative vs Quantitative:');
    fprintf(fid,'%10i\t%s\n',nnz(reversible2Reversible),' Reversible -> Reversible');
    fprintf(fid,'%10i\t%s\n',nnz(reversible2Forward),' Reversible -> Forward');
    fprintf(fid,'%10i\t%s\n',nnz(reversible2Reverse),' Reversible -> Reverse');
    fprintf(fid,'%10i\t%s\n',nnz(reversible2Uncertain),' Reversible -> Uncertain');
    fprintf(fid,'%10i\t%s\n',nnz(forward2Forward),' Forward -> Forward');
    fprintf(fid,'%10i\t%s\n',nnz(forward2Reverse),' Forward -> Reverse');
    fprintf(fid,'%10i\t%s\n',nnz(forward2Reversible),' Forward -> Reversible');
    fprintf(fid,'%10i\t%s\n',nnz(forward2Uncertain),' Forward -> Uncertain');
    fprintf(fid,'%10i\t%s\n',nnz(reverse2Reversible),' Reverse -> Reverse');
    fprintf(fid,'%10i\t%s\n',nnz(reverse2Forward),' Reverse -> Forward');
    fprintf(fid,'%10i\t%s\n',nnz(reverse2Reversible),' Reverse -> Reversible');
    fprintf(fid,'%10i\t%s\n',nnz(reverse2Uncertain),' Reversible -> Uncertain');
    fprintf(fid,'\n');

    fprintf(fid,'%s\n','Breakdown of relaxation of reaction directionality, Qualitiative vs Quantitative:');
    %total number of qualitatively forward reactions that are
    %quantitatively reversible
    fprintf(fid,'%10i\t%s\n',nnz(forward2Reversible),' qualitatively forward reactions that are quantitatively reversible (total).');
    %qualitatively forward reactions that are quantitatively reversible by
    %the range of dGt0
    fprintf(fid,'%10i\t%s%s\n',nnz(forward2Reversible_bydGt0LHS),' of which are quantitatively reversible by range of dGt0. ',['P(\Delta_{r}G^{\primeo}<0) > ' num2str(cumNormProbFwdUpper)]);
    %qualitatively reverse reactions that are quantitatively
    %reversible by concentration alone (with dGt0 error)
    fprintf(fid,'%10i\t%s%s\n',nnz(forward2Reversible_bydGt0Mid),' of which are quantitatively reversible by range of dGt0. ',[num2str(cumNormProbFwdLower) '< P(\Delta_{r}G^{\primeo}<0) < ' num2str(cumNormProbFwdUpper)]);
    %qualitatively reverse reactions that are quantitatively
    %reversible by concentration alone (with dGt0 error)
    fprintf(fid,'%10i\t%s%s\n',nnz(forward2Reversible_bydGt0RHS),' of which are quantitatively reversible by range of dGt0. ',['P(\Delta_{r}G^{\primeo}<0) < ' num2str(cumNormProbFwdLower)]);
    %qualitatively forward reactions that are quantitatively
    %reversible by concentration alone (no dGt0 error)
    fprintf(fid,'%10i\t%s\n',nnz(forward2Reversible_byConc_zero_fixed_DrG0),' of which are quantitatively forward by fixed dGr0t, but reversible by concentration alone (zero fixed DrGt0).');
    %qualitatively reverse reactions that are quantitatively
    %reversible by concentration alone (with dGt0 error)
    fprintf(fid,'%10i\t%s\n',nnz(forward2Reversible_byConc_negative_fixed_DrG0),' of which are quantitatively reverse by dGr0t, but reversible by concentration (negative fixed DrGt0).');
    %qualitatively reverse reactions that are quantitatively
    %reversible by concentration alone (with dGt0 error)
    fprintf(fid,'%10i\t%s\n',nnz(forward2Reversible_byConc_positive_fixed_DrG0),' of which are quantitatively forward by dGr0t, but reversible by concentration (positve fixed DrGt0).');
    %qualitatively reverse reactions that are quantitatively
    %reversible by concentration alone (with dGt0 error)
    fprintf(fid,'%10i\t%s\n',nnz(forward2Reversible_byConc_negative_uncertain_DrG0),' of which are quantitatively reverse by dGr0t, but reversible by concentration (uncertain negative DrGt0).');
    %qualitatively reverse reactions that are quantitatively
    %reversible by concentration alone (with dGt0 error)
    fprintf(fid,'%10i\t%s\n',nnz(forward2Reversible_byConc_positive_uncertain_DrG0),' of which are quantitatively forward by dGr0t, but reversible by concentration (uncertain positive DrGt0).');
end
if printLevel<0
    fclose(fid);
end

%changed directions
directions.forward2Forward=forward2Forward;
directions.forward2Reverse=forward2Reverse;
directions.forward2Reversible=forward2Reversible;
directions.forward2Uncertain=forward2Uncertain;
directions.reversible2Forward=reversible2Forward;
directions.reversible2Reverse=reversible2Reverse;
directions.reversible2Reversible=reversible2Reversible;
directions.reversible2Uncertain=reversible2Uncertain;
directions.reverse2Forward=reverse2Forward;
directions.reverse2Reverse=reverse2Reverse;
directions.reverse2Reversible=reverse2Reversible;
directions.reverse2Uncertain=reverse2Uncertain;

directions.tightened=tightened;

%all forward reversible classes
directions.forward2Reversible_bydGt0=forward2Reversible_bydGt0;
directions.forward2Reversible_bydGt0LHS=forward2Reversible_bydGt0LHS;
directions.forward2Reversible_bydGt0Mid=forward2Reversible_bydGt0Mid;
directions.forward2Reversible_bydGt0RHS=forward2Reversible_bydGt0RHS;

directions.forward2Reversible_byConc_zero_fixed_DrG0=forward2Reversible_byConc_zero_fixed_DrG0;
directions.forward2Reversible_byConc_negative_fixed_DrG0=forward2Reversible_byConc_negative_fixed_DrG0;
directions.forward2Reversible_byConc_positive_fixed_DrG0=forward2Reversible_byConc_positive_fixed_DrG0;
directions.forward2Reversible_byConc_negative_uncertain_DrG0=forward2Reversible_byConc_negative_uncertain_DrG0;
directions.forward2Reversible_byConc_positive_uncertain_DrG0=forward2Reversible_byConc_positive_uncertain_DrG0;


function [estimate] = ft_inverse_music(sourcemodel, sens, headmodel, dat, varargin)

% FT_INVERSE_MUSIC source localization using MUltiple SIgnal Classification.
% This is a signal subspace method, which covers the techniques for
% multiple source localization by using the eigen-structure of the
% measured data matrix.
%
% Use as
%   [estimate] = ft_inverse_music(sourcemodel, sens, headmodel, dat, ...)
% where
%   sourcemodel is the input source model, see FT_PREPARE_SOURCEMODEL
%   sens        is the gradiometer or electrode definition, see FT_DATATYPE_SENS
%   headmodel   is the volume conductor definition, see FT_PREPARE_HEADMODEL
%   dat         is the data matrix with the ERP or ERF
% and
%   estimate    contains the estimated source parameters
%
% Additional input arguments should be specified as key-value pairs and can include
%   'cov'              = data covariance matrix
%   'numcomponent'     = integer number
%   'feedback'         = can be 'none', 'gui', 'dial', 'textbar', 'text', 'textcr', 'textnl' (default = 'text')
%
% These options influence the forward computation of the leadfield
%   'reducerank'      = 'no' or number  (default = 3 for EEG, 2 for MEG)
%   'backproject'     = 'yes' or 'no', in the case of a rank reduction this parameter determines whether the result will be backprojected onto the original subspace (default = 'yes')
%   'normalize'       = 'no', 'yes' or 'column' (default = 'no')
%   'normalizeparam'  = parameter for depth normalization (default = 0.5)
%   'weight'          = number or Nx1 vector, weight for each dipole position to compensate for the size of the corresponding patch (default = 1)
%
% This implements
% - J.C. Mosher, P.S. Lewis and R.M. Leahy, "Multiple dipole modeling and
%   localization from spatiotemporal MEG data", IEEE Trans. Biomed. Eng.,
%   pp 541-557, June, 1992.
%
% See also FT_SOURCEANALYSIS, FT_PREPARE_HEADMODEL, FT_PREPARE_SOURCEMODEL

% Copyright (C) 2004-2008, 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 mod(nargin-4,2)
  % the first 4 arguments are fixed, the other arguments should come in pairs
  ft_error('invalid number of optional arguments');
end

% get the optional input arguments, or use defaults
cov            = ft_getopt(varargin, 'cov');
numcomponent   = ft_getopt(varargin, 'numcomponent');     % this is required, see below
feedback       = ft_getopt(varargin, 'feedback', 'text');

% construct the low-level options for the leadfield computation as key-value pairs, these are passed to FT_COMPUTE_LEADFIELD
leadfieldopt = {};
leadfieldopt = ft_setopt(leadfieldopt, 'reducerank',     ft_getopt(varargin, 'reducerank'));
leadfieldopt = ft_setopt(leadfieldopt, 'backproject',    ft_getopt(varargin, 'backproject'));
leadfieldopt = ft_setopt(leadfieldopt, 'normalize',      ft_getopt(varargin, 'normalize'));
leadfieldopt = ft_setopt(leadfieldopt, 'normalizeparam', ft_getopt(varargin, 'normalizeparam'));
leadfieldopt = ft_setopt(leadfieldopt, 'weight',         ft_getopt(varargin, 'weight'));

if isempty(numcomponent)
  ft_error('you must specify the number of signal components');
end

% flags to avoid calling isfield repeatedly in the loop over grid positions (saves a lot of time)
hasmom        = isfield(sourcemodel, 'mom');
hasleadfield  = isfield(sourcemodel, 'leadfield');
hasfilter     = false; % not used here

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% find the dipole positions that are inside/outside the brain
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if ~isfield(sourcemodel, 'inside')
  if hasfilter
    sourcemodel.inside = ~cellfun(@isempty, sourcemodel.filter);
  elseif hasleadfield
    sourcemodel.inside = ~cellfun(@isempty, sourcemodel.leadfield);
  else
    sourcemodel.inside = ft_inside_headmodel(sourcemodel.pos, headmodel);
  end
end

% convert to logical representation
sourcemodel = fixinside(sourcemodel);

% keep the original details on inside and outside positions
originside = sourcemodel.inside;
origpos    = sourcemodel.pos;

% select only the dipole positions inside the brain for scanning
sourcemodel.pos    = sourcemodel.pos(originside,:);
sourcemodel.inside = true(size(sourcemodel.pos,1),1);

if hasmom
  sourcemodel.mom = sourcemodel.mom(:,originside);
end

if hasleadfield
  ft_info('using precomputed leadfields\n');
  sourcemodel.leadfield = sourcemodel.leadfield(originside);
else
  ft_info('computing forward model on the fly\n');
end

if ~isempty(cov)
  % compute signal and noise subspace from covariance matrix
  [u, s, v] = svd(cov);
else
  % compute signal and noise subspace from average data matrix
  [u, s, v] = svd(dat);
end
% select the noise subspace, c.f. equation 25
us = u(:,(numcomponent+1):end);
ps = us * us';

% allocate space to hold the result
estimate = [];
estimate.jr = nan(size(sourcemodel.pos,1),1);

ft_progress('init', feedback, 'scanning grid');
for i=1:size(sourcemodel.pos,1)
  ft_progress(i/size(sourcemodel.pos,1), 'scanning grid %d/%d\n', i, size(sourcemodel.pos,1));

  if hasleadfield && hasmom && size(sourcemodel.mom, 1)==size(sourcemodel.leadfield{i}, 2)
    % reuse the leadfield that was previously computed and project
    lf = sourcemodel.leadfield{i} * sourcemodel.mom(:,i);
  elseif  hasleadfield &&  hasmom
    % reuse the leadfield that was previously computed but don't project
    lf = sourcemodel.leadfield{i};
  elseif  hasleadfield && ~hasmom
    % reuse the leadfield that was previously computed
    lf = sourcemodel.leadfield{i};
  elseif ~hasleadfield &&  hasmom
    % compute the leadfield for a fixed dipole orientation
    lf = ft_compute_leadfield(sourcemodel.pos(i,:), sens, headmodel, leadfieldopt{:}) * sourcemodel.mom(:,i);
  else
    % compute the leadfield
    lf = ft_compute_leadfield(sourcemodel.pos(i,:), sens, headmodel, leadfieldopt{:});
  end

  % compute the music metric, c.f. equation 26
  estimate.jr(i) = (norm(ps * lf)./norm(lf)).^2;
  % as described in the Mosher 1992 paper on page 550, "...the general approach is to
  % evaluare Jr(i) over a fine three-dimensional grid, plot its inverse,
  % and look for p sharp spikes..."

end % for each dipole position
ft_progress('close');

% reassign the estimated values over the inside and outside grid positions
estimate.inside   = originside;
estimate.pos      = origpos;
if isfield(estimate, 'jr')
  estimate.jr( originside) = estimate.jr;
  estimate.jr(~originside) = nan;
end
if isfield(estimate, 'leadfield')
  estimate.leadfield( originside) = estimate.leadfield;
  estimate.leadfield(~originside) = {[]};
end


function eddyscan_compiled_script(file_name, file_path, save_path, varargin)%#codegen
if ~strcmp(file_path(end), '/')
    file_path = strcat(file_path, '/');
end
if ~strcmp(save_path(end), '/')
    save_path = strcat(save_path, '/');
end
vars = load('area_map.mat');
area_map = vars.area_map;
[dir, rem] = strtok(file_name, '/');
filename = strtok(rem, '/');
eddy_dir = [save_path, dir];
indices = regexp(filename, '[0-9]');
nums = filename(indices);
date = nums(1:8);
eddy_file = ['anticyc_', date, '.mat'];
if exist(eddy_dir, 'dir')
    cd(eddy_dir);
    if exist(eddy_file, 'file')
        disp('Eddy file detected, quitting.');
        %quit;
    end
end
cd([file_path, dir]);
if exist([file_path, dir, '/', filename], 'file') && ~exist([save_path, dir, '/', eddy_file], 'file')
    ssh = ncread(filename, 'sla')';
    lat = double(ncread(filename, 'lat'));
    lon = double(ncread(filename, 'lon'));
    cd(save_path);
    ant_eddies = scan_single(ssh, lat, lon, date, 'anticyc', 'v2', area_map, varargin{:});%#ok
    cyc_eddies = scan_single(ssh, lat, lon, date, 'cyclonic', 'v2', area_map, varargin{:});%#ok
    if ~exist([save_path, dir], 'dir')
        mkdir([save_path, dir]);
    end
    cd([save_path, dir]);
    save(['anticyc_', date, '.mat'], 'ant_eddies');
    save(['cyclonic_', date, '.mat'], 'cyc_eddies');
else

end
%quit;
end

function [ eddies ] = scan_single( ssh, lat, lon, date, cyc, scan_type, areamap, varargin )
%SCAN_SINGLE Wrapper function to do scanning
% ssh: ssh slice with nans for land, size should be [length(lat) length(lon)]
% lat: 1D array of the latitudes of ssh grid
% lon: 1D array of the longitudes of ssh grid
% cyc: 'anticyc' or 'cyclonic'
% scan_type: 'v1', 'v2', 'hybrid'
%         v1: Will run top-down scanning (only works with full data of 0.25 x 0.25 ssh grid)
%         v2: Will run bottom-up scanning from the minima of the field
%     hybrid: Will run v2 and v1 scanning and will take the union of the
%             two sets where, for common features, v2 bodies will be used
% areamap: A 2D array that refer to the area of each pixel in SSH data (should have same size as ssh), or 1D array
%   that refer to area of each pixel for a specific lat in a regular grid (pixeld have same area for the same
%   latitude)
% Optional parameters (only applicable for v2 eddyscan):
%   'minimumArea': minimum number of pixels for an eddy, used for validating eddies, default value is 9
%   'thresholdStep': the minimum step for thresholding, the unit is SSH's unit, default value is 0.05
%   'isPadding': whether or not to pad SSH data, should be true when scanning SSH data with the longitudes expanding the
%   whole world dmap. Set to false if only partial SSH data is used. Default value is true
    if ~any(isnan(ssh(:)))
        error('Invalid ssh data, must contain NaNs for land values');
    end

    if ~all(size(ssh) == [length(lat) length(lon)])
        error('Invalid ssh data size, should be [length(lat) length(lon]');
    end

    if ~all(size(areamap) == size(ssh))
        % Not a 2d array with same size as ssh
        if ~any(size(areamap) == [1 1]) || length(areamap) ~= length(lat)
            disp('Invalid areamap, using NaN for eddy surface area');
        end
    end

    stype = get_stype(scan_type);
    ctype = get_ctype(cyc);
    disp('About to start scanning eddies');

    %oldpath = addpath('lib');
    switch stype
        case 1
            eddies = top_down_single(ssh, lat, lon, areamap, ctype, varargin{:});
        case 2
            eddies = bottom_up_single(ssh, lat, lon, areamap, ctype, varargin{:});
%        case 0
%            scanners = {@top_down_single, @bottom_up_single};
%            eddies_out = {[], []};
%            parfor i = 1:2
%                eddies_out{i} = scanners{i}(ssh, lat, lon, areamap, ctype, varargin{:});
%            end
%            eddies = get_combined_eddy_frames(eddies_out{2}, eddies_out{1}, ssh);
    end
    [eddies.Date] = deal(date);

    %path(oldpath);

end
%end

function cyc_t = get_ctype(cyc)
    switch cyc
        case 'anticyc'
            cyc_t = 1;
        case 'cyclonic'
            cyc_t = -1;
        otherwise
            error('cyc must be anticyc or cyclonic');
    end
end

function scan_t = get_stype(scan_type)
    switch scan_type
        case 'v1'
            scan_t = 1;
        case 'v2'
            scan_t = 2;
        case 'hybrid'
            scan_t = 0;
        otherwise
            error('scan_type must be v1, v2, or hybrid');
    end
end

function [ eddies ] = bottom_up_single(ssh_data, lat, lon, areamap, cyc, varargin)
%BOTTOM_UP_SINGLE Finds eddies using the Bottom Up method
%   Will return an array of struct's that contain the eddy data.
%   ssh_data: A 2D array of double's that contain the sea surface heights (latsxlons)
%   lat: A 1D array of double's that gives the latitude for a given index (dimension should match
%         that of ssh_data)
%   lon: A 1D array of double's that gives the longitude for a given index (dimension should match
%         that of ssh_data)
%   areamap: A 2D array that refer to the area of each pixel in SSH data (should have same size as ssh), or 1D array
%   that refer to area of each pixel for a specific lat in a regular grid (pixeld have same area for the same
%   latitude)
%   cyc: Pass 1 to output anticyclonic eddies or -1 to output cyclonic eddies
%   Optional parameters:
%   'minimumArea': minimum number of pixels for an eddy, used for validating eddies
%   'thresholdStep': the minimum step for thresholding, the unit is SSH's unit
%   'isPadding': whether or not to pad SSH data, should be true when scanning SSH data of the whole map. Set to false if
%   only partial SSH data is used.
%   'sshUnits': The units the SSH data is in. bottom_up_single is built to work natively on centimeter SSH data.
%   Valid parameters are 'meters' and 'centimeters'. If the paramater passed in is 'meters', the SSH data will
%   be multiplied by 100. No changes will be made if the paramater passed in is 'centimeters'.
%   The default value of 'sshUnits' is centimeters.
    p = inputParser;
    defaultMinPixelSize = 9;
    defaultThresholdStep = 0.05;
    defaultSSHUnits = 'centimeters';
    defaultPaddingFlag = true;
    addRequired(p, 'ssh_data');
    addRequired(p, 'lat');
    addRequired(p, 'lon');
    addRequired(p, 'areamap');
    addRequired(p, 'cyc');
    addParameter(p, 'minimumArea', defaultMinPixelSize);%, @isnumeric);
    addParameter(p, 'thresholdStep', defaultThresholdStep);%, @isnumeric);
    addParameter(p, 'isPadding', defaultPaddingFlag);
    addParameter(p, 'sshUnits', defaultSSHUnits);
    parse(p, ssh_data, lat, lon, areamap, cyc, varargin{:});
    minimumArea = p.Results.minimumArea;
    thresholdStep = p.Results.thresholdStep;
    isPadding = p.Results.isPadding;
    SSH_Units = p.Results.sshUnits;
    disp(minimumArea);
    if isa(minimumArea, 'char')
        disp('Minimum area was a string, converting to double.');
        minimumArea = str2double(minimumArea);
        disp(minimumArea);
    end
    if isa(thresholdStep, 'char')
        disp('Threshold step was a string, converting to double.');
        thresholdStep = str2double(thresholdStep);
        disp(thresholdStep);
    end
    if strcmp(SSH_Units, 'meters')
        ssh_data = ssh_data * 100;
    elseif strcmp(SSH_Units, 'centimeters')
        max_val = max(ssh_data(:));
        min_val = max(ssh_data(:));
        if max_val < 1 && min_val > -1
            ssh_data = ssh_data * 100;
        elseif max_val < 100 && min_val > -100

        else
            error('Could not figure out what units the SSH data provided is in. Please specify it as an additional parameter: sshUnits');
        end
    end

    %Check if the grid is regular (differences between lats and lons are equal)
    lat_diffs = lat(2:end) - lat(1:end-1);
    lat_diffs2 = lat_diffs(2:end) - lat_diffs(1:end-1);
    lon_diffs = lon(2:end) - lon(1:end-1);
    lon_diffs(lon_diffs <= -180) = lon_diffs(lon_diffs <= -180) + 360;
    lon_diffs(lon_diffs >= 180) = lon_diffs(lon_diffs >= 180) - 360;
    lon_diffs = abs(lon_diffs);
    lon_diffs2 = lon_diffs(2:end) - lon_diffs(1:end-1);
    if all(lat_diffs2 == 0) && all(lon_diffs2 == 0)
        % Regular grid, create a georasterref object to get eddy's centroid
        geo_raster_lat_limit = [lat(1) lat(end)];
        if lon(1) > lon(end)
            geo_raster_lon_limit = [lon(1) (360 + lon(end))];
        else
            geo_raster_lon_limit = [lon(1) lon(end)];
        end

        R = georasterref('LatLim', geo_raster_lat_limit, 'LonLim', geo_raster_lon_limit, 'RasterSize', ...
         size(ssh_data), 'ColumnsStartFrom', 'south', 'RowsStartFrom', 'west');
    else
        % Use normal indexing to get eddy's centroid
        R = [];
    end
    disp('About to get extrema');

    extrema = get_extrema(ssh_data, cyc);
    disp('Got extrema');
    if isPadding
        origExtrema = extrema;
        extrema = [zeros(size(extrema, 1), 200), extrema, zeros(size(extrema, 1), 200)];
        sshExtended = [ssh_data(:, end-199:end), ssh_data(:, :), ssh_data(:, 1:200)];
        [extrema_lat_indexes, extrema_lon_indexes] = ind2sub(size(extrema), find(extrema == 1));

        extrema(:, 1:200) = origExtrema(:, end-199:end);
        extrema(:, end-199:end) = origExtrema(:, 1:200);
    else
        [extrema_lat_indexes, extrema_lon_indexes] = ind2sub(size(extrema), find(extrema == 1));
        sshExtended = ssh_data;
    end

    disp('Scanning');
    eddies = new_eddy();
    eddies(length(extrema_lat_indexes)).Date = NaN;
    cyc_sshExtended = sshExtended * cyc;
    parfor i = 1:length(extrema_lat_indexes) % Normally a parfor. Modified to a for loop solely for itasca testing
        curr_lat_index = extrema_lat_indexes(i); curr_lon_index = extrema_lon_indexes(i);
        e = thresholdBU(cyc, curr_lat_index-5, curr_lat_index+5, curr_lon_index-5, curr_lon_index+5, ...
            sshExtended, extrema, curr_lat_index, curr_lon_index, sshExtended(curr_lat_index, curr_lon_index), ...
            thresholdStep, NaN, ...
            zeros(size(sshExtended)), lat, lon, R, areamap, minimumArea, isPadding, cyc_sshExtended);
        %disp(e);
        if ~isempty(e.Stats)
            %disp(['Adding eddy at index: ', num2str(i)]);
            %disp(e);
            eddies(i) = e;
        end
    end
    %lat_array = [eddies.Lat];
    %disp(lat_array);
    mask = false(1, length(eddies));
    for i = 1:length(eddies)
        if isempty(eddies(i).Lat)
    %        disp(['Empty lat array index at ', num2str(i)]);
            mask(i) = true;
        end
    end
    disp('Scanned');
    %mask = cellfun('isempty', {eddies.Lat});
    eddies = eddies(~mask);
    %lat_array = [eddies.Lat];
    %disp(lat_array);
end

function [eddy] = thresholdBU(cyc, block_bottom_index, block_top_index, block_left_index, block_right_index, ...
        ssh, extrema, extrema_lat_index, extrema_lon_index, thresh, threshold_step, last_step, previous, ...
        lat, lon, R, areamap, min_pixel_size, is_padding, cyc_ssh)
% THRESHOLDBU Get an eddy by bottom up method
%   cyc: 1 for anticyclonic and -1 for cyclonic
%   block_bottom(top/left/right)_index: index of the bottom/top/left/right of the block that will be used for
%   thresholding
%   ssh: ssh data(extended if is_padding is true)
%   extrema: logical index of ssh extrema
%   extrema_lat/lon_index: lat/lon index of the extremum that is being used to find an eddy
%   thresh: current threshold that is being used to find an eddy
%   threshold_step: the step to increase/decrease threshold value, based on eddy type
%   last_step: the last step was used for thresholding
%   previous: 2d logical array of the connected component that contains the extremum in the last thresholdBU call
%   lat: 1d array of latitudes of the ssh grid
%   lon: 1d array of longitudes of the ssh grid
%   R: the georasterref object for SSH grid
%   areamap: 1D or 2D array of reference to area of each pixel in SSH grid
%   min_pixel_size: minimum number of pixels for an eddy
%   is_padding: whether or not the SSH data is padded

    switch cyc
        case 1
            intensity = 'MaxIntensity';
        case -1
            intensity = 'MinIntensity';
        otherwise
            error('Invalid cyc');
    end

    if block_bottom_index < 1 || block_top_index > size(ssh, 1) || ...
            block_left_index < 1 || block_right_index > size(ssh, 2)
        edgeOfWorld = true;

        while block_bottom_index < 1 || block_top_index > size(ssh, 1) || ...
            block_left_index < 1 || block_right_index > size(ssh, 2)
            % Make sure that the block is inside the grid

            block_bottom_index = block_bottom_index + 1;
            block_top_index = block_top_index - 1;
            block_left_index = block_left_index + 1;
            block_right_index = block_right_index - 1;
            if block_top_index <= block_bottom_index + 2 || block_right_index <= block_left_index + 2
                % If the block is too small, just return an empty eddy
                eddy = new_eddy();
                return;
            end
        end

        block = ssh(block_bottom_index:block_top_index, block_left_index:block_right_index);

        extremaBlock = extrema(block_bottom_index:block_top_index, block_left_index:block_right_index);

    else
        edgeOfWorld = false;
        block = ssh(block_bottom_index:block_top_index, block_left_index:block_right_index);
        extremaBlock = extrema(block_bottom_index:block_top_index, block_left_index:block_right_index);
    end

    if isnan(last_step)
        step = threshold_step;
    else
        step = last_step;
    end

    iter = 1;
    while true
        iter = iter+1;
        if iter > 5000

            perim = imdilate(logical(current), ones(3)) & ~logical(current);
            if all(isnan(block(perim)))
                eddy = new_eddy();
                return;
            end
            disp('potential infinite loop')
        end

        bw = cyc .* block >= cyc .* thresh;
        labels = bwlabel(bw);

        extrema_label = labels(extrema_lat_index - block_bottom_index + 1, extrema_lon_index - block_left_index + 1);
        current = labels == extrema_label;
        currentExtrema = extremaBlock(current);

        existing_pixel_at_box_edge = outterRing(labels, extrema_label);

        if sum(currentExtrema) > 1 || ( edgeOfWorld && existing_pixel_at_box_edge)

            if step ~= threshold_step
                % Go back to last threshold
                thresh = thresh + cyc*step;
                step = threshold_step;
                thresh = thresh - cyc*step;
                continue;
            end

            if size(block, 1) ~= size(previous(block_bottom_index:block_top_index, block_left_index:block_right_index), 1)
                prevBlock = block(2:end-1, 2:end-1);
            else
                prevBlock = block;
            end

            perim = imdilate(logical(previous(block_bottom_index:block_top_index, block_left_index:block_right_index)), ones(3)) ...
                & ~logical(previous(block_bottom_index:block_top_index, block_left_index:block_right_index));
            nan = isnan(prevBlock(perim));
            if sum(nan) / length(nan) > .3
                %if more than half of your perimeter is land, then throw it out.
                eddy = new_eddy();
                return;
            end

            if sum(previous(:)) < min_pixel_size
                    eddy = new_eddy();
                    return;
            end

            perim = bwperim(previous);
            meanPerim = mean(ssh(logical(perim)));
            amp = cyc * (ssh(extrema_lat_index, extrema_lon_index)-meanPerim);

            stats = regionprops(previous, ssh, 'Area', 'Extrema',...
                'PixelIdxList', intensity, 'ConvexImage', 'PixelList', ...
                'Solidity', 'Extent', 'Orientation', 'MajorAxisLength', ...
                'MinorAxisLength');

            stats.Intensity = stats.(intensity);
            stats = rmfield(stats, intensity);

            if is_padding
                [idx, r, c] = extidx2original(stats.PixelIdxList, [length(lat) length(lon)], size(ssh));
                stats.PixelIdxList = idx;
            else
                [r, c] = ind2sub(size(ssh), stats.PixelIdxList);
            end

            stats.PixelList = [r, c];

            % Getting geodesic speed
            if is_padding
                geoSpeed = mean_geo_speed(ssh(:, 201:end-200), stats.PixelIdxList, lat, lon);
                if ~isempty(R)
                    [elat, elon] = weighted_centroid(cyc_ssh(:, 201:end-200), stats.PixelList, stats.PixelIdxList, R);
                else
                    [elat, elon] = weighted_centroid_irregular_grid(cyc_ssh(:, 201:end-200), stats.PixelList, stats.PixelIdxList, lat, lon);
                end
            else
                geoSpeed = mean_geo_speed(ssh, stats.PixelIdxList, lat, lon);
                if ~isempty(R)
                    [elat, elon] = weighted_centroid(cyc_ssh, stats.PixelList, stats.PixelIdxList, R);
                else
                    [elat, elon] = weighted_centroid_irregular_grid(cyc_ssh, stats.PixelList, stats.PixelIdxList, lat, lon);
                end
            end

            % weighted_centroid returns lon from 0-360, fix this
            % TODO: should we also fix lat lon -270 to 80?
            elon = (elon > 180).*(elon - 360) + (elon <= 180).*elon;

            % Getting surface area of the eddy
            if all(size(areamap) == [length(lat) length(lon)])
                % area is 2D array for areas of pixels at [lat, lon]
                sarea = sum(areamap(stats.PixelIdxList));
            elseif any(size(areamap) == [1 1]) && length(areamap) == length(lat)
                % Area is 1D array for areas of pixels at a specific latitude
                sarea = sum(areamap(stats.PixelList(:, 1)));
            else
                % Invalid areamap
                sarea = NaN;
            end

            eddy = new_eddy(rmfield(stats, 'PixelList'), amp, elat, elon, thresh, sarea, cyc, geoSpeed, 'ESv2');

            return
        end

        if existing_pixel_at_box_edge
            %disp('expanding size');
            eddy = thresholdBU(cyc, block_bottom_index-1, block_top_index+1, block_left_index-1,...
                block_right_index+1, ssh, extrema, extrema_lat_index, extrema_lon_index, thresh, threshold_step, ...
                step, previous, lat, lon, R, areamap, min_pixel_size, ...
                is_padding, cyc_ssh);
            return
        end

        previous(block_bottom_index:block_top_index, block_left_index:block_right_index) = current;

        step = step * 2; % double the step for less number of iterations
        thresh = thresh - cyc*step;
    end

end

function [ extrema ] = get_extrema( ssh, cyc )
%GET_EXTREMA Returns a matrix containing all of the minima or maxima
%(depending on the value of cyc) in a 5x5 matrix within the 2D ssh field.
% ssh: ssh slice containing NaNs for land
% cyc: 1 for anticyclonic, -1 for cyclonic

    padded = [ssh(:,end-1:end) ssh(:,:) ssh(:,1:2)];
    padded(isnan(padded)) = cyc*-Inf;
    padded = padarray(padded, [1, 1], cyc*-Inf);
    n = ones(5); n(3, 3) = 0;
    padded = cyc .* padded; % Want to find right extrema for cyclonic and anticyc eddies

    extrema = padded > imdilate(padded, n);
    extrema = extrema(2:end-1, 4:end-3);
end

function [res] = outterRing(box, val)
    ring = [box(1, 1:end)'; box(end, 1:end)'; box(1:end, 1); box(1:end, end)];
    res = any(ring == val);
end

function [idx, row, col] = extidx2original(idx, original_size, extended_size)
%EXTIDX2ORIGINAL Convert from extended indexes to original indexes
    [row, col] = ind2sub(extended_size,idx);

    offright = col > (extended_size(2) + original_size(2)) / 2;
    offleft = col < (extended_size(2) - original_size(2)) / 2 + 1;
    notoff = ~(offleft | offright);
    col(offright)=col(offright) - (extended_size(2) + original_size(2)) / 2;
    col(offleft) = col(offleft) + original_size(2) - (extended_size(2) - original_size(2)) / 2;
    col(notoff)=col(notoff) - (extended_size(2) - original_size(2)) / 2;

    idx = sub2ind(original_size,row,col);
end

function mean_speed = mean_geo_speed(ssh, pixels, lat, lon)
%MEAN_GEO_SPEED Returns the mean geostrophic speed for pixels.
% lat and lon should yield the correct values for indices of ssh.

    g = 980.665; % cm/s
    omega = 7.2921e-5;
    [x, y] = ind2sub(size(ssh), pixels);
    lats = lat(x);
    f = 2*omega*sin(lats);
    f(f == 0) = 2*omega*sin(0.25); % f is coriolis frequency

    dSSH_y = ssh(sub2ind(size(ssh), x, mod(y, size(ssh, 2))+1)) - ...
        ssh(sub2ind(size(ssh), x, mod(y-2, size(ssh, 2))+1));
    dSSH_x = ssh(sub2ind(size(ssh), min(x+1, zeros(size(x)) + size(ssh, 1)), y))...
        - ssh(sub2ind(size(ssh), max(x-1, ones(size(x))), y));

    dy = deg2km(distance(lat(x), lon(mod(y, size(ssh, 1))+1), lat(x), lon(mod(y-2, size(ssh, 1))+1))) * 100000;
    dx = (min(x+1, size(ssh, 1)) - max(1, x-1)) .* 111.12 .* 100000 .* 180 ./ (length(lats) - 1);

    vs = -g .* (dSSH_y) ./ (2.*f .* dy);
    us = g .* (dSSH_x) ./ (2 .* f .* dx );
    speeds = sqrt(us .^2 + vs .^2);
    mean_speed = nanmean(speeds);
end

function [lat, lon] = weighted_centroid(cyc_ssh, pixellist, pixelidxlist, R)
%WEIGHTED_CENTROID Returns the location of the weighted centroid for the
%pixels provided
    %ssh = cyc * ssh;
    shift = min(cyc_ssh(pixelidxlist));

    x = pixellist(:, 1);
    y = pixellist(:, 2);

    if min(y) == 1 && max(y) == size(cyc_ssh,2)
        y(y > size(cyc_ssh,2)/2) = y(y > size(cyc_ssh,2)/2) - size(cyc_ssh,2);
    end

    mask = ~isnan(cyc_ssh(pixelidxlist));
    intensities = cyc_ssh(pixelidxlist)+shift;
    intensities = intensities - min(intensities); % should start from 0

    xbar = sum(x(mask) .* intensities(mask).^2) / sum(intensities(mask).^2);
    ybar = sum(y(mask) .* intensities(mask).^2) / sum(intensities(mask).^2);

    if ybar <= 0
        ybar = ybar + size(cyc_ssh,2);
    end

    [lat, lon] = pix2latlon(R, xbar, ybar);

end

function [lat, lon] = weighted_centroid_irregular_grid(cyc_ssh, pixellist, pixelidxlist, lats, lons)
%WEIGHTED_CENTROID_IRREGULAR_GRID Returns the location of the weighted centroid for the
%pixels provided
    %ssh = cyc * ssh;
    shift = min(cyc_ssh(pixelidxlist));

    x = pixellist(:, 1);
    y = pixellist(:, 2);

    if min(y) == 1 && max(y) == size(cyc_ssh,2)
        y(y > size(cyc_ssh,2)/2) = y(y > size(cyc_ssh,2)/2) - size(cyc_ssh,2);
    end

    mask = ~isnan(cyc_ssh(pixelidxlist));
    intensities = cyc_ssh(pixelidxlist)+shift;
    intensities = intensities - min(intensities); % should start from 0
    xbar = sum(x(mask) .* intensities(mask).^2) / sum(intensities(mask).^2);
    ybar = sum(y(mask) .* intensities(mask).^2) / sum(intensities(mask).^2);

    if ybar <= 0
        ybar = ybar + size(cyc_ssh,2);
    end

    x_lower = floor(xbar);
    x_upper = ceil(xbar);
    if x_lower == 0
        lat = lats(1);
    elseif x_upper == length(lats) + 1
        lat = lats(end);
    else
        lat_lower = lats(x_lower);
        lat_upper = lats(x_upper);
        lat = lat_lower + (lat_upper - lat_lower) * (xbar - x_lower) / (x_upper - x_lower);
        if isnan(lat)
            lat = 90;
        end
    end
    y_lower = floor(ybar);
    y_upper = ceil(ybar);
    if y_lower == 0
        lon = lons(1);
    elseif y_upper == length(lons) + 1
        lon = lons(end);
    else
        lon_lower = lons(y_lower);
        lon_upper = lons(y_upper);
        if lon_upper - lon_lower > 180
            lon_upper = lon_upper - 360;
        elseif lon_lower - lon_upper > 180
            lon_lower = lon_lower - 360;
        end
        lon = lon_lower + (lon_upper - lon_lower) * (ybar - y_lower) / (y_upper - y_lower);
    end

end

function eddy = new_eddy(STATS, amplitude, lat, lon, thresh, sa, cyc, geospeed, detect)
%NEW_EDDY Initializes new eddy objects, run with no arguments to create an
%empty matrix
    if nargin
        eddy = struct('Stats', STATS, ...
            'Lat', lat, ...
            'Lon', lon, ...
            'Amplitude', amplitude, ...
            'ThreshFound', thresh, ...
            'SurfaceArea', sa, ...
            'Date', NaN, ...
            'Cyc', cyc, ...
            'MeanGeoSpeed', geospeed, ...
            'DetectedBy', detect);
    else
        eddy = struct('Stats', [], ...
            'Lat', [], ...
            'Lon', [], ...
            'Amplitude', [], ...
            'ThreshFound', [], ...
            'SurfaceArea', [], ...
            'Date', [], ...
            'Cyc', [], ...
            'MeanGeoSpeed', [], ...
            'DetectedBy', []);
    end
end