function [elc, lab] = elec1020_locate(pnt, dhk, nas, ini, lpa, rpa, feedback)

% ELEC1020_LOCATE determines 10-20 (20%, 10% and 5%) electrode positions
% on a scalp surface that is described by its surface triangulation

% Copyright (C) 2003, 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/>.

if nargin<7
  feedback = false;
end

% determine the approximate location of the vertex
ori = (lpa+rpa+nas+ini)/4;      % center of head
ver =  cross(rpa-lpa, nas-ini); % orientation
ver = ver /sqrt(norm(ver));     % make correct length
ver = ori + 0.7*ver;            % location from center of head

if feedback
  figure
  ft_plot_mesh(struct('pos', pnt, 'tri', dhk), 'edgecolor', 'none', 'facecolor', 'skin')
  lighting gouraud
  material dull
  lightangle(0, 90);
  alpha 0.9
  ft_plot_mesh(nas, 'vertexsize', 30)
  ft_plot_mesh(lpa, 'vertexsize', 30)
  ft_plot_mesh(ini, 'vertexsize', 30)
  ft_plot_mesh(rpa, 'vertexsize', 30)
  ft_plot_mesh(ver, 'vertexsize', 30)
  grid on
  hold on
  view([1 1 0.5])
end

% point near LPA that is at 50% of left lower contour
[cnt1, cnt2] = elec1020_follow(pnt, dhk, nas, lpa, ini, feedback);
mle = elec1020_fraction(cnt1, cnt2, 0.5);

% point near RPA that is at 50% of right lower contour
[cnt1, cnt2] = elec1020_follow(pnt, dhk, nas, rpa, ini, feedback);
mre = elec1020_fraction(cnt1, cnt2, 0.5);

% determine two points that approximate the vertex
[cnt1, cnt2] = elec1020_follow(pnt, dhk, nas, ver, ini, feedback);
ver1 = elec1020_fraction(cnt1, cnt2, 0.5);
[cnt1, cnt2] = elec1020_follow(pnt, dhk, mle, ver, mre, feedback);
ver2 = elec1020_fraction(cnt1, cnt2, 0.5);

% refined estimate is the average of these two
ver = (ver1+ver2)/2;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% start contouring
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% ant-post contour through vertex
fprintf('constructing vertical ant-post contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, nas, ver, ini, feedback);
Nz   = elec1020_fraction(cnt1, cnt2,  0/20);
NFpz = elec1020_fraction(cnt1, cnt2,  1/20);
Fpz  = elec1020_fraction(cnt1, cnt2,  2/20);
AFpz = elec1020_fraction(cnt1, cnt2,  3/20);
AFz  = elec1020_fraction(cnt1, cnt2,  4/20);
AFFz = elec1020_fraction(cnt1, cnt2,  5/20);
Fz   = elec1020_fraction(cnt1, cnt2,  6/20);
FFCz = elec1020_fraction(cnt1, cnt2,  7/20);
FCz  = elec1020_fraction(cnt1, cnt2,  8/20);
FCCz = elec1020_fraction(cnt1, cnt2,  9/20);
Cz   = elec1020_fraction(cnt1, cnt2, 10/20);
CCPz = elec1020_fraction(cnt1, cnt2, 11/20);
CPz  = elec1020_fraction(cnt1, cnt2, 12/20);
CPPz = elec1020_fraction(cnt1, cnt2, 13/20);
Pz   = elec1020_fraction(cnt1, cnt2, 14/20);
PPOz = elec1020_fraction(cnt1, cnt2, 15/20);
POz  = elec1020_fraction(cnt1, cnt2, 16/20);
POOz = elec1020_fraction(cnt1, cnt2, 17/20);
Oz   = elec1020_fraction(cnt1, cnt2, 18/20);
OIz  = elec1020_fraction(cnt1, cnt2, 19/20);
Iz   = elec1020_fraction(cnt1, cnt2, 20/20);

% left-right through vertex
fprintf('constructing C contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, mle, ver, mre, feedback);
T9   = elec1020_fraction(cnt1, cnt2,  0/20);
T9h  = elec1020_fraction(cnt1, cnt2,  1/20);
T7   = elec1020_fraction(cnt1, cnt2,  2/20);
T7h  = elec1020_fraction(cnt1, cnt2,  3/20);
C5   = elec1020_fraction(cnt1, cnt2,  4/20);
C5h  = elec1020_fraction(cnt1, cnt2,  5/20);
C3   = elec1020_fraction(cnt1, cnt2,  6/20);
C3h  = elec1020_fraction(cnt1, cnt2,  7/20);
C1   = elec1020_fraction(cnt1, cnt2,  8/20);
C1h  = elec1020_fraction(cnt1, cnt2,  9/20);
Cz   = elec1020_fraction(cnt1, cnt2, 10/20);
C2h  = elec1020_fraction(cnt1, cnt2, 11/20);
C2   = elec1020_fraction(cnt1, cnt2, 12/20);
C4h  = elec1020_fraction(cnt1, cnt2, 13/20);
C4   = elec1020_fraction(cnt1, cnt2, 14/20);
C6h  = elec1020_fraction(cnt1, cnt2, 15/20);
C6   = elec1020_fraction(cnt1, cnt2, 16/20);
T8h  = elec1020_fraction(cnt1, cnt2, 17/20);
T8   = elec1020_fraction(cnt1, cnt2, 18/20);
T10h = elec1020_fraction(cnt1, cnt2, 19/20);
T10  = elec1020_fraction(cnt1, cnt2, 20/20);

% horizontal ant-post through T7
fprintf('constructing horizontal left contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, Fpz, T7, Oz, feedback);
Fp1h = elec1020_fraction(cnt1, cnt2,  1/20);
Fp1  = elec1020_fraction(cnt1, cnt2,  2/20);
AFp7 = elec1020_fraction(cnt1, cnt2,  3/20);
AF7  = elec1020_fraction(cnt1, cnt2,  4/20);
AFF7 = elec1020_fraction(cnt1, cnt2,  5/20);
F7   = elec1020_fraction(cnt1, cnt2,  6/20);
FFT7 = elec1020_fraction(cnt1, cnt2,  7/20);
FT7  = elec1020_fraction(cnt1, cnt2,  8/20);
FTT7 = elec1020_fraction(cnt1, cnt2,  9/20);
T7   = elec1020_fraction(cnt1, cnt2, 10/20);
TTP7 = elec1020_fraction(cnt1, cnt2, 11/20);
TP7  = elec1020_fraction(cnt1, cnt2, 12/20);
TPP7 = elec1020_fraction(cnt1, cnt2, 13/20);
P7   = elec1020_fraction(cnt1, cnt2, 14/20);
PPO7 = elec1020_fraction(cnt1, cnt2, 15/20);
PO7  = elec1020_fraction(cnt1, cnt2, 16/20);
POO7 = elec1020_fraction(cnt1, cnt2, 17/20);
O1   = elec1020_fraction(cnt1, cnt2, 18/20);
O1h  = elec1020_fraction(cnt1, cnt2, 19/20);

% horizontal ant-post through T8
fprintf('constructing horizontal right contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, Fpz, T8, Oz, feedback);
Fp2h = elec1020_fraction(cnt1, cnt2,  1/20);
Fp2  = elec1020_fraction(cnt1, cnt2,  2/20);
AFp8 = elec1020_fraction(cnt1, cnt2,  3/20);
AF8  = elec1020_fraction(cnt1, cnt2,  4/20);
AFF8 = elec1020_fraction(cnt1, cnt2,  5/20);
F8   = elec1020_fraction(cnt1, cnt2,  6/20);
FFT8 = elec1020_fraction(cnt1, cnt2,  7/20);
FT8  = elec1020_fraction(cnt1, cnt2,  8/20);
FTT8 = elec1020_fraction(cnt1, cnt2,  9/20);
T8   = elec1020_fraction(cnt1, cnt2, 10/20);
TTP8 = elec1020_fraction(cnt1, cnt2, 11/20);
TP8  = elec1020_fraction(cnt1, cnt2, 12/20);
TPP8 = elec1020_fraction(cnt1, cnt2, 13/20);
P8   = elec1020_fraction(cnt1, cnt2, 14/20);
PPO8 = elec1020_fraction(cnt1, cnt2, 15/20);
PO8  = elec1020_fraction(cnt1, cnt2, 16/20);
POO8 = elec1020_fraction(cnt1, cnt2, 17/20);
O2   = elec1020_fraction(cnt1, cnt2, 18/20);
O2h  = elec1020_fraction(cnt1, cnt2, 19/20);

fprintf('constructing AFp contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, AFp7, AFpz, AFp8, feedback);
AFp7h  = elec1020_fraction(cnt1, cnt2,  1/16);
AFp5   = elec1020_fraction(cnt1, cnt2,  2/16);
AFp5h  = elec1020_fraction(cnt1, cnt2,  3/16);
AFp3   = elec1020_fraction(cnt1, cnt2,  4/16);
AFp3h  = elec1020_fraction(cnt1, cnt2,  5/16);
AFp1   = elec1020_fraction(cnt1, cnt2,  6/16);
AFp1h  = elec1020_fraction(cnt1, cnt2,  7/16);
AFp2h  = elec1020_fraction(cnt1, cnt2,  9/16);
AFp2   = elec1020_fraction(cnt1, cnt2, 10/16);
AFp4h  = elec1020_fraction(cnt1, cnt2, 11/16);
AFp4   = elec1020_fraction(cnt1, cnt2, 12/16);
AFp6h  = elec1020_fraction(cnt1, cnt2, 13/16);
AFp6   = elec1020_fraction(cnt1, cnt2, 14/16);
AFp8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing AF contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, AF7, AFz, AF8, feedback);
AF7h  = elec1020_fraction(cnt1, cnt2,  1/16);
AF5   = elec1020_fraction(cnt1, cnt2,  2/16);
AF5h  = elec1020_fraction(cnt1, cnt2,  3/16);
AF3   = elec1020_fraction(cnt1, cnt2,  4/16);
AF3h  = elec1020_fraction(cnt1, cnt2,  5/16);
AF1   = elec1020_fraction(cnt1, cnt2,  6/16);
AF1h  = elec1020_fraction(cnt1, cnt2,  7/16);
AF2h  = elec1020_fraction(cnt1, cnt2,  9/16);
AF2   = elec1020_fraction(cnt1, cnt2, 10/16);
AF4h  = elec1020_fraction(cnt1, cnt2, 11/16);
AF4   = elec1020_fraction(cnt1, cnt2, 12/16);
AF6h  = elec1020_fraction(cnt1, cnt2, 13/16);
AF6   = elec1020_fraction(cnt1, cnt2, 14/16);
AF8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing AFF contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, AFF7, AFFz, AFF8, feedback);
AFF7h  = elec1020_fraction(cnt1, cnt2,  1/16);
AFF5   = elec1020_fraction(cnt1, cnt2,  2/16);
AFF5h  = elec1020_fraction(cnt1, cnt2,  3/16);
AFF3   = elec1020_fraction(cnt1, cnt2,  4/16);
AFF3h  = elec1020_fraction(cnt1, cnt2,  5/16);
AFF1   = elec1020_fraction(cnt1, cnt2,  6/16);
AFF1h  = elec1020_fraction(cnt1, cnt2,  7/16);
AFF2h  = elec1020_fraction(cnt1, cnt2,  9/16);
AFF2   = elec1020_fraction(cnt1, cnt2, 10/16);
AFF4h  = elec1020_fraction(cnt1, cnt2, 11/16);
AFF4   = elec1020_fraction(cnt1, cnt2, 12/16);
AFF6h  = elec1020_fraction(cnt1, cnt2, 13/16);
AFF6   = elec1020_fraction(cnt1, cnt2, 14/16);
AFF8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing F contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, F7, Fz, F8, feedback);
F7h  = elec1020_fraction(cnt1, cnt2,  1/16);
F5   = elec1020_fraction(cnt1, cnt2,  2/16);
F5h  = elec1020_fraction(cnt1, cnt2,  3/16);
F3   = elec1020_fraction(cnt1, cnt2,  4/16);
F3h  = elec1020_fraction(cnt1, cnt2,  5/16);
F1   = elec1020_fraction(cnt1, cnt2,  6/16);
F1h  = elec1020_fraction(cnt1, cnt2,  7/16);
F2h  = elec1020_fraction(cnt1, cnt2,  9/16);
F2   = elec1020_fraction(cnt1, cnt2, 10/16);
F4h  = elec1020_fraction(cnt1, cnt2, 11/16);
F4   = elec1020_fraction(cnt1, cnt2, 12/16);
F6h  = elec1020_fraction(cnt1, cnt2, 13/16);
F6   = elec1020_fraction(cnt1, cnt2, 14/16);
F8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing FFC contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, FFT7, FFCz, FFT8, feedback);
FFT7h  = elec1020_fraction(cnt1, cnt2,  1/16);
FFC5   = elec1020_fraction(cnt1, cnt2,  2/16);
FFC5h  = elec1020_fraction(cnt1, cnt2,  3/16);
FFC3   = elec1020_fraction(cnt1, cnt2,  4/16);
FFC3h  = elec1020_fraction(cnt1, cnt2,  5/16);
FFC1   = elec1020_fraction(cnt1, cnt2,  6/16);
FFC1h  = elec1020_fraction(cnt1, cnt2,  7/16);
FFC2h  = elec1020_fraction(cnt1, cnt2,  9/16);
FFC2   = elec1020_fraction(cnt1, cnt2, 10/16);
FFC4h  = elec1020_fraction(cnt1, cnt2, 11/16);
FFC4   = elec1020_fraction(cnt1, cnt2, 12/16);
FFC6h  = elec1020_fraction(cnt1, cnt2, 13/16);
FFC6   = elec1020_fraction(cnt1, cnt2, 14/16);
FFT8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing FC contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, FT7, FCz, FT8, feedback);
FT7h  = elec1020_fraction(cnt1, cnt2,  1/16);
FC5   = elec1020_fraction(cnt1, cnt2,  2/16);
FC5h  = elec1020_fraction(cnt1, cnt2,  3/16);
FC3   = elec1020_fraction(cnt1, cnt2,  4/16);
FC3h  = elec1020_fraction(cnt1, cnt2,  5/16);
FC1   = elec1020_fraction(cnt1, cnt2,  6/16);
FC1h  = elec1020_fraction(cnt1, cnt2,  7/16);
FC2h  = elec1020_fraction(cnt1, cnt2,  9/16);
FC2   = elec1020_fraction(cnt1, cnt2, 10/16);
FC4h  = elec1020_fraction(cnt1, cnt2, 11/16);
FC4   = elec1020_fraction(cnt1, cnt2, 12/16);
FC6h  = elec1020_fraction(cnt1, cnt2, 13/16);
FC6   = elec1020_fraction(cnt1, cnt2, 14/16);
FT8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing FCC contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, FTT7, FCCz, FTT8, feedback);
FTT7h  = elec1020_fraction(cnt1, cnt2,  1/16);
FCC5   = elec1020_fraction(cnt1, cnt2,  2/16);
FCC5h  = elec1020_fraction(cnt1, cnt2,  3/16);
FCC3   = elec1020_fraction(cnt1, cnt2,  4/16);
FCC3h  = elec1020_fraction(cnt1, cnt2,  5/16);
FCC1   = elec1020_fraction(cnt1, cnt2,  6/16);
FCC1h  = elec1020_fraction(cnt1, cnt2,  7/16);
FCC2h  = elec1020_fraction(cnt1, cnt2,  9/16);
FCC2   = elec1020_fraction(cnt1, cnt2, 10/16);
FCC4h  = elec1020_fraction(cnt1, cnt2, 11/16);
FCC4   = elec1020_fraction(cnt1, cnt2, 12/16);
FCC6h  = elec1020_fraction(cnt1, cnt2, 13/16);
FCC6   = elec1020_fraction(cnt1, cnt2, 14/16);
FTT8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing CCP contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, TTP7, CCPz, TTP8, feedback);
TTP7h  = elec1020_fraction(cnt1, cnt2,  1/16);
CCP5   = elec1020_fraction(cnt1, cnt2,  2/16);
CCP5h  = elec1020_fraction(cnt1, cnt2,  3/16);
CCP3   = elec1020_fraction(cnt1, cnt2,  4/16);
CCP3h  = elec1020_fraction(cnt1, cnt2,  5/16);
CCP1   = elec1020_fraction(cnt1, cnt2,  6/16);
CCP1h  = elec1020_fraction(cnt1, cnt2,  7/16);
CCP2h  = elec1020_fraction(cnt1, cnt2,  9/16);
CCP2   = elec1020_fraction(cnt1, cnt2, 10/16);
CCP4h  = elec1020_fraction(cnt1, cnt2, 11/16);
CCP4   = elec1020_fraction(cnt1, cnt2, 12/16);
CCP6h  = elec1020_fraction(cnt1, cnt2, 13/16);
CCP6   = elec1020_fraction(cnt1, cnt2, 14/16);
TTP8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing CP contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, TP7, CPz, TP8, feedback);
TP7h  = elec1020_fraction(cnt1, cnt2,  1/16);
CP5   = elec1020_fraction(cnt1, cnt2,  2/16);
CP5h  = elec1020_fraction(cnt1, cnt2,  3/16);
CP3   = elec1020_fraction(cnt1, cnt2,  4/16);
CP3h  = elec1020_fraction(cnt1, cnt2,  5/16);
CP1   = elec1020_fraction(cnt1, cnt2,  6/16);
CP1h  = elec1020_fraction(cnt1, cnt2,  7/16);
CP2h  = elec1020_fraction(cnt1, cnt2,  9/16);
CP2   = elec1020_fraction(cnt1, cnt2, 10/16);
CP4h  = elec1020_fraction(cnt1, cnt2, 11/16);
CP4   = elec1020_fraction(cnt1, cnt2, 12/16);
CP6h  = elec1020_fraction(cnt1, cnt2, 13/16);
CP6   = elec1020_fraction(cnt1, cnt2, 14/16);
TP8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing CPP contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, TPP7, CPPz, TPP8, feedback);
TPP7h  = elec1020_fraction(cnt1, cnt2,  1/16);
CPP5   = elec1020_fraction(cnt1, cnt2,  2/16);
CPP5h  = elec1020_fraction(cnt1, cnt2,  3/16);
CPP3   = elec1020_fraction(cnt1, cnt2,  4/16);
CPP3h  = elec1020_fraction(cnt1, cnt2,  5/16);
CPP1   = elec1020_fraction(cnt1, cnt2,  6/16);
CPP1h  = elec1020_fraction(cnt1, cnt2,  7/16);
CPP2h  = elec1020_fraction(cnt1, cnt2,  9/16);
CPP2   = elec1020_fraction(cnt1, cnt2, 10/16);
CPP4h  = elec1020_fraction(cnt1, cnt2, 11/16);
CPP4   = elec1020_fraction(cnt1, cnt2, 12/16);
CPP6h  = elec1020_fraction(cnt1, cnt2, 13/16);
CPP6   = elec1020_fraction(cnt1, cnt2, 14/16);
TPP8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing P contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, P7, Pz, P8, feedback);
P7h  = elec1020_fraction(cnt1, cnt2,  1/16);
P5   = elec1020_fraction(cnt1, cnt2,  2/16);
P5h  = elec1020_fraction(cnt1, cnt2,  3/16);
P3   = elec1020_fraction(cnt1, cnt2,  4/16);
P3h  = elec1020_fraction(cnt1, cnt2,  5/16);
P1   = elec1020_fraction(cnt1, cnt2,  6/16);
P1h  = elec1020_fraction(cnt1, cnt2,  7/16);
P2h  = elec1020_fraction(cnt1, cnt2,  9/16);
P2   = elec1020_fraction(cnt1, cnt2, 10/16);
P4h  = elec1020_fraction(cnt1, cnt2, 11/16);
P4   = elec1020_fraction(cnt1, cnt2, 12/16);
P6h  = elec1020_fraction(cnt1, cnt2, 13/16);
P6   = elec1020_fraction(cnt1, cnt2, 14/16);
P8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing PPO contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, PPO7, PPOz, PPO8, feedback);
PPO7h  = elec1020_fraction(cnt1, cnt2,  1/16);
PPO5   = elec1020_fraction(cnt1, cnt2,  2/16);
PPO5h  = elec1020_fraction(cnt1, cnt2,  3/16);
PPO3   = elec1020_fraction(cnt1, cnt2,  4/16);
PPO3h  = elec1020_fraction(cnt1, cnt2,  5/16);
PPO1   = elec1020_fraction(cnt1, cnt2,  6/16);
PPO1h  = elec1020_fraction(cnt1, cnt2,  7/16);
PPO2h  = elec1020_fraction(cnt1, cnt2,  9/16);
PPO2   = elec1020_fraction(cnt1, cnt2, 10/16);
PPO4h  = elec1020_fraction(cnt1, cnt2, 11/16);
PPO4   = elec1020_fraction(cnt1, cnt2, 12/16);
PPO6h  = elec1020_fraction(cnt1, cnt2, 13/16);
PPO6   = elec1020_fraction(cnt1, cnt2, 14/16);
PPO8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing PO contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, PO7, POz, PO8, feedback);
PO7h  = elec1020_fraction(cnt1, cnt2,  1/16);
PO5   = elec1020_fraction(cnt1, cnt2,  2/16);
PO5h  = elec1020_fraction(cnt1, cnt2,  3/16);
PO3   = elec1020_fraction(cnt1, cnt2,  4/16);
PO3h  = elec1020_fraction(cnt1, cnt2,  5/16);
PO1   = elec1020_fraction(cnt1, cnt2,  6/16);
PO1h  = elec1020_fraction(cnt1, cnt2,  7/16);
PO2h  = elec1020_fraction(cnt1, cnt2,  9/16);
PO2   = elec1020_fraction(cnt1, cnt2, 10/16);
PO4h  = elec1020_fraction(cnt1, cnt2, 11/16);
PO4   = elec1020_fraction(cnt1, cnt2, 12/16);
PO6h  = elec1020_fraction(cnt1, cnt2, 13/16);
PO6   = elec1020_fraction(cnt1, cnt2, 14/16);
PO8h  = elec1020_fraction(cnt1, cnt2, 15/16);

fprintf('constructing POO contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, POO7, POOz, POO8, feedback);
POO7h  = elec1020_fraction(cnt1, cnt2,  1/16);
POO5   = elec1020_fraction(cnt1, cnt2,  2/16);
POO5h  = elec1020_fraction(cnt1, cnt2,  3/16);
POO3   = elec1020_fraction(cnt1, cnt2,  4/16);
POO3h  = elec1020_fraction(cnt1, cnt2,  5/16);
POO1   = elec1020_fraction(cnt1, cnt2,  6/16);
POO1h  = elec1020_fraction(cnt1, cnt2,  7/16);
POO2h  = elec1020_fraction(cnt1, cnt2,  9/16);
POO2   = elec1020_fraction(cnt1, cnt2, 10/16);
POO4h  = elec1020_fraction(cnt1, cnt2, 11/16);
POO4   = elec1020_fraction(cnt1, cnt2, 12/16);
POO6h  = elec1020_fraction(cnt1, cnt2, 13/16);
POO6   = elec1020_fraction(cnt1, cnt2, 14/16);
POO8h  = elec1020_fraction(cnt1, cnt2, 15/16);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% start contouring the low electrode locations
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% low horizontal ant-post through T9
fprintf('constructing low horizontal left contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, Nz, T9, Iz, feedback);
AFp9 = elec1020_fraction(cnt1, cnt2,  3/20);
AF9  = elec1020_fraction(cnt1, cnt2,  4/20);
AFF9 = elec1020_fraction(cnt1, cnt2,  5/20);
F9   = elec1020_fraction(cnt1, cnt2,  6/20);
FFT9 = elec1020_fraction(cnt1, cnt2,  7/20);
FT9  = elec1020_fraction(cnt1, cnt2,  8/20);
FTT9 = elec1020_fraction(cnt1, cnt2,  9/20);
T9   = elec1020_fraction(cnt1, cnt2, 10/20);
TTP9 = elec1020_fraction(cnt1, cnt2, 11/20);
TP9  = elec1020_fraction(cnt1, cnt2, 12/20);
TPP9 = elec1020_fraction(cnt1, cnt2, 13/20);
P9   = elec1020_fraction(cnt1, cnt2, 14/20);
PPO9 = elec1020_fraction(cnt1, cnt2, 15/20);
PO9  = elec1020_fraction(cnt1, cnt2, 16/20);
POO9 = elec1020_fraction(cnt1, cnt2, 17/20);
I1   = elec1020_fraction(cnt1, cnt2, 18/20);
I1h  = elec1020_fraction(cnt1, cnt2, 19/20);

[cnt1, cnt2] = elec1020_follow(pnt, dhk, NFpz, T9h, OIz, feedback);
AFp9h = elec1020_fraction(cnt1, cnt2,  3/20);
AF9h  = elec1020_fraction(cnt1, cnt2,  4/20);
AFF9h = elec1020_fraction(cnt1, cnt2,  5/20);
F9h   = elec1020_fraction(cnt1, cnt2,  6/20);
FFT9h = elec1020_fraction(cnt1, cnt2,  7/20);
FT9h  = elec1020_fraction(cnt1, cnt2,  8/20);
FTT9h = elec1020_fraction(cnt1, cnt2,  9/20);
T9h   = elec1020_fraction(cnt1, cnt2, 10/20);
TTP9h = elec1020_fraction(cnt1, cnt2, 11/20);
TP9h  = elec1020_fraction(cnt1, cnt2, 12/20);
TPP9h = elec1020_fraction(cnt1, cnt2, 13/20);
P9h   = elec1020_fraction(cnt1, cnt2, 14/20);
PPO9h = elec1020_fraction(cnt1, cnt2, 15/20);
PO9h  = elec1020_fraction(cnt1, cnt2, 16/20);
POO9h = elec1020_fraction(cnt1, cnt2, 17/20);
OI1   = elec1020_fraction(cnt1, cnt2, 18/20);
OI1h  = elec1020_fraction(cnt1, cnt2, 19/20);

% low horizontal ant-post through T10
fprintf('constructing low horizontal right contour\n');
[cnt1, cnt2] = elec1020_follow(pnt, dhk, Nz, T10, Iz, feedback);
AFp10 = elec1020_fraction(cnt1, cnt2,  3/20);
AF10  = elec1020_fraction(cnt1, cnt2,  4/20);
AFF10 = elec1020_fraction(cnt1, cnt2,  5/20);
F10   = elec1020_fraction(cnt1, cnt2,  6/20);
FFT10 = elec1020_fraction(cnt1, cnt2,  7/20);
FT10  = elec1020_fraction(cnt1, cnt2,  8/20);
FTT10 = elec1020_fraction(cnt1, cnt2,  9/20);
T10   = elec1020_fraction(cnt1, cnt2, 10/20);
TTP10 = elec1020_fraction(cnt1, cnt2, 11/20);
TP10  = elec1020_fraction(cnt1, cnt2, 12/20);
TPP10 = elec1020_fraction(cnt1, cnt2, 13/20);
P10   = elec1020_fraction(cnt1, cnt2, 14/20);
PPO10 = elec1020_fraction(cnt1, cnt2, 15/20);
PO10  = elec1020_fraction(cnt1, cnt2, 16/20);
POO10 = elec1020_fraction(cnt1, cnt2, 17/20);
I2    = elec1020_fraction(cnt1, cnt2, 18/20);
I2h   = elec1020_fraction(cnt1, cnt2, 19/20);

[cnt1, cnt2] = elec1020_follow(pnt, dhk, NFpz, T10h, OIz, feedback);
AFp10h = elec1020_fraction(cnt1, cnt2,  3/20);
AF10h  = elec1020_fraction(cnt1, cnt2,  4/20);
AFF10h = elec1020_fraction(cnt1, cnt2,  5/20);
F10h   = elec1020_fraction(cnt1, cnt2,  6/20);
FFT10h = elec1020_fraction(cnt1, cnt2,  7/20);
FT10h  = elec1020_fraction(cnt1, cnt2,  8/20);
FTT10h = elec1020_fraction(cnt1, cnt2,  9/20);
T10h   = elec1020_fraction(cnt1, cnt2, 10/20);
TTP10h = elec1020_fraction(cnt1, cnt2, 11/20);
TP10h  = elec1020_fraction(cnt1, cnt2, 12/20);
TPP10h = elec1020_fraction(cnt1, cnt2, 13/20);
P10h   = elec1020_fraction(cnt1, cnt2, 14/20);
PPO10h = elec1020_fraction(cnt1, cnt2, 15/20);
PO10h  = elec1020_fraction(cnt1, cnt2, 16/20);
POO10h = elec1020_fraction(cnt1, cnt2, 17/20);
OI2    = elec1020_fraction(cnt1, cnt2, 18/20);
OI2h   = elec1020_fraction(cnt1, cnt2, 19/20);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% collect all the computed electrode locations
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

lab = {
  'LPA'
  'RPA'
  'NAS'
  'INI'
  'Nz'
  'Fp1'
  'Fpz'
  'Fp2'
  'AF9'
  'AF7'
  'AF5'
  'AF3'
  'AF1'
  'AFz'
  'AF2'
  'AF4'
  'AF6'
  'AF8'
  'AF10'
  'F9'
  'F7'
  'F5'
  'F3'
  'F1'
  'Fz'
  'F2'
  'F4'
  'F6'
  'F8'
  'F10'
  'FT9'
  'FT7'
  'FC5'
  'FC3'
  'FC1'
  'FCz'
  'FC2'
  'FC4'
  'FC6'
  'FT8'
  'FT10'
  'T9'
  'T7'
  'C5'
  'C3'
  'C1'
  'Cz'
  'C2'
  'C4'
  'C6'
  'T8'
  'T10'
  'TP9'
  'TP7'
  'CP5'
  'CP3'
  'CP1'
  'CPz'
  'CP2'
  'CP4'
  'CP6'
  'TP8'
  'TP10'
  'P9'
  'P7'
  'P5'
  'P3'
  'P1'
  'Pz'
  'P2'
  'P4'
  'P6'
  'P8'
  'P10'
  'PO9'
  'PO7'
  'PO5'
  'PO3'
  'PO1'
  'POz'
  'PO2'
  'PO4'
  'PO6'
  'PO8'
  'PO10'
  'O1'
  'Oz'
  'O2'
  'I1'
  'Iz'
  'I2'
  'AFp9h'
  'AFp7h'
  'AFp5h'
  'AFp3h'
  'AFp1h'
  'AFp2h'
  'AFp4h'
  'AFp6h'
  'AFp8h'
  'AFp10h'
  'AFF9h'
  'AFF7h'
  'AFF5h'
  'AFF3h'
  'AFF1h'
  'AFF2h'
  'AFF4h'
  'AFF6h'
  'AFF8h'
  'AFF10h'
  'FFT9h'
  'FFT7h'
  'FFC5h'
  'FFC3h'
  'FFC1h'
  'FFC2h'
  'FFC4h'
  'FFC6h'
  'FFT8h'
  'FFT10h'
  'FTT9h'
  'FTT7h'
  'FCC5h'
  'FCC3h'
  'FCC1h'
  'FCC2h'
  'FCC4h'
  'FCC6h'
  'FTT8h'
  'FTT10h'
  'TTP9h'
  'TTP7h'
  'CCP5h'
  'CCP3h'
  'CCP1h'
  'CCP2h'
  'CCP4h'
  'CCP6h'
  'TTP8h'
  'TTP10h'
  'TPP9h'
  'TPP7h'
  'CPP5h'
  'CPP3h'
  'CPP1h'
  'CPP2h'
  'CPP4h'
  'CPP6h'
  'TPP8h'
  'TPP10h'
  'PPO9h'
  'PPO7h'
  'PPO5h'
  'PPO3h'
  'PPO1h'
  'PPO2h'
  'PPO4h'
  'PPO6h'
  'PPO8h'
  'PPO10h'
  'POO9h'
  'POO7h'
  'POO5h'
  'POO3h'
  'POO1h'
  'POO2h'
  'POO4h'
  'POO6h'
  'POO8h'
  'POO10h'
  'OI1h'
  'OI2h'
  'Fp1h'
  'Fp2h'
  'AF9h'
  'AF7h'
  'AF5h'
  'AF3h'
  'AF1h'
  'AF2h'
  'AF4h'
  'AF6h'
  'AF8h'
  'AF10h'
  'F9h'
  'F7h'
  'F5h'
  'F3h'
  'F1h'
  'F2h'
  'F4h'
  'F6h'
  'F8h'
  'F10h'
  'FT9h'
  'FT7h'
  'FC5h'
  'FC3h'
  'FC1h'
  'FC2h'
  'FC4h'
  'FC6h'
  'FT8h'
  'FT10h'
  'T9h'
  'T7h'
  'C5h'
  'C3h'
  'C1h'
  'C2h'
  'C4h'
  'C6h'
  'T8h'
  'T10h'
  'TP9h'
  'TP7h'
  'CP5h'
  'CP3h'
  'CP1h'
  'CP2h'
  'CP4h'
  'CP6h'
  'TP8h'
  'TP10h'
  'P9h'
  'P7h'
  'P5h'
  'P3h'
  'P1h'
  'P2h'
  'P4h'
  'P6h'
  'P8h'
  'P10h'
  'PO9h'
  'PO7h'
  'PO5h'
  'PO3h'
  'PO1h'
  'PO2h'
  'PO4h'
  'PO6h'
  'PO8h'
  'PO10h'
  'O1h'
  'O2h'
  'I1h'
  'I2h'
  'AFp9'
  'AFp7'
  'AFp5'
  'AFp3'
  'AFp1'
  'AFpz'
  'AFp2'
  'AFp4'
  'AFp6'
  'AFp8'
  'AFp10'
  'AFF9'
  'AFF7'
  'AFF5'
  'AFF3'
  'AFF1'
  'AFFz'
  'AFF2'
  'AFF4'
  'AFF6'
  'AFF8'
  'AFF10'
  'FFT9'
  'FFT7'
  'FFC5'
  'FFC3'
  'FFC1'
  'FFCz'
  'FFC2'
  'FFC4'
  'FFC6'
  'FFT8'
  'FFT10'
  'FTT9'
  'FTT7'
  'FCC5'
  'FCC3'
  'FCC1'
  'FCCz'
  'FCC2'
  'FCC4'
  'FCC6'
  'FTT8'
  'FTT10'
  'TTP9'
  'TTP7'
  'CCP5'
  'CCP3'
  'CCP1'
  'CCPz'
  'CCP2'
  'CCP4'
  'CCP6'
  'TTP8'
  'TTP10'
  'TPP9'
  'TPP7'
  'CPP5'
  'CPP3'
  'CPP1'
  'CPPz'
  'CPP2'
  'CPP4'
  'CPP6'
  'TPP8'
  'TPP10'
  'PPO9'
  'PPO7'
  'PPO5'
  'PPO3'
  'PPO1'
  'PPOz'
  'PPO2'
  'PPO4'
  'PPO6'
  'PPO8'
  'PPO10'
  'POO9'
  'POO7'
  'POO5'
  'POO3'
  'POO1'
  'POOz'
  'POO2'
  'POO4'
  'POO6'
  'POO8'
  'POO10'
  'OI1'
  'OIz'
  'OI2'
  'T3' % this is now called T7
  'T4' % this is now called T8
  'T5' % this is now called P7
  'T6' % this is now called P8
  'M1' % left mastoid
  'M2' % right mastoid
  'A1' % left ear lobe
  'A2' % right ear lobe
  };

% allow for upper case electrode position labels
NAS = nas;
INI = ini;
LPA = lpa;
RPA = rpa;

% it would be possible to assign locations to these old locations
% but there are no locations determined for M1/2 and A1/2
if false
  T3 = T7;
  T4 = T8;
  T5 = P7;
  T6 = P8;
end

% assign the known local electrode positions to the output matrix
nlab = numel(lab);
elc = ones(nlab,3) * nan;
for i=1:nlab
  if exist(lab{i}, 'var')
    eval(sprintf('elc(%d,:) = %s;', i, lab{i}));
  else
    fprintf('not placing electrode %s\n', lab{i});
  end
end

% remove unknown electrode positions
sel = ~isnan(elc(:,1));
elc = elc(sel, :);
lab = lab(sel);

if feedback
  elec = [];
  elec.elecpos = elc;
  elec.label = lab;
  ft_plot_sens(elec)
end


function [delta,h,alpha] = iswnbr(w,thetaSQR)
% [delta,h,alpha] = iswnbr(vSQR,thetaSQR)
%
% ISWNBR  Checks feasibility w.r.t. wide region/neighborhood of Sturm-Zhang.
%   vTAR:= (1-alpha)*max(h,v) projection v onto theta-central region
%   delta = (sqrt(n)/theta) * norm(vTAR - v) / norm(v)
%
% **********  INTERNAL FUNCTION OF SEDUMI **********
%
% See also sedumi

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

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

% ----------------------------------------
% r = n/thetaSQR
% hSQR = sumwNT/(r-|T|),  hubSQR = sumwNT/(r-|T| - |Q|)
% sumdifv = h*|T| - sumvT   (sumvT = sum(v_T), growing )
% sumdifw = hSQR*|T| - sumwT
% alpha = sumdifv / (r*h)
% deltaSQR = r * ( 2*alpha-alpha^2 - (1-alpha)^2 * sumdifw/gap )
% WE UPDATE sumdifv AND sumdifw IN A STABLE WAY
% ----------------------------------------
n = length(w); gap = sum(w);
sumwNT = gap;
r = n / thetaSQR;
cardT = 0; wQ = []; sumdifv = 0; sumdifw = 0;
cardQ = n;
hSQR = sumwNT / (r - cardT);  hubSQR = sumwNT / (r-(n-1));
for j = 1:n
    wj = w(j);
    if wj >= hubSQR              % wj >= hubSQR ==> not in T
        cardQ = cardQ - 1;
        hubSQR = sumwNT / (r-cardT-cardQ);
    elseif wj < hSQR             % wj < hSQR ==> in T
        cardT = cardT + 1;
        cardQ = cardQ - 1;
        hubSQR = (1-wj/sumwNT) * hubSQR;
        sumwNT = sumwNT - wj;
        oldhSQR = hSQR;
        hSQR = sumwNT / (r - cardT);
        sumdifw = sumdifw + (oldhSQR-wj) + cardT * (hSQR-oldhSQR);
        sumdifv = sumdifv + (sqrt(oldhSQR)-sqrt(wj)) + ...
            cardT * (sqrt(hSQR)-sqrt(oldhSQR));
    else                    % Inconclusive: j in Q
        wQ = [wQ;wj]; %#ok
    end % if
end % for
% ----------------------------------------
% The same treatment for the Q set, but we
% sort the (presumably short) wQ first.
% ----------------------------------------
if ~isempty(wQ)
    sort(wQ);
    STOP = 0; j = 1;
    while ~STOP
        wj = wQ(j);
        if wj >= hSQR
            STOP = 1;
        else
            cardT = cardT + 1;
            sumwNT = sumwNT - wj;
            oldhSQR = hSQR;
            hSQR = sumwNT / (r - cardT);
            sumdifw = sumdifw + (oldhSQR-wj) + cardT * (hSQR-oldhSQR);
            sumdifv = sumdifv + (sqrt(oldhSQR)-sqrt(wj)) + ...
                cardT * (sqrt(hSQR)-sqrt(oldhSQR));
            j = j+1;
            if j > length(wQ)
                STOP = 1;
            end
        end
    end
end % treatment Q
% ----------------------------------------
% alpha = sumdifv/(r*h)
% deltaSQR = r * ( 2*alpha-alpha^2 - (1-alpha)^2 * sumdifw/gap )
%  (THE ABOVE DIFFERENCE SHOULD NOT BE NUMERICALLY DANGEROUS,
%    SINCE alpha IS *SIGNIF* BIGGER THAN sumdifw/gap )
% ----------------------------------------
h = sqrt(hSQR);
alpha = sumdifv/ (r*h);
deltaSQR = alpha*(2-alpha) - (1-alpha)^2 * sumdifw/gap;
delta = sqrt(r*deltaSQR);


function [energyV,entropyV,sumAvgV,corrV,invDiffMomV,contrastV,...
    clustShadeV,clustProminV] = haralickTextureAllDir(scanArray3M, nL, offsetsM, flagv, hWait)
% [energyV,entropyV,sumAvgV,corrV,invDiffMomV,contrastV,...
%     clustShadeV,clustProminV] = haralickTextureAllDir(scanArray3M, nL, offsetsM, flagv, hWait)
%
% Haralick texture with cooccurrence matrix combined from all directions.
%
% APA, 05/12/2016

% Generate flags
if ~exist('flagv','var')
    flagv = ones(1,8);
elseif exist('flagv','var') && isempty(flagv)
    flagv = ones(1,8);
end

% % Flag to draw waitbar
% waitbarFlag = 0;
% if exist('hWait','var') && ishandle(hWait)
%     waitbarFlag = 1;
% end

% % Grid resolution
% deltaX = deltaXYZv(1);
% deltaY = deltaXYZv(2);
% deltaZ = deltaXYZv(3);
%
% % Get Block size to process
% slcWindow = floor(2*patchSizeV(3)/deltaZ);
% rowWindow = floor(2*patchSizeV(1)/deltaY);
% colWindow = floor(2*patchSizeV(2)/deltaX);
%
% % Make sure that the window is of odd size
% if mod(slcWindow,2) == 0
%     slcWindow = slcWindow + 1;
% end
% if mod(rowWindow,2) == 0
%     rowWindow = rowWindow + 1;
% end
% if mod(colWindow,2) == 0
%     colWindow = colWindow + 1;
% end

slcWindow = size(scanArray3M,3);
rowWindow = size(scanArray3M,1);
colWindow = size(scanArray3M,2);

% Build distance matrices
numColsPad = 1;
numRowsPad = 1;
numSlcsPad = 1;

% Get number of voxels per slice
[numRows, numCols, numSlices] = size(scanArray3M);
numVoxels = numRows*numCols;

minQ = min(scanArray3M(:));
maxQ = max(scanArray3M(:));
dq = (maxQ-minQ)/nL/4;
levels = linspace(minQ,maxQ,nL);
levels = linspace(dq,maxQ-dq,nL);
q1 = imquantize_cerr(scanArray3M,nL);
q2 = imquantize(scanArray3M, levels);
%levels = multithresh(scanArray3M, nL);
%q3 = imquantize(scanArray3M, levels);

% Pad doseArray2 so that sliding window works also for the edge voxels
%scanArrayTmp3M = padarray(scanArray3M,[numRowsPad numColsPad
%numSlcsPad],NaN,'both'); % aa commented
if exist('padarray.m','file')
    q = padarray(q1,[numRowsPad numColsPad numSlcsPad],NaN,'both');
else
    q = padarray_oct(q1,[numRowsPad numColsPad numSlcsPad],NaN,'both');
end

% Quantize the image
%nL = 16;
% if any(isnan(q)) % aa commented
%     nL = nL-1; % aa commented
% end % aa commented
%q = imquantize_cerr(scanArrayTmp3M,nL); % aa commented
%clear scanArrayTmp3M; % aa commented
qmax = max(q(:));
nanFlag = 0;
if any(isnan(q(:)))
    nanFlag = 1;
    q(isnan(q))=qmax+1;
end
qs=sort(unique(q));
lq=length(qs);

for k = 1:length(qs)
    q(q==qs(k)) = k;
end

q = uint16(q); % q is the quantized image
%numSlcsWithPadding = size(q,3);

% % Create indices for 2D blocks
% [m,n,~] = size(q);
% m = uint32(m);
% n = uint32(n);
% colWindow = uint32(colWindow);
% rowWindow = uint32(rowWindow);
% slcWindow = uint32(slcWindow);
%
% % Index calculation adapted from
% % http://stackoverflow.com/questions/25449279/efficient-implementation-of-im2col-and-col2im
%
% %// Start indices for each block
% start_ind = reshape(bsxfun(@plus,[1:m-rowWindow+1]',[0:n-colWindow]*m),[],1); %//'
%
% %// Row indices
% lin_row = permute(bsxfun(@plus,start_ind,[0:rowWindow-1])',[1 3 2]);  %//'
%
% %// Get linear indices based on row and col indices and get desired output
% % imTmpM = A(reshape(bsxfun(@plus,lin_row,[0:ncols-1]*m),nrows*ncols,[]));
% indM = reshape(bsxfun(@plus,lin_row,(0:colWindow-1)*m),rowWindow*colWindow,[]);

% Create indices list
%indCooccurM = reshape(1:nL*nL,nL,nL);

% Number of offsets
numOffsets = size(offsetsM,1);

% Indices of last level to filter out
nanIndV = false([lq*lq,1]);
nanIndV([lq:lq:lq*lq-lq, lq*lq-lq:lq*lq]) = true;

% Build levels vector for mu, sig
levRowV = repmat(1:lq,[1 lq]);
levColV = repmat(1:lq,[lq 1]);
levColV = levColV(:)';

% Build list of indices for px and contrast calculation
for n=0:lq-1
    % indices for p(x-y), contrast
    indCtrstV = false(lq*lq,1);
    indCtrst1V = 1:lq-n;
    indCtrst2V = 1+n:lq;
    indCtrstTmpV = indCtrst1V + (indCtrst2V-1)*lq;
    indCtrstTmpV = [indCtrstTmpV indCtrst2V + (indCtrst1V-1)*lq];
    indCtrstV(indCtrstTmpV) = 1;
    indCtrstV(nanIndV) = [];
    indCtrstC{n+1} = indCtrstV;

    % indices for p(x+y)
    indPxPlusYv = false(lq*lq,1);
    indPxPlusYv(levRowV + levColV == n+2) = 1;
    indPxPlusYv(nanIndV) = [];
    indPxPlusYc{n+1} = indPxPlusYv;

    % indices for px
    indPxV = false(lq*lq,1);
    indPxV(lq*n+1:lq*(n+1)) = true;
    indPxV(nanIndV) = [];
    indPxC{n+1} = indPxV;
end

% Filter NaN indices from row/col levels
levRowV(nanIndV) = [];
levColV(nanIndV) = [];

% Build linear indices column/row-wise for Symmetry
indRowV = zeros(1,lq*lq);
for i=1:lq
    indRowV((i-1)*lq+1:(i-1)*lq+lq) = i:lq:lq*lq;
end

% Initialize
energyV = [];
entropyV = [];
sumAvgV = [];
corrcorrV = [];
invDiffMomV = [];
contrastV = [];
clustShadeV = [];
clustProminV = [];

tic
% Initialize cooccurrence matrix (vectorized for speed)
cooccurV = zeros(lq*lq,1,'single');
for off = 1:numOffsets

    offset = offsetsM(off,:);
    slc1M = q(numRowsPad+(1:numRows),numColsPad+(1:numCols),...
        numSlcsPad+(1:numSlices));
    slc2M = circshift(q,offset);
    slc2M = slc2M(numRowsPad+(1:numRows),numColsPad+(1:numCols),numSlcsPad+(1:numSlices))...
        + (slc1M-1)*lq;
    cooccurV = cooccurV + accumarray(slc2M(:),1, [lq*lq,1]); % patch-wise cooccurance

end

cooccurV = cooccurV + cooccurV(indRowV,:); % for symmetry
cooccurV(nanIndV,:) = [];
cooccurV = cooccurV./sum(cooccurV);

% Calculate scalar texture for this offset
% Angular Second Moment (Energy)
if flagv(1)
    energyV = sum(cooccurV.^2);
end
% Entropy
if flagv(2)
    entropyV = -sum(cooccurV.*log2(cooccurV+1e-10));
end
% Contrast, inverse Difference Moment
contrastV = 0;
invDiffMomV = 0;
for n=0:nL-1
    % px
    px(n+1) = sum(cooccurV(indPxC{n+1},:),1);
    % p(x+y)
    pXplusY = cooccurV(indCtrstC{n+1},:);
    % p(x-y)
    pXminusY = cooccurV(indCtrstC{n+1},:);
    % contrast
    if flagv(6)
        contrastV = contrastV + ...
            sum(n^2*cooccurV(indCtrstC{n+1},:));
    end
    % inv diff moment
    if flagv(5)
        invDiffMomV = invDiffMomV + ...
            sum((1/(1+n^2))*cooccurV(indCtrstC{n+1},:));
    end
end
% weighted pixel average (mu), weighted pixel variance (sig)
mu = (1:nL) * px';
sig = ((1:nL)-mu).^2 * px';
%px = sum(cooccurV(indCooccurM),2);
muX = mean(px);
sigX = std(px);
%         for colNum = 1:numCalcVoxs
clstrV = (levRowV + levColV - 2*mu);
% Cluster Shade
if flagv(7)
    clustShadeV = clstrV.*clstrV.*clstrV * cooccurV(:,1);
end
% Cluster Prominence
%             clustProminTmpV(colNum) = (levRowV + levColV - 2*mu(colNum)).^4 ...
%                 * cooccurPatchM(:,colNum);
if flagv(8)
    clustProminV = clstrV.*clstrV.*clstrV.*clstrV ...
        * cooccurV(:,1);
end
% Sum Avg
if flagv(3)
    sumAvgV = (levRowV + levColV) * cooccurV(:,1);
end
% Correlation
if flagv(4)
    corrV = (levRowV .* levColV * cooccurV(:,1) - ...
        muX*muX) / sigX^2; %var(1); % check this
end

%     % Average texture from all directions
%     energyV = energyV / numOffsets;
%     entropyV = entropyV / numOffsets;
%     contrastV = contrastV / numOffsets;
%     invDiffMomV = invDiffMomV / numOffsets;
%     clustShadeV = clustShadeV / numOffsets;
%     clustProminV = clustProminV / numOffsets;

%     if flagv(1)
%         energyV = energyV / numOffsets;
%     end
%     if flagv(2)
%         entropyV = entropyV / numOffsets;
%     end
%     if flagv(3)
%         sumAvgV = sumAvgV / numOffsets;
%     end
%     if flagv(4)
%         corrV = corrV / numOffsets;
%     end
%
%     if flagv(6)
%         contrastV = contrastV / numOffsets;
%     end
%     if flagv(5)
%         invDiffMomV = invDiffMomV / numOffsets;
%     end
%     if flagv(7)
%         clustShadeV = clustShadeV / numOffsets;
%     end
%     if flagv(8)
%         clustProminV = clustProminV / numOffsets;
%     end

%     if waitbarFlag
%         set(hWait, 'Vertices', [[0 0 slcNum/numSlices slcNum/numSlices]' [0 1 1 0]']);
%         drawnow;
%     end

% end
toc


function om = add_legacy_cost(om, name, idx, varargin)
%ADD_LEGACY_COST  Adds a set of user costs to the model.
%
%   OM.ADD_LEGACY_COST(NAME, CP);
%   OM.ADD_LEGACY_COST(NAME, CP, VARSETS);
%   OM.ADD_LEGACY_COST(NAME, IDX_LIST, CP);
%   OM.ADD_LEGACY_COST(NAME, IDX_LIST, CP, VARSETS);
%
%   Adds a named block of user-defined costs to the model. Each set is
%   defined by the CP struct described below. All user-defined sets of
%   costs are combined together into a single set of cost parameters in
%   a single CP struct by BULD_COST_PARAMS. This full aggregate set of
%   cost parameters can be retreived from the model by GET_COST_PARAMS.
%
%   Examples:
%       cp1 = struct('N', N1, 'Cw', Cw1);
%       cp2 = struct('N', N2, 'Cw', Cw2, 'H', H, 'dd', dd, ...
%                     'rh', rh, 'kk', kk, 'mm', mm);
%       om.add_legacy_cost('usr1', cp1, {'Pg', 'Qg', 'z'});
%       om.add_legacy_cost('usr2', cp2, {'Vm', 'Pg', 'Qg', 'z'});
%
%       om.init_indexed_name('c', {2, 3});
%       for i = 1:2
%         for j = 1:3
%           om.add_legacy_cost('c', {i, j}, cp(i,j), ...);
%         end
%       end
%
%   Let x refer to the vector formed by combining the specified VARSETS,
%   and f_u(x, CP) be the cost at x corresponding to the cost parameters
%   contained in CP, where CP is a struct with the following fields:
%       N      - nw x nx sparse matrix (optional, identity matrix by default)
%       Cw     - nw x 1 vector
%       H      - nw x nw sparse matrix (optional, all zeros by default)
%       dd, mm - nw x 1 vectors (optional, all ones by default)
%       rh, kk - nw x 1 vectors (optional, all zeros by default)
%
%   These parameters are used as follows to compute f_u(x, CP)
%
%       R  = N*x - rh
%
%               /  kk(i),  R(i) < -kk(i)
%       K(i) = <   0,     -kk(i) <= R(i) <= kk(i)
%               \ -kk(i),  R(i) > kk(i)
%
%       RR = R + K
%
%       U(i) =  /  0, -kk(i) <= R(i) <= kk(i)
%               \  1, otherwise
%
%       DDL(i) = /  1, dd(i) = 1
%                \  0, otherwise
%
%       DDQ(i) = /  1, dd(i) = 2
%                \  0, otherwise
%
%       Dl = diag(mm) * diag(U) * diag(DDL)
%       Dq = diag(mm) * diag(U) * diag(DDQ)
%
%       w = (Dl + Dq * diag(RR)) * RR
%
%       f_u(x, CP) = 1/2 * w'*H*w + Cw'*w
%
%   See also OPT_MODEL, PARAMS_LEGACY_COST, EVAL_LEGACY_COST.

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

if iscell(idx)
    cp = varargin{1};
    args = varargin(2:end);
else                            %% simple named set
    cp = idx;
    args = varargin;
    idx = {};
end

if isempty(args)
    varsets = {};
else
    varsets = args{1};
end

%% convert varsets from cell to struct array if necessary
varsets = om.varsets_cell2struct(varsets);
nv = om.varsets_len(varsets);   %% number of variables

if isfield(cp, 'N')
    [nw, nx] = size(cp.N);
else
    nw = length(cp.Cw);
    nx = nw;
    cp.N = speye(nw, nx);
end

%% check sizes
if nx ~= nv
    if nw == 0
        cp.N = sparse(nw, nx);
    else
        error('@opt_model/add_legacy_cost: number of columns in N (%d x %d) does not match\nnumber of variables (%d)\n', nw, nx, nv);
    end
end
if size(cp.Cw, 1) ~= nw
    error('@opt_model/add_legacy_cost: number of rows of Cw (%d x %d) and N (%d x %d) must match\n', size(cp.Cw), nw, nx);
end
if isfield(cp, 'H') && (size(cp.H, 1) ~= nw || size(cp.H, 2) ~= nw)
    error('@opt_model/add_legacy_cost: both dimensions of H (%d x %d) must match the number of rows in N (%d x %d)\n', size(cp.H), nw, nx);
end
if isfield(cp, 'dd') && size(cp.dd, 1) ~= nw
    error('@opt_model/add_legacy_cost: number of rows of dd (%d x %d) and N (%d x %d) must match\n', size(cp.dd), nw, nx);
end
if isfield(cp, 'rh') && size(cp.rh, 1) ~= nw
    error('@opt_model/add_legacy_cost: number of rows of rh (%d x %d) and N (%d x %d) must match\n', size(cp.rh), nw, nx);
end
if isfield(cp, 'kk') && size(cp.kk, 1) ~= nw
    error('@opt_model/add_legacy_cost: number of rows of kk (%d x %d) and N (%d x %d) must match\n', size(cp.kk), nw, nx);
end
if isfield(cp, 'mm') && size(cp.mm, 1) ~= nw
    error('@opt_model/add_legacy_cost: number of rows of mm (%d x %d) and N (%d x %d) must match\n', size(cp.mm), nw, nx);
end

%% add the legacy cost set
om.add_named_set('cost', name, idx, nw, cp, varsets);


function model = rmSearchFit_oneGaussianNonlinear(model, data, params, wProcess, t)
% rmSearchFit_oneGaussianNonlinear - wrapper for 'fine' one Gaussian fit
%
% model = rmSearchFit_oneGaussianNonlinear(model, data, params, wProcess, t);
%
% 2008/01 SOD: split of from rmSearchFit.
% 2010/02 SOD: cleanup.
% 2015/02 JW: branched from rmSearchFit_oneGaussian, now includes
%             non-linear model (see Kay et al, 2013, on Compressive
%             Spatial Summation)

% fminsearch options
searchOptions = params.analysis.fmins.options;
expandRange   = params.analysis.fmins.expandRange;

% convert to double just in case
params.analysis.X = double(params.analysis.X);
params.analysis.Y = double(params.analysis.Y);

params.analysis.allstimimages_unconvolved = double(params.analysis.allstimimages_unconvolved);
data = double(data);

% get starting upper and lower range and reset TolFun
% (raw rss computation (similar to norm) and TolFun adjustments)
model.s = model.s_major;
[range, TolFun] = rmSearchFit_range(params,model,data);

% GLU 2021-10-14: not sute if best option, but I am going to restrict here
%                 the exponent
%                 If we are here we know that we are fitting the nonlinear css
if params.analysis.fixcssexp ~= 0
   range.start(4,:) = params.analysis.fixcssexp * ones(size(range.start(4,:)));
   range.lower(4,:) = params.analysis.fixcssexp * ones(size(range.start(4,:)));;
   range.upper(4,:) = params.analysis.fixcssexp * ones(size(range.start(4,:)));;
end

% amount of negative fits
nNegFit  = 0;
vethresh = params.analysis.fmins.vethresh;
trends   = t.trends;
t_id     = t.dcid+1;

%-----------------------------------
% Go for each voxel
%-----------------------------------
progress = 0;tic;
for ii = 1:numel(wProcess),
    % progress monitor (10 dots)
    if floor(ii./numel(wProcess)*10)>progress,
        % print out estimated time left
        if progress==0,
            esttime = toc.*10;
            if floor(esttime./3600)>0,
                fprintf(1,'[%s]:Estimated processing time: %d voxels: %d hours.\n',...
                    mfilename,numel(wProcess),ceil(esttime./3600));
            else
                fprintf(1,'[%s]:Estimated processing time: %d voxels: %d minutes.\n',...
                    mfilename,numel(wProcess),ceil(esttime./60));
            end;
            fprintf(1,'[%s]:Nonlinear optimization (x,y,sigma, exponent):',mfilename);
        end;
        fprintf(1,'.');drawnow;
        progress = progress + 1;
    end;

    % volume index
    vi = wProcess(ii);
    vData = data(:,ii);

    % reset tolFun: Precision of evaluation function.
    % We define RMS improvement relative to the initial raw 'no-fit' data
    % RMS. So, 1 means stop if there is less than 1% improvement on the fit:
    % searchOptions = optimset(searchOptions,'tolFun',optimget(params.analysis.fmins.options,'tolFun')./100.*rawrss);
    % optimset and optimget are a little slow so:
    searchOptions.TolFun = TolFun(ii);

    % actual fitting routine
    if searchOptions.MaxIter>0
        outParams = ...
            fmincon(@(x) rmModelSearchFit_oneGaussianNonlinear(x,vData,...
                                                            params.analysis.X,...
                                                            params.analysis.Y,...
                                                            params.analysis.allstimimages_unconvolved,...
                                                            params.analysis.Hrf,...
                                                            params.analysis.scans,...
                                                            trends),...
                     range.start(:,vi),...  % X0. Vector of 5 [x,y,sigma,exp,?]
                     [],[], ...             % A, B
                     [],[], ...             % Aeq, Beq
                     range.lower(:,vi), ... % LB
                     range.upper(:,vi),...  % UB
                     @(x) distanceCon(x,range.start(:,vi),range.step(:,vi).*expandRange),... % NONLCON
                     searchOptions); % OPTIONS
    else
        outParams = range.start(:,vi);
    end

    %[ outParams range.lower(:,vi) range.start(:,vi) range.upper(:,vi)]

    % make RF, prediction and get rss,b
    Xv = params.analysis.X-outParams(1);
    Yv = params.analysis.Y-outParams(2);
    n  = outParams(4);
    rf = exp( (Yv.*Yv + Xv.*Xv) ./ (-2.*(outParams(3).^2)) );
    pred = (params.analysis.allstimimages_unconvolved * rf).^ n;
    for scan = 1:numel(params.stim)
        inds = params.analysis.scans == scan;
        pred(inds) = filter(params.analysis.Hrf{scan}, 1, pred(inds));
    end

    X  = [pred trends];
    b    = pinv(X)*vData;
    rss  = norm(vData-X*b).^2;

    % store results only if the first beta is positive, somehow fmincon
    % outputs negative fits. If the fit is negative keep old (grid) fit. We
    % do adjust the rss, so it won't be accidentally counted as a 'good'
    % fit.
    if b(1)>0,
        model.x0(vi)         = outParams(1);
        model.y0(vi)         = outParams(2);
        model.s(vi)          = outParams(3);
        model.s_major(vi)    = outParams(3);
        model.s_minor(vi)    = outParams(3);
        model.s_theta(vi)    = 0;
        model.exponent(vi)   = outParams(4);
        model.rss(vi)        = rss;
        model.b([1 t_id],vi) = b;
    else
        % change the percent variance explained to be just under the
        % current vethresh. So it counts as a 'coarse'-fit but can still be
        % included in later 'fine'-fits
        model.rss(vi)  = (1-max((vethresh-0.01),0)).*model.rawrss(vi);
        nNegFit = nNegFit + 1;
    end
end

% end time monitor
et  = toc;
if floor(et/3600)>0,
    fprintf(1,'Done [%d hours].\n',ceil(et/3600));
else
    fprintf(1,'Done [%d minutes].\n',ceil(et/60));
end;
fprintf(1,'[%s]:Removed negative fits: %d (%.1f%%).\n',...
    mfilename,nNegFit,nNegFit./numel(wProcess).*100);
return;

%-----------------------------------
% make sure that the pRF can only be moved "step" away from original
% position "startParams" - for the one Gaussian model
function [C, Ceq]=distanceCon(x,startParams,step)
Ceq = [];
dist = x([1 2])-startParams([1 2]);
C = norm(dist) - step;
return;
%-----------------------------------


%% Example: Proton Treatment Plan with Manipulated CT values
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Copyright 2017 the matRad development team.
%
% This file is part of the matRad project. It is subject to the license
% terms in the LICENSE file found in the top-level directory of this
% distribution and at https://github.com/e0404/matRad/LICENSES.txt. No part
% of the matRad project, including this file, may be copied, modified,
% propagated, or distributed except according to the terms contained in the
% LICENSE file.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%
% In this example we will show
% (i) how to load patient data into matRad
% (ii) how to setup a proton dose calculation
% (iii) how to inversely optimize the pencil beam intensities directly from command window in MATLAB.
% (iv) how to re-optimize a treatment plan
% (v) how to manipulate the CT cube by adding noise to the cube
% (vi) how to recalculate the dose considering the manipulated CT cube and the previously optimized pencil beam intensities
% (vii) how to compare the two results

%% Patient Data Import
% Let's begin with a clear Matlab environment and import the prostate
% patient into your workspace.

matRad_rc; %If this throws an error, run it from the parent directory first to set the paths

load('PROSTATE.mat');

%% Treatment Plan
% The next step is to define your treatment plan labeled as 'pln'. This
% structure requires input from the treatment planner and defines
% the most important cornerstones of your treatment plan.

pln.radiationMode           = 'protons';
pln.machine                 = 'generic_MCsquare';
pln.numOfFractions          = 30;
pln.propOpt.bioOptimization = 'const_RBExD';
pln.propStf.gantryAngles    = [90 270];
pln.propStf.couchAngles     = [0 0];
pln.propStf.bixelWidth      = 3;
pln.propStf.numOfBeams      = numel(pln.propStf.gantryAngles);
pln.propStf.isoCenter       = ones(pln.propStf.numOfBeams,1) * matRad_getIsoCenter(cst,ct,0);
pln.propOpt.runDAO          = 0;
pln.propOpt.runSequencing   = 0;

% dose calculation settings
pln.propDoseCalc.doseGrid.resolution.x = 3; % [mm]
pln.propDoseCalc.doseGrid.resolution.y = 3; % [mm]
pln.propDoseCalc.doseGrid.resolution.z = 3; % [mm]

%% Generate Beam Geometry STF
stf = matRad_generateStf(ct,cst,pln);

%% Dose Calculation
dij = matRad_calcParticleDoseMC(ct,stf,pln,cst);

%% Inverse Optimization for IMPT
resultGUI = matRad_fluenceOptimization(dij,cst,pln);

%% Calculate quality indicators
[dvh,qi]       = matRad_indicatorWrapper(cst,pln,resultGUI);
ixRectum       = 1;
display(qi(ixRectum).D_5);

%%
% Let's change the optimization parameter of the rectum in such a way that it
% will be better spared. We increase the penalty and lower the threshold
% of the squared overdose objective function. Afterwards we re-optimize
% the treatment plan and evaluate dose statistics one more time.

objective = cst{ixRectum,6}{1}; %This gives a struct
objective = matRad_DoseOptimizationFunction.createInstanceFromStruct(objective); %Now we turn it into a class
objective = objective.setDoseParameters(40); %We can simply call this function to change the/all dose parameter(s)
cst{ixRectum,6}{1} = struct(objective); % We put it back as struct

cst{ixRectum,6}{1}.parameters{1} = 40;
cst{ixRectum,6}{1}.penalty = 500;
resultGUI               = matRad_fluenceOptimization(dij,cst,pln);
[dvh2,qi2]              = matRad_indicatorWrapper(cst,pln,resultGUI);
display(qi2(ixRectum).D_5);

%% Plot the Resulting Dose Slice
% Let's plot the transversal iso-center dose slice
slice = round(pln.propStf.isoCenter(1,3)./ct.resolution.z);
figure
imagesc(resultGUI.RBExDose(:,:,slice)),colorbar, colormap(jet)

%%
% Now let's simulate a range undershoot by scaling the relative stopping power cube by 3.5% percent
ct_manip         = ct;
ct_manip.cubeHU{1} = 1.035*ct_manip.cubeHU{1};

%% Recalculate Plan with MC square
% Let's use the existing optimized pencil beam weights and recalculate the RBE weighted dose
resultGUI_noise = matRad_calcDoseDirectMC(ct_manip,stf,pln,cst,resultGUI.w);

%%  Visual Comparison of results
% Let's compare the new recalculation against the optimization result.
plane      = 3;
doseWindow = [0 max([resultGUI.RBExDose(:); resultGUI_noise.RBExDose(:)])];

figure,title('original plan')
matRad_plotSliceWrapper(gca,ct,cst,1,resultGUI.RBExDose,plane,slice,[],0.75,colorcube,[],doseWindow,[]);
figure,title('manipulated plan')
matRad_plotSliceWrapper(gca,ct_manip,cst,1,resultGUI_noise.RBExDose,plane,slice,[],0.75,colorcube,[],doseWindow,[]);

% Let's plot single profiles along the beam direction
ixProfileY = round(pln.propStf.isoCenter(1,1)./ct.resolution.x);

profileOrginal = resultGUI.RBExDose(:,ixProfileY,slice);
profileNoise   = resultGUI_noise.RBExDose(:,ixProfileY,slice);

figure,plot(profileOrginal,'LineWidth',2),grid on,hold on,
       plot(profileNoise,'LineWidth',2),legend({'original profile','noise profile'}),
       xlabel('mm'),ylabel('Gy(RBE)'),title('profile plot')

%% Quantitative Comparison of results
% Compare the two dose cubes using a gamma-index analysis.

doseDifference   = 2;
distToAgreement  = 2;
n                = 1;

[gammaCube,gammaPassRateCell] = matRad_gammaIndex(...
    resultGUI_noise.RBExDose,resultGUI.RBExDose,...
    [ct.resolution.x, ct.resolution.y, ct.resolution.z],...
    [doseDifference distToAgreement],slice,n,'global',cst);


function jed = nyt_to_jed ( volume, issue )

%*****************************************************************************80
%
%% NYT_TO_JED converts an NYT date to a JED.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    28 February 2013
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, integer VOLUME, ISSUE, the New York Times
%    volume and issue.
%
%    Output, real JED, the Julian Ephemeris Date.
%
  jed_epoch_50000 = 2449790.5;

  if ( 149 < volume )

    jed = jed_epoch_50000 + issue - 50000 + 500;
%
%  Take care of the bizarre case of the second half of Volume 149,
%  Jan 1 2000 to Sep 17 2000, issues 51254 through ?, which were also
%  lowered by 500.
%
  elseif ( volume == 149 && issue < 51600 )
    jed = jed_epoch_50000 + issue - 50000 + 500;
  elseif ( 44028 <= issue )
    jed = jed_epoch_50000 + issue - 50000;
%
%  Factor in the strike of 1978.
%
  else
    jed = jed_epoch_50000 + issue - 50000 - 88;
  end

  return
end


function [dataout] = ft_eventtiminganalysis(cfg, data)

% FT_EVENTTIMINGANALYSIS computes a model of single trial event-
% related activity, by estimating per trial the latency (and
% amplitude) of event-related signal components.
%
% Use as
%   [dataout] = ft_eventtiminganalysis(cfg, data)
% where data is single-channel raw data as obtained by FT_PREPROCESSING
% and cfg is a configuration structure according to
%
%  cfg.method  = method for estimating event-related activity
%                 'aseo', analysis of single-trial ERP and ongoing
%                         activity (according to Xu et al, 2009)
%                 'gbve', graph-based variability estimation
%                         (according to Gramfort et al, IEEE TBME 2009)
%  cfg.channel = Nx1 cell-array with selection of channels (default = 'all'),
%                see FT_CHANNELSELECTION for details
%  cfg.trials  = 'all' or a selection given as a 1xN vector (default = 'all')
%  cfg.output  = 'model', or 'residual', which returns the modelled data,
%                or the residuals.
%
% Method specific options are specified in the appropriate substructure.
%
% For the ASEO method, the following options can be specified:
%   cfg.aseo.noiseEstimate   = 'non-parametric' or 'parametric', estimate noise
%                              using parametric or non-parametric (default) method
%   cfg.aseo.tapsmofrq       = value, smoothing parameter of noise for
%                              nonparametric estimation (default = 5)
%   cfg.aseo.jitter          = value, time jitter in initial timewindow
%                              estimate (in seconds). default 0.050 seconds
%   cfg.aseo.numiteration    = value, number of iteration (default = 1)
%   cfg.aseo.initlatency     = Nx2 matrix, initial set of latencies in seconds of event-
%                              related components, give as [comp1start, comp1end;
%                              comp2start, comp2end] (default not
%                              specified). For multiple channels it should
%                              be a cell-array, one matrix per channel
%  Alternatively, rather than specifying a (set of latencies), one can also
%  specify:
%
%   cfg.aseo.initcomp        = vector, initial estimate of the waveform
%                              components. For multiple channels it should
%                              be a cell-array, one matrix per channel.
%
% For the GBVE method, the following options can be specified:
%   cfg.gbve.sigma             = vector, range of sigma values to explore in
%                                cross-validation loop (default: 0.01:0.01:0.2)
%   cfg.gbve.distance          = scalar, distance metric to use as
%                                evaluation criterion, see plugin code for
%                                more informatoin
%   cfg.gbve.alpha             = vector, range of alpha values to explor in
%                                cross-validation loop (default: [0 0.001 0.01 0.1])
%   cfg.gbve.exponent          = scalar, see plugin code for information
%   cfg.gbve.use_maximum       = boolean, (default: 1) consider the positive going peak
%   cfg.gbve.show_pca          = boolean, see plugin code (default 0)
%   cfg.gbve.show_trial_number = boolean, see plugin code (default 0)
%   cfg.gbve.verbose           = boolean (default: 1)
%   cfg.gbve.disp_log          = boolean, see plugin code (default 0)
%   cfg.gbve.latency           = vector [min max], latency range in s
%                                (default: [-inf inf])
%   cfg.gbve.xwin              = scalar smoothing parameter for moving
%                                average smoothing (default: 1), see
%                                eeglab's movav function for more
%                                information.
%
% 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_SINGLETRIALANALYSIS_ASEO

% Copyright (C) 2018-2019, Jan-Mathijs Schoffelen DCCN
%
% 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$

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
  % do not continue function execution in case the outputfile is present and the user indicated to keep it
  return
end

% ensure that the input data is valid for this function
data = ft_checkdata(data, 'datatype', {'raw+comp', 'raw'}, 'feedback', 'yes', 'hassampleinfo', 'yes');

% ensure that the required options are present
cfg         = ft_checkconfig(cfg, 'required', {'method'});
cfg.trials  = ft_getopt(cfg, 'trials',  'all', 1); % all trials as default
cfg.channel = ft_getopt(cfg, 'channel', 'all');
cfg.output  = ft_getopt(cfg, 'output',  'model');
% ensure that the options are valid
cfg = ft_checkopt(cfg, 'method', 'char', {'aseo' 'gbve'});

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% the actual computation is done in the middle part
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% select trials of interest
tmpcfg = keepfields(cfg, {'trials' 'channel' 'showcallinfo'});
data   = ft_selectdata(tmpcfg, data);
% restore the provenance information
[cfg, data] = rollback_provenance(cfg, data);

% some error checks
if isfield(data, 'trial') && numel(data.trial)==0, ft_error('no trials were selected'); end
if numel(data.label)==0, ft_error('no channels were selected'); end

switch cfg.method
  case 'aseo'
    % define general variables that are used locally
    fsample = data.fsample; % Sampling Frequency in Hz
    nchan   = numel(data.label);
    nsample = numel(data.time{1}); %FIXME ASSUMING FIXED TIME AXIS ACROSS ALL TRIALS

    % setting a bunch of options, to be passed on to the lower level function
    if ~isfield(cfg, 'aseo'), cfg.aseo = []; end
    cfg.aseo.thresholdAmpH = ft_getopt(cfg.aseo, 'thresholdAmpH', 0.5);
    cfg.aseo.thresholdAmpL = ft_getopt(cfg.aseo, 'thresholdAmpL', 0.1);
    cfg.aseo.thresholdCorr = ft_getopt(cfg.aseo, 'thresholdCorr', 0.2);
    cfg.aseo.maxOrderAR    = ft_getopt(cfg.aseo, 'maxOrderAR',    5);
    cfg.aseo.noiseEstimate = ft_getopt(cfg.aseo, 'noiseEstimate', 'nonparametric');
    cfg.aseo.numiteration  = ft_getopt(cfg.aseo, 'numiteration',  1);
    cfg.aseo.tapsmofrq     = ft_getopt(cfg.aseo, 'tapsmofrq',     5);
    cfg.aseo.fsample       = fsample;
    cfg.aseo.nsample       = nsample;
    cfg.aseo.pad           = ft_getopt(cfg.aseo, 'pad', (2.*nsample)/fsample);

    % deal with the different ways with which the initial waveforms can be defined
    initlatency      = ft_getopt(cfg.aseo, 'initlatency', {});
    initcomp         = ft_getopt(cfg.aseo, 'initcomp',    {});
    jitter           = ft_getopt(cfg.aseo, 'jitter',      0.050); % half temporal width of shift in s

    if isempty(initlatency) && isempty(initcomp)
      ft_error('for the ASEO method you should supply either an initial estimate of the waveform component, or a set of latencies');
    elseif ~isempty(initlatency)
      % this takes precedence, and should contain per channel the begin and
      % end points of the subwindows in time, based on which the initial
      % subcomponents are estimated

      % ensure it to be a cell-array if the input is a matrix
      if ~iscell(initlatency)
        initlatency = repmat({initlatency},[1 nchan]);
      end
      make_init = true;
    elseif ~isempty(initcomp)
      % ensure it to be a cell-array if the input is a matrix
      if ~iscell(initcomp)
        initcomp = repmat({initcomp}, [1 nchan]);
      end
      make_init = false;
    end

    if make_init
      assert(numel(initlatency)==nchan);
      for k = 1:nchan
        % preprocessing data
        tmp     = cellrowselect(data.trial,k);
        chandat = cat(1,tmp{:});
        chandat = ft_preproc_baselinecorrect(chandat, nearest(data.time{1}, -inf), nearest(data.time{1}, 0));
        avgdat  = nanmean(chandat, 1);

        % set the initial ERP waveforms according to the preset parameters
        ncomp       = size(initlatency{k},1);
        initcomp{k} = zeros(nsample, ncomp);
        for m = 1:ncomp
          begsmp = nearest(data.time{1},initlatency{k}(m, 1));
          endsmp = nearest(data.time{1},initlatency{k}(m, 2));
          if begsmp<1,       begsmp = 1;       end
          if endsmp>nsample, endsmp = nsample; end

          tmp = avgdat(begsmp:endsmp)';
          initcomp{k}(begsmp:endsmp, m) = tmp;
        end
        initcomp{k} = initcomp{k} - repmat(mean(initcomp{k}),nsample,1);
      end
    else
      assert(numel(initcomp)==nchan);
    end

    if ~iscell(jitter)
      jitter = repmat({jitter}, [1 nchan]);
    end

    for k = 1:numel(jitter)
      if ~isempty(jitter{k})
        if size(jitter{k},1)~=size(initcomp{k},2), jitter{k} = repmat(jitter{k}(1,:),[size(initcomp{k},2) 1]); end
      end
    end

    % initialize the output data
    dataout = removefields(data, 'cfg');
    for k = 1:numel(data.trial)
      dataout.trial{k}(:) = nan;
    end

    % initialize the struct that will contain the output parameters
    params = struct([]);

    % do the actual computations
    for k = 1:nchan
      % preprocessing data
      tmp     = cellrowselect(data.trial,k);
      chandat = cat(1,tmp{:});

      % baseline correction
      chandat = ft_preproc_baselinecorrect(chandat, nearest(data.time{1}, -inf), nearest(data.time{1}, 0));

      % do zero-padding and FFT to the signal and initial waveforms
      npad         = cfg.aseo.pad*fsample;    % length of data + zero-padding number
      nfft         = 2.^(ceil(log2(npad)))*2;
      initcomp_fft = fft(initcomp{k}, nfft);  % Fourier transform of the initial waveform
      chandat_fft  = fft(chandat', nfft);     % Fourier transform of the signal

      cfg.aseo.jitter = jitter{k};
      output       = ft_singletrialanalysis_aseo(cfg, chandat_fft, initcomp_fft);

      params(k).latency    = output(end).lat_est./fsample;
      params(k).amplitude  = output(end).amp_est;
      params(k).components = output(end).erp_est;
      params(k).rejectflag = output(end).rejectflag;
      params(k).noise      = output(end).noise;

      for m = 1:numel(data.trial)
        if output(end).rejectflag(m)==0
          switch cfg.output
            case 'model'
              dataout.trial{m}(k,:) = data.trial{m}(k,:)-output(end).residual(:,m)';
            case 'residual'
              dataout.trial{m}(k,:) = output(end).residual(:,m)';
          end
        end
      end
    end

case 'gbve'
  ft_hastoolbox('lagextraction', 1);
  ft_hastoolbox('eeglab',        1); % because the low-level code might use a specific moving average function from EEGLAB
  ft_hastoolbox('cellfunction',  1);

  if ~isfield(cfg, 'gbve'), cfg.gbve = []; end
  cfg.gbve.NORMALIZE_DATA    = ft_getopt(cfg.gbve, 'NORMALIZE_DATA',     true);
  cfg.gbve.CENTER_DATA       = ft_getopt(cfg.gbve, 'CENTER_DATA',        false);
  cfg.gbve.USE_ADAPTIVE_SIGMA= ft_getopt(cfg.gbve, 'USE_ADAPTIVE_SIGMA', false);
  cfg.gbve.sigma             = ft_getopt(cfg.gbve, 'sigma',    0.01:0.01:0.2);
  cfg.gbve.distance          = ft_getopt(cfg.gbve, 'distance', 'corr2');
  cfg.gbve.alpha             = ft_getopt(cfg.gbve, 'alpha',    [0 0.001 0.01 0.1]);
  cfg.gbve.exponent          = ft_getopt(cfg.gbve, 'exponent', 1);
  cfg.gbve.use_maximum       = ft_getopt(cfg.gbve, 'use_maximum', 1); % consider the positive going peak
  cfg.gbve.show_pca          = ft_getopt(cfg.gbve, 'show_pca',          false);
  cfg.gbve.show_trial_number = ft_getopt(cfg.gbve, 'show_trial_number', false);
  cfg.gbve.verbose           = ft_getopt(cfg.gbve, 'verbose',           true);
  cfg.gbve.disp_log          = ft_getopt(cfg.gbve, 'disp_log',          false);
  cfg.gbve.latency           = ft_getopt(cfg.gbve, 'latency',  [-inf inf]);
  cfg.gbve.xwin              = ft_getopt(cfg.gbve, 'xwin',     1); % default is a bit of smoothing

  nchan = numel(data.label);
  ntrl  = numel(data.trial);

  tmin  = nearest(data.time{1}, cfg.gbve.latency(1));
  tmax  = nearest(data.time{1}, cfg.gbve.latency(2));

  % initialize the struct that will contain the output parameters
  dataout = removefields(data, 'cfg');
  params  = struct([]);
  for k = 1:nchan
    % preprocessing data
    options = cfg.gbve;

    fprintf('--- Processing channel %d\n',k);

    tmp     = cellrowselect(data.trial,k);
    chandat = cat(1,tmp{:});
    points  = chandat(:,tmin:tmax);

    % perform a loop across alpha values, cross validation
    alphas = options.alpha;

    if length(alphas) > 1 % Use Cross validation error if multiple alphas are specified
      best_CVerr = -Inf;

      K = 5;
      disp(['--- Running K Cross Validation (K = ',num2str(K),')']);

      block_idx = fix(linspace(1, ntrl, K+1)); % K cross validation
      for jj=1:length(alphas)
        options.alpha = alphas(jj);

        CVerr = 0;
        for kk = 1:K
          bidx = block_idx(kk):block_idx(kk+1);
          idx = 1:ntrl;
          idx(bidx) = [];

          data_k       = chandat(idx,:);
          points_k     = points(idx,:);
          [order,lags] = extractlag(points_k,options);

          data_reordered = data_k(order,:);
          lags           = lags + tmin;
          [data_aligned, dum] = perform_realign(data_reordered, data.time{1}, lags);
          data_aligned(~isfinite(data_aligned)) = nan;
          ep_evoked = nanmean(data_aligned);
          ep_evoked = ep_evoked ./ norm(ep_evoked);

          data_k = chandat(bidx,:);
          data_norm = sqrt(sum(data_k.^2,2));
          data_k = diag(1./data_norm)*data_k;
          data_k(data_norm==0,:) = 0;

          for pp=1:length(bidx)
            c     = xcorr(ep_evoked,data_k(pp,:));
            CVerr = CVerr + max(c(:));
          end
        end

        CVerr = CVerr/ntrl;

        if CVerr > best_CVerr
          best_CVerr = CVerr;
          best_alpha = alphas(jj);
        end
      end
      options.alpha = best_alpha;
    end

    if options.use_maximum
      [order,lags] = extractlag( points, options );
    else
      [order,lags] = extractlag( -points, options );
    end
    disp(['---------- Using alpha = ',num2str(options.alpha)]);
    data_reordered = chandat(order,:);
    lags = lags + tmin;
    [data_aligned] = perform_realign(data_reordered, data.time{1}, lags );
    data_aligned(~isfinite(data_aligned)) = nan;

    [dum,order_inv] = sort(order);
    lags_no_order = lags(order_inv);
    data_aligned  = data_aligned(order_inv,:);

    params(k).latency = data.time{1}(lags_no_order)';
    switch cfg.output
      case 'model'
        tmp = mat2cell(data_aligned, ones(1,size(data_aligned,1)), size(data_aligned,2))';
        dataout.trial = cellrowassign(dataout.trial, tmp, k);
      case 'residual'
        % to be done
        error('not yet implemented');
    end
  end

end

dataout.params = params;
dataout.cfg    = cfg;

% 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 dataout
ft_postamble history    dataout
ft_postamble savevar    dataout


function [traj, infStates] = tapas_hhmm(r, p, varargin)
% Estimates a hierarchical hidden Markov model (HHMM)
%
% This function can be called in two ways:
%
% (1) hhmm(r, p)
%
%     where r is the structure generated by fitModel and p is the parameter vector in native space;
%
% (2) hhmm(r, ptrans, 'trans')
%
%     where r is the structure generated by fitModel, ptrans is the parameter vector in
%     transformed space, and 'trans' is a flag indicating this.
%
% --------------------------------------------------------------------------------------------------
% Copyright (C) 2013 Christoph Mathys, TNU, UZH & ETHZ
%
% This file is part of the HGF toolbox, which is released under the terms of the GNU General Public
% Licence (GPL), version 3. You can redistribute it and/or modify it under the terms of the GPL
% (either version 3 or, at your option, any later version). For further details, see the file
% COPYING or <http://www.gnu.org/licenses/>.

% Transform paramaters back to their native space if needed
if ~isempty(varargin) && strcmp(varargin{1},'trans');
    [p pstruct] = hhmm_transp(r, p);
end

% Fixed configuration elements
%
% Number of possible outcomes
m = r.c_prc.n_outcomes;

% Get model tree (N is for node)
N = pstruct.N;

% Check constraints
if ~isempty(N{1}.V)
    error('tapas:hgf:hhmm:IllegEntryProbRoot', 'Illegal entry probability for root node.');
end

for id = 1:length(N)
    if isempty(N{id}.A) == isempty(N{id}.B)
        error('tapas:hgf:hhmm:IllegCombOfAB', 'Illegal combination of A and B for node no. %d.', id);
    end

    if length(N{id}.children(:)) ~= size(N{id}.A,2)
        error('tapas:hgf:hhmm:NumOfChildIncons', 'Number of children inconsistent with A for node no. %d.', id);
    end

    if ~isempty(N{id}.A) && any(sum(N{id}.A,2)>1)
        error('tapas:hgf:hhmm:IllegA', 'Illegal transition matrix A for node no. %d: row sums have to be less than or equal to 1.', id);
    end

    if ~isempty(N{id}.A)
        for cid = N{id}.children
            cidx = find(N{id}.children==cid);
            if ~isempty(N{cid}.children) && N{id}.A(cidx,cidx) ~= 0
                error('tapas:hgf:hhmm:IllegASelf', 'Illegal transition matrix A for node no. %d: only production nodes may have self-transitions.', id);
            end
        end
    end

    if ~isempty(N{id}.B) && sum(N{id}.B(:))~=1
        error('tapas:hgf:hhmm:IllegB', 'Illegal outcome contingency vector B for node no. %d.', id);
    end

    if ~isempty(N{id}.children)
        Vsum = 0;
        for cid = N{id}.children
            Vsum = Vsum + N{cid}.V;
        end

        if Vsum ~= 1
            error('tapas:hgf:hhmm:IllegV', 'Illegal vertical transition probabilities V from node no. %d.', id);
        end
    end
end

% Flatten the tree into one large transition matrix
% ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

% Find production nodes
pn = [];
for id = 1:length(N)
    if isempty(N{id}.children)
        pn = [pn, id];
    end
end
flatDim = length(pn);

% Initialize outcome contingency matrix
Bflat = NaN(m,flatDim);

% Fill Bflat
for i = 1:flatDim
    Bflat(:,i) = N{pn(i)}.B';
end

% Check Bflat
if any(~isfinite(Bflat(:)))
    error('tapas:hgf:hhmm:NoOutConMat', 'Could not construct outcome contingency matrix.');
end

% Initialize flattened transitioned matrix
Aflat = NaN(flatDim);

% Fill Aflat
for i = 1:flatDim
    for j = 1:flatDim
        if N{pn(i)}.parent == N{pn(j)}.parent
            % If the production nodes are siblings, read out
            % their parent's A matrix
            pid = N{pn(i)}.parent;
            idx = find(N{pid}.children==pn(i));
            jdx = find(N{pid}.children==pn(j));
            Aflat(i,j) = N{pid}.A(idx,jdx);
        else
            % Otherwise, determine their lowest common ancestor
            % and use that to calculate the transition probability
            caid = ca(N,pn(i),pn(j));
            tp = 1;

            nid  = pn(i);
            pid  = N{nid}.parent;
            nidx = find(N{pid}.children==nid);

            % Move up to one node below lowest common ancestor from start node pn(i)
            while pid ~= caid
                aend = 1-sum(N{pid}.A(nidx,:));
                tp = tp*aend;

                nid = pid;
                pid = N{nid}.parent;
                nidx = find(N{pid}.children==nid);
            end

            % Do the horizontal transition to the ancestral line of target node pn(j)
            ancj = anc(N,pn(j));
            while ancj(1) ~= caid
                ancj(1) = [];
            end
            ancj(1) = [];

            caidxi = nidx;
            caidxj = find(N{caid}.children==ancj(1));
            tp = tp*N{caid}.A(caidxi,caidxj);

            % Go down to the target node pn(j)
            ancj(1) = [];
            while ~isempty(ancj)
                tp = tp*N{ancj(1)}.V;
                ancj(1) = [];
            end

            Aflat(i,j) = tp;
        end
    end
end

% Check Aflat
if any(~isfinite(Aflat(:)))
    error('tapas:hgf:hhmm:NoFlatTransMat', 'Could not flatten transition matrix.');
end

% Calculate prior probabilities of production nodes
pnp = NaN(1,flatDim);
for i = 1:flatDim
    anci = anc(N,pn(i));
    anci(1) = [];
    p = 1;
    while ~isempty(anci)
        p = p*N{anci(1)}.V;
        anci(1) = [];
    end
    pnp(i) = p;
end

% Check pnp
if sum(pnp) ~= 1
    error('tapas:hgf:hhmm:NoPriorProdNodes', 'Cannot calculate prior probabilities of production nodes.');
end

% Input and number of trials
u = r.u(:,1);
n = length(u);

% Initialize alpha-prime
alpr = NaN(n,flatDim);

% alpr(1,:)
altmp = pnp.*Bflat(u(1),:);
llh = sum(altmp);
alpr(1,:) = altmp./llh;

% Pass through alpha-prime update loop
for k = 2:1:n
    if not(ismember(k, r.ign))

        %%%%%%%%%%%%%%%%%%%%%%
        % Effect of input u(k)
        %%%%%%%%%%%%%%%%%%%%%%

        altmp = Bflat(u(k),:).*(alpr(k-1,:)*Aflat);
        llh = sum(altmp);
        alpr(k,:) = altmp./llh;
    else
        alpr(k,:) = alpr(k-1,:);
    end
end

% Predicted states
alprhat = [pnp; alpr];
alprhat(end,:) = [];

% Create result data structure
traj = struct;

traj.alpr    = alpr;
traj.alprhat = alprhat;

% Create matrix needed by observation model
infStates = traj.alpr;

end % function hhmm

% ----------------------------------------------------------------------------------------
% Find lowest common ancestor of nodes
function ca = ca(N,ida,idb)
    % Find ancestors of ida and idb
    anca = anc(N,ida);
    ancb = anc(N,idb);

    % Determine lowest common ancestor
    ca = NaN;
    aa = anca(1);
    ab = ancb(1);
    while aa == ab && ~isempty(anca) && ~isempty(ancb)
        ca = aa;
        anca(1) = [];
        ancb(1) = [];
        aa = anca(1);
        ab = ancb(1);
    end

    if aa == ab
        ca = aa;
    end
end

% ----------------------------------------------------------------------------------------
% Find ancestors of a node
function anc = anc(N,id)
    anc = id;
    idt = N{id}.parent;
    while ~isempty(idt)
        anc = [idt, anc];
        idt = N{idt}.parent;
    end
end


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

function [Curve,Ind] = boundary_curve(P,Curve0,rball,dmax)

% ---------------------------------------------------------------------
% BOUNDARY_CURVE.M      Determines the boundary curve based on the
%                           previously defined boundary curve.
%
% Version 1.1.0
% Latest update     3 May 2022
%
% Copyright (C) 2015-2022 Pasi Raumonen
% ---------------------------------------------------------------------
%
% Inputs:
% P         Point cloud of the cross section
% Curve0     Seed points from previous cross section curve
% rball     Radius of the balls centered at seed points
% dmax      Maximum distance between concecutive curve points, if larger,
%               then create a new one between the points
% ---------------------------------------------------------------------

% Changes from version 1.0.0 to 1.1.0, 3 May 2022:
% 1) Increased the cubical neighborhood in the generation of the segments

%% Partition the point cloud into cubes
Min = double(min([P(:,1:2); Curve0(:,1:2)]));
Max = double(max([P(:,1:2); Curve0(:,1:2)]));
N = double(ceil((Max-Min)/rball)+5);
% cube coordinates of the section points
CC = floor([P(:,1)-Min(1) P(:,2)-Min(2)]/rball)+3;
% Sorts the points according a lexicographical order
S = [CC(:,1) CC(:,2)-1]*[1 N(1)]';
[S,I] = sort(S);
% Define "partition"
np = size(P,1);
partition = cell(N(1),N(2));
p = 1;              % The index of the point under comparison
while p <= np
  t = 1;
  while (p+t <= np) && (S(p) == S(p+t))
    t = t+1;
  end
  q = I(p);
  partition{CC(q,1),CC(q,2)} = I(p:p+t-1);
  p = p+t;
end

%% Define segments using the previous points
% cube coordinates of the seed points:
CC = floor([Curve0(:,1)-Min(1) Curve0(:,2)-Min(2)]/rball)+3;
I = CC < 3;
CC(I) = 3;
nc = size(Curve0,1);  % number of sets
Dist = 1e8*ones(np,1);  % distance of point to the closest center
SoP = zeros(np,1);  % the segment the points belong to
Radius = rball^2;
for i = 1:nc
  points = partition(CC(i,1)-2:CC(i,1)+2,CC(i,2)-2:CC(i,2)+2);
  points = vertcat(points{:});
  V = [P(points,1)-Curve0(i,1) P(points,2)-Curve0(i,2)];
  dist = sum(V.*V,2);
  PointsInBall = dist < Radius;
  points = points(PointsInBall);
  dist = dist(PointsInBall);
  D = Dist(points);
  L = dist < D;
  I = points(L);
  Dist(I) = dist(L);
  SoP(I) = i;
end

%% Finalise the segments
% Number of points in each segment and index of each point in its segment
Num = zeros(nc,1);
IndPoints = zeros(np,1);
for i = 1:np
  if SoP(i) > 0
    Num(SoP(i)) = Num(SoP(i))+1;
    IndPoints(i) = Num(SoP(i));
  end
end
% Continue if enough non-emtpy segments
if nnz(Num) > 0.05*nc
  % Initialization of the "Seg"
  Seg = cell(nc,1);
  for i = 1:nc
    Seg{i} = zeros(Num(i),1);
  end
  % Define the "Seg"
  for i = 1:np
    if SoP(i) > 0
      Seg{SoP(i),1}(IndPoints(i),1) = i;
    end
  end

  %% Define the new curve points as the average of the segments
  Curve = zeros(nc,3);  % the new boundary curve
  Empty = false(nc,1);
  for i = 1:nc
    S = Seg{i};
    if ~isempty(S)
      Curve(i,:) = mean(P(S,:),1);
      if norm(Curve(i,:)-Curve0(i,:)) > 1.25*dmax
        Curve(i,:) = Curve0(i,:);
      end
    else
      Empty(i) = true;
    end
  end

  %% Interpolate for empty segments
  % For empty segments create points by interpolation from neighboring
  % non-empty segments
  if any(Empty)
    for i = 1:nc
      if Empty(i)
        if i > 1 && i < nc
          k = 0;
          while i+k <= nc && Empty(i+k)
            k = k+1;
          end
          if i+k <= nc
            LineEle = Curve(i+k,:)-Curve(i-1,:);
          else
            LineEle = Curve(1,:)-Curve(i-1,:);
          end
          if k < 5
            for j = 1:k
              Curve(i+j-1,:) = Curve(i-1,:)+j/(k+1)*LineEle;
            end
          else
            Curve(i:i+k-1,:) = Curve0(i:i+k-1,:);
          end
        elseif i == 1
          a = 0;
          while Empty(end-a)
            a = a+1;
          end
          b = 1;
          while Empty(b)
            b = b+1;
          end
          LineEle = Curve(b,:)-Curve(nc-a,:);
          n = a+b-1;
          if n < 5
            for j = 1:a-1
              Curve(nc-a+1+j,:) = Curve(nc-a,:)+j/n*LineEle;
            end
            for j = 1:b-1
              Curve(j,:) = Curve(nc-a,:)+(j+a-1)/n*LineEle;
            end
          else
            Curve(nc-a+2:nc,1:2) = Curve0(nc-a+2:nc,1:2);
            Curve(nc-a+2:nc,3) = Curve0(nc-a+2:nc,3);
            Curve(1:b-1,1:2) = Curve0(1:b-1,1:2);
            Curve(1:b-1,3) = Curve0(1:b-1,3);
          end
        elseif i == nc
          LineEle = Curve(1,:)-Curve(nc-1,:);
          Curve(i,:) = Curve(nc-1,:)+0.5*LineEle;
        end
      end
    end
  end

  % Correct the height
  Curve(:,3) = min(Curve(:,3));

  % Check self-intersection
  [Intersect,IntersectLines] = check_self_intersection(Curve(:,1:2));

  % If self-intersection, try to modify the curve
  j = 1;
  while Intersect && j <= 5
    n = size(Curve,1);
    InterLines = (1:1:n)';
    NumberOfIntersections = cellfun('length',IntersectLines(:,1));
    I = NumberOfIntersections > 0;
    InterLines = InterLines(I);
    CrossLen = vertcat(IntersectLines{I,2});
    if length(CrossLen) == length(InterLines)
      LineEle = Curve([2:end 1],:)-Curve(1:end,:);
      d = sqrt(sum(LineEle.*LineEle,2));
      m = length(InterLines);
      for i = 1:2:m
        if InterLines(i) ~= n
          Curve(InterLines(i)+1,:) = Curve(InterLines(i),:)+...
            0.9*CrossLen(i)/d(InterLines(i))*LineEle(InterLines(i),:);
        else
          Curve(1,:) = Curve(InterLines(i),:)+...
            0.9*CrossLen(i)/d(InterLines(i))*LineEle(InterLines(i),:);
        end
      end
      [Intersect,IntersectLines] = check_self_intersection(Curve(:,1:2));
      j = j+1;
    else
      j = 6;
    end
  end

  %% Add new points if too large distances
  LineEle = Curve([2:end 1],:)-Curve(1:end,:);
  d = sum(LineEle.*LineEle,2);
  Large = d > dmax^2;
  m = nnz(Large);
  if m > 0
    Curve0 = zeros(nc+m,3);
    Ind = zeros(nc+m,2);
    t = 0;
    for i = 1:nc
      if Large(i)
        t = t+1;
        Curve0(t,:) = Curve(i,:);
        if i < nc
          Ind(t,:) = [i i+1];
        else
          Ind(t,:) = [i 1];
        end
        t = t+1;
        Curve0(t,:) = Curve(i,:)+0.5*LineEle(i,:);
        if i < nc
          Ind(t,:) = [i+1 0];
        else
          Ind(t,:) = [1 0];
        end
      else
        t = t+1;
        Curve0(t,:) = Curve(i,:);
        if i < nc
          Ind(t,:) = [i i+1];
        else
          Ind(t,:) = [i 1];
        end
      end
    end
    Curve = Curve0;

  else
    Ind = [(1:1:nc)' [(2:1:nc)'; 1]];
  end

  %% Remove new points if too small distances
  nc = size(Curve,1);
  LineEle = Curve([2:end 1],:)-Curve(1:end,:);
  d = sum(LineEle.*LineEle,2);
  Small = d < (0.333*dmax)^2;
  m = nnz(Small);
  if m > 0
    for i = 1:nc-1
      if ~Small(i) && Small(i+1)
        Ind(i,2) = -1;
      elseif Small(i) && Small(i+1)
        Small(i+1) = false;
      end
    end
    if ~Small(nc) && Small(1)
      Ind(nc,2) = -1;
      Ind(1,2) = -1;
      Small(1) = false;
      Small(nc) = true;
      I = Ind(:,2) > 0;
      Ind(2:end,1) = Ind(2:end,1)+1;
      Ind(I,2) = Ind(I,2)+1;

    end
    Ind = Ind(~Small,:);
    Curve = Curve(~Small,:);
  end

else
  % If not enough new points, return the old curve
  Ind = [(1:1:nc)' [(2:1:nc)'; 1]];
  Curve = Curve0;
end


function q = conjexp(p)

% conjexp
%
%   1/p+1/q=1

q = p/(p-1);
if p==1
    q = Inf;
end
if p==Inf
    q = p;
end

end


function y = abs( x )

%Disciplined convex/geometric programming information for ABS:
%   ABS(X) is convex and nonmonotonic in X. Therefore, when used in
%   DCPs, X must be affine. ABS(X) is not useful in DGPs, since all
%   log-convex and log-concave expressions are already positive.

%
% Determine the expression types
%

% 0 : convex, concave, invalid
% 1 : constant
% 2 : real affine
% 3 : complex affine
persistent remap
if isempty( remap ),
    remap_1 = cvx_remap( 'constant' );
    remap_2 = cvx_remap( 'real-affine' );
    remap_3 = cvx_remap( 'complex-affine' );
    remap_4 = cvx_remap( 'log-valid' );
    remap = remap_1 + ( 2 * remap_2 + 3 * remap_3 + 4 * remap_4 ) .* ~remap_1;
end
v = remap( cvx_classify( x ) );

%
% Process each type of expression one piece at a time
%

vu = sort( v(:) );
vu = vu([true;diff(vu)~=0]);
nv = length( vu );
sx = x.size_;
if nv ~= 1,
    y = cvx( sx, [] );
end
for k = 1 : nv,

    %
    % Select the category of expression to compute
    %

    vk = vu( k );
    if nv == 1,
        xt = x;
    else
        t = v == vk;
        xt = cvx_subsref( x, t );
    end

    %
    % Perform the computations
    %

    switch vk,
        case 0,
            % Invalid
            error( 'Disciplined convex programming error:\n    Illegal operation: abs( {%s} ).', cvx_class( xt ) );
        case 1,
            % Constant
            cvx_optval = cvx( builtin( 'abs', cvx_constant( xt ) ) );
        case 2,
            % Real affine
            w = [];
            st = size( xt );
            cvx_begin
                epigraph variable w( st )
                { xt, w } == lorentz( st, 0 ); %#ok
            cvx_end
        case 3,
            % Complex affine
            w = [];
            st = size( xt );
            cvx_begin
                epigraph variable w( st )
                { xt, w } == complex_lorentz( st, 0 ); %#ok
            cvx_end
        case 4,
            % log-affine, log-convex
            cvx_optval = xt;
        otherwise,
            error( 'Shouldn''t be here.' );
    end

    %
    % Store the results
    %

    if nv == 1,
        y = cvx_optval;
    else
        y = cvx_subsasgn( y, t, cvx_optval );
    end

end

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


function [isih] = ft_spike_isi(cfg, spike)

% FT_SPIKE_ISI computes the interspike interval histogram
%
% The input SPIKE should be organised as
% a) the spike datatype, obtained from FT_SPIKE_MAKETRIALS
% b) the raw datatype, containing binary spike trains, obtained from
% FT_APPENDSPIKE or FT_CHECKDATA. In this case the raw datatype is
% converted to the spike datatype.
%
% Use as
%   [isih] = ft_spike_isi(cfg, spike)
%
% Configurations:
%   cfg.outputunit       = 'spikecount' (default) or 'proportion' (sum of all bins = 1).
%   cfg.spikechannel     = string or index of spike channels to
%                          trigger on (default = 'all')
%                          See FT_CHANNELSELECTION for details.
%   cfg.trials           = numeric selection of trials (default = 'all')
%   cfg.bins             = ascending vector of isi bin edges.
%   cfg.latency          = [begin end] in seconds, 'max' (default), 'min', 'prestim'(t<=0), or
%                          'poststim' (t>=0).
%                          If 'max', we use all available latencies.
%                          If 'min', we use only the time window contained by all trials.
%                          If 'prestim' or 'poststim', we use time to or
%                          from 0, respectively.
%`  cfg.keeptrials       = 'yes' or 'no'. If 'yes', we keep the individual
%                           isis between spikes and output as isih.isi
%   cfg.param            = string, one of
%      'gamfit'      : returns [shape scale] for gamma distribution fit
%      'coeffvar'    : coefficient of variation (sd / mean)
%      'lv'          : Shinomoto's Local Variation measure (2009)
%
% Outputs:
%   isih.avg             = nUnits-by-nBins interspike interval histogram
%   isih.time            = 1 x nBins bincenters corresponding to isih.avg
%   isih.isi             = 1-by-nUnits cell with interval to previous spike per spike.
%                          For example isih.isi{1}(2) = 0.1 means that the
%                          second spike fired was 0.1 s later than the
%                          first. Note that jumps within trials or first
%                          spikes within trials are given NaNs.
%   isih.label           = 1-by-nUnits cell-array with labels

% Copyright (C) 2010-2012, Martin Vinck
%
% 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 provenance spike

% check if data is of proper format
spike = ft_checkdata(spike,'datatype', 'spike', 'feedback', 'yes');

% get the default options
cfg.outputunit   = ft_getopt(cfg,'outputunit','spikecount');
cfg.spikechannel = ft_getopt(cfg,'spikechannel', 'all');
cfg.trials       = ft_getopt(cfg,'trials', 'all');
cfg.latency      = ft_getopt(cfg,'latency','maxperiod');
cfg.keeptrials   = ft_getopt(cfg,'keeptrials', 'yes');
cfg.bins         = ft_getopt(cfg,'bins', 0:0.002:1);
cfg.param        = ft_getopt(cfg,'param', 'coeffvar');

% ensure that the options are valid
cfg = ft_checkopt(cfg,'outputunit','char', {'spikecount', 'proportion'});
cfg = ft_checkopt(cfg,'bins', 'ascendingdoublevector');
cfg = ft_checkopt(cfg,'spikechannel',{'cell', 'char', 'double'});
cfg = ft_checkopt(cfg,'latency', {'char', 'ascendingdoublebivector'});
cfg = ft_checkopt(cfg,'trials', {'char', 'doublevector', 'logical'});
cfg = ft_checkopt(cfg,'keeptrials', 'char', {'yes', 'no'});
cfg = ft_checkopt(cfg,'param', 'char', {'gamfit', 'coeffvar', 'lv'});

cfg = ft_checkconfig(cfg, 'allowed', {'param', 'outputunit', 'bins', 'spikechannel', 'latency', 'trials', 'keeptrials'});

% get the number of trials or change DATA according to cfg.trials
if  strcmp(cfg.trials,'all')
  cfg.trials = 1:size(spike.trialtime,1);
elseif islogical(cfg.trials) || all(cfg.trials==0 | cfg.trials==1)
  cfg.trials = find(cfg.trials);
end
cfg.trials = sort(cfg.trials);

cfg.channel = ft_channelselection(cfg.spikechannel, spike.label);
spikesel    = match_str(spike.label, cfg.channel);
nUnits      = length(spikesel); % number of spike channels
if nUnits==0, error('No spikechannel selected by means of cfg.spikechannel'); end

% determine the duration of each trial
begTrialLatency = spike.trialtime(cfg.trials,1);
endTrialLatency = spike.trialtime(cfg.trials,2);

% select the latencies
if strcmp(cfg.latency,'minperiod')
  cfg.latency = [max(begTriallatency) min(endTriallatency)];
elseif strcmp(cfg.latency,'maxperiod')
  cfg.latency = [min(begTrialLatency) max(endTrialLatency)];
elseif strcmp(cfg.latency,'prestim')
  cfg.latency = [min(begTrialLatency) 0];
elseif strcmp(cfg.latency,'poststim')
  cfg.latency = [0 max(endTrialLatency)];
end

% construct the isi bins
bins  = cfg.bins;
nBins = length(bins)-1;

% compute the interspike interval
keepTrials = strcmp(cfg.keeptrials,'yes');
if keepTrials, isiSpike = cell(1,nUnits); end  % contains the individual histogram spike times
isihist  = zeros(nUnits,nBins+1); % isi histogram
out = [];
for iUnit = 1:nUnits
  unitIndx = spikesel(iUnit);

  % only select the spikes in the right latencies and trials
  spikeTrials    = spike.trial{unitIndx}(:)'; % ensure row vector
  spikeTimes     = spike.time{unitIndx}(:)'; % ensure row vector

  % select only the spikes in the window and with the selected trials
  spikesInTrials = ismember(spikeTrials, cfg.trials); % row vec
  spikesInWin    = spikeTimes>=cfg.latency(1)&spikeTimes<=cfg.latency(2); % row vec
  spikeTimes     = spikeTimes(spikesInTrials & spikesInWin);
  spikeTrials    = spikeTrials(spikesInTrials & spikesInWin);

  % find the spikes that jumped to the next trial, replace them with NaNs
  trialJump = logical([1 diff(spikeTrials(:)')]);
  isi = [NaN diff(spikeTimes(:)')];
  isi(trialJump) = NaN;

  switch cfg.param
  case 'coeffvar'
    out(iUnit) = nanstd(isi)./nanmean(isi);
  case 'gamfit'
    data = isi(~isnan(isi));  % remove the nans from isiSpike
    if ~isempty(data)
      [out(iUnit,:)] = mle(data,'distribution', 'gamma'); % fit a gamma distribution
    else
      out(iUnit,:)   = [NaN NaN];
    end
  case 'lv'
    dIsi     = isi(1:end-1) - isi(2:end);
    sumIsi   = isi(1:end-1) + isi(2:end);
    sl       = ~isnan(dIsi) & ~isnan(sumIsi); % remove if one has nan
    df       = sum(sl) - 1;
    out(iUnit) = (3/df)*sum((dIsi(sl)./sumIsi(sl)).^2);
  end

  % convert and store the isi
  if keepTrials, isiSpike{iUnit} = isi; end
  isihist(iUnit,:)   = histc(isi,bins);
end

isihist(:,end) = []; % the last number is only an equality to a bin edge
if strcmp(cfg.outputunit,'proportion')
  isihist = isihist./repmat(nansum(isihist,2),1,size(isihist,2));
end

% gather the rest of the results
if strcmp(cfg.keeptrials,'yes'), isih.isi  = isiSpike; end
isih.time        = bins(1:end-1);
isih.avg         = isihist;
isih.dimord      = 'chan_time';
isih.label       = spike.label(spikesel);
param = cfg.param;
isih.(param) = out;

% do the general cleanup and bookkeeping at the end of the function

ft_postamble previous   spike
ft_postamble provenance isih
ft_postamble history    isih