function [ obj ] = mh_invert( obj )
% Metropolized Gibbs sampling on collapsed HUGE model.
%
% This is a protected method of the tapas_Huge class. It cannot be called
% from outside the class.
%
%

obj = obj.mh_init( );
% number of iterations and burn-in
nIt = obj.options.nvp.numberofiterations;
if isempty(nIt)
    nIt = 2e5;
end
nBi = obj.options.nvp.burnin;
if isempty(nBi) || nBi >= nIt
    nBi = fix(nIt/2);
end

% inverse chain temperature
invTemp = obj.options.nvp.inversetemperature;

% reserve memory
q_nk = zeros(obj.N, obj.K);
obj.trace = struct();
obj.trace.smp = repmat(obj.aux.sample, nIt, 1);
obj.trace.lpr = repmat(obj.aux.lpr, nIt, 1);
psrf = repmat(obj.aux.sample, fix(nIt/obj.const.nPsrf) + 1, 1);

%% ===== MAIN LOOP =====
for iIt = 1:nIt

    % subject level
    for iMhDcm = 1:obj.options.mh.nSteps.dcm
        % sample DCM (parameters)
        obj = mh_sample_dcm( obj, invTemp );
        % sample lambda (hyperparameters)
        obj = mh_sample_noise( obj, invTemp );
    end

    % group level
    if mod(iIt, obj.options.mh.nSteps.knKm) == 0
        k = mod(iIt/obj.options.mh.nSteps.knKm - 1,obj.K) + 1;
        obj = mh_sample_kmhop( obj, k );
    else
        % sample weights pi
        if obj.K > 1
            for iMh = 1:obj.options.mh.nSteps.weights
                obj = mh_sample_weights( obj );
            end
        end
        % sample cluster mu and Sigma
        for iMh = 1:obj.options.mh.nSteps.clusters
            obj = mh_sample_cluster( obj );
        end
    end

    % save sample
    obj.trace.smp(iIt) = obj.aux.sample;
    obj.trace.lpr(iIt) = obj.aux.lpr;

    % adapt proposal step size
    if (iIt <= nBi/3) && (mod(iIt, obj.const.mhAdapt(1)) == 0)
        obj = mh_adapt(obj, iIt);
    end

    % accumulate cluster assigment estimate
    if iIt > nBi
        tmp = bsxfun(@times, exp(obj.aux.rho), obj.aux.sample.pi);
        q_nk = q_nk + bsxfun(@rdivide, tmp, sum(tmp, 2));
    end
    % convergence monitoring
    if mod(iIt, obj.const.nPsrf) == 0
        iPsrf = iIt/obj.const.nPsrf;
        psrf(iPsrf) = mh_psrf(obj.trace.smp(1:iIt), 4);
    end
    if obj.options.nvp.verbose && mod(iIt, 100) == 1
        fprintf('Iteration %u\n', iIt);
    end

% -------------------------------------
end%         END MAIN LOOP
% -------------------------------------
%%          Post Processing
% estimates for assignment probability
q_nk = q_nk/(nIt - nBi);

% acceptance ratio
ratio = struct();
obj.aux.nProp.sp(1) = obj.aux.nProp.sp(1)*obj.N;
for parameter = {'pi', 'mu', 'kappa', 'theta', 'lambda', 'sp'}
    ratio.(parameter{1}) = obj.aux.nAccept.(parameter{1})./...
        obj.aux.nProp.(parameter{1});
end
% posterior mean and quantiles
postMean    = struct();
postVar     = struct();
postQuant   = struct();
for parameter = {'pi', 'mu', 'kappa', 'theta_c', 'theta_h', 'lambda'}
    tmp = reshape([obj.trace.smp(nBi+1:end).(parameter{1})], ...
        [size(obj.aux.sample.(parameter{1})), nIt - nBi]);
    postMean.(parameter{1})  = mean(tmp, 3);
    postVar.(parameter{1})   = var(tmp, 0, 3);
    postQuant.(parameter{1}) = quantile(tmp, obj.options.quantiles, 3);
end
% cumulative probability levels for quantiles
postQuant.levels = obj.options.quantiles;

% calculate PSRF post burn-in
psrf(end) = mh_psrf(obj.trace.smp(nBi + 1:end), 4);

% collect posterior summaries
obj.posterior = struct('nIt', nIt, 'nBi', nBi, 'q_nk', q_nk, ...
    'ratio', ratio, 'psrf', psrf, 'mean', postMean, 'variance', postVar, ...
    'quantile', postQuant, 'lvarBold', obj.aux.lvarBold, ...
    'nrv', exp(-postMean.lambda));

% thin MC chain
if ~isempty(obj.options.nTrace)
    % keep only nTrace samples from post-burn-in phase
    nTrace  = min(nIt - nBi, obj.options.nTrace);
    nThin   = fix((nIt - nBi)/nTrace);
    % select samples uniformly
    obj.trace.smp = obj.trace.smp(end-nThin*(nTrace - 1):nThin:end);
    obj.trace.lpr = obj.trace.lpr(end-nThin*(nTrace - 1):nThin:end);
end

end

%---------------------------------
%            SAMPLING
%---------------------------------
%% SAMPLING: weights (pi)
function [ obj ] = mh_sample_weights( obj )

% propose (in unconstrained space)
prop_piu = obj.aux.sample.pi_u + ...
    randn(1,obj.K-1)*obj.aux.step.pi;
% transform to unit simplex
prop_pis = 1./(1 + exp(log(obj.K-1:-1:1) - prop_piu));
prop_pic = cumprod(1-prop_pis);
prop_pi = [prop_pis(1),-diff(prop_pic),prop_pic(end)];

% evaluate joint (in unconstrained space)
% log-prior on pi
prop_lpr = log(prop_pi)*(obj.prior.alpha_0 - 1) + ...
   sum(log(prop_pis)) + sum(log(1-prop_pis)) + ...
   sum(log(prop_pic(1:end-1)));
prop_lpr = max(prop_lpr, -realmax);
% log-conditional of theta_c given pi
prop_lcd = log(exp(obj.aux.rho)*prop_pi') + obj.aux.rho_max;

% accept/reject
obj.aux.nProp.pi = obj.aux.nProp.pi + 1;
a = exp(prop_lpr - obj.aux.lpr.pi...
        + sum(prop_lcd - obj.aux.lpr.theta_c));
%         a = exp(prop_lpr - obj.aux.lpr.pi);
if ~isnan(a) && ~isinf(a) && rand()<a
    obj.aux.sample.pi_u = prop_piu;
    obj.aux.sample.pi = prop_pi;
    obj.aux.lpr.pi = prop_lpr;
    obj.aux.lpr.theta_c = prop_lcd;
    obj.aux.nAccept.pi = obj.aux.nAccept.pi + 1;
end

end

%% SAMPLING: cluster parameters (mu, kappa = - log(sigma^2))
function [ obj ] = mh_sample_cluster( obj )

obj.aux.nProp.mu = obj.aux.nProp.mu + 1;
obj.aux.nProp.kappa = obj.aux.nProp.mu;

for k = 1:obj.K

    prop_kappa = obj.aux.sample.kappa(k,:);
    % mean
    tmp = randn(1,obj.idx.P_c)*obj.aux.step.mu(k);
    prop_mu = obj.aux.sample.mu(k,:) + tmp*...
        obj.aux.transform.mu(:,:,k);
    dlq = 0; % delta log-proposal density

    % evaluate log-conditional
    % prior
    prop_dmu_k = prop_mu - obj.prior.m_0;
    prop_lpr_mu = -prop_dmu_k*obj.prior.T_0*prop_dmu_k'/2;

    % log-conditional theta_c given mu and kappa
    prop_dtheta_c = bsxfun(@minus, obj.aux.sample.theta_c, prop_mu);
    tmp = obj.aux.l2pi - .5*prop_dtheta_c.^2*exp(prop_kappa') ...
        + .5*sum(prop_kappa);
    if obj.K > 1
        prop_rho = bsxfun(@plus, obj.aux.rho, obj.aux.rho_max);
        prop_rho(:,k) = tmp;
        prop_rho_max = max(prop_rho, [], 2);
        prop_rho = bsxfun(@minus, prop_rho, prop_rho_max);
        prop_lcd = log(exp(prop_rho)*obj.aux.sample.pi') ...
            + prop_rho_max; % log-sum-exp
    else
        prop_rho_max = tmp;
        prop_lcd = prop_rho_max;
        prop_rho = zeros(obj.N, 1);
    end

    % accept/reject
    a = exp(sum(prop_lcd - obj.aux.lpr.theta_c) ...
          + prop_lpr_mu - obj.aux.lpr.mu(k) + dlq);
%             a = exp(prop_lpr_mu - obj.aux.lpr.mu(k));
    if ~isnan(a) && ~isinf(a) && rand()<a
        obj.aux.nAccept.mu(k) = obj.aux.nAccept.mu(k) + 1;
        obj.aux.sample.mu(k,:) = prop_mu;
        obj.aux.rho = prop_rho;
        obj.aux.rho_max = prop_rho_max;
        obj.aux.lpr.theta_c = prop_lcd;
        obj.aux.lpr.mu(k) = prop_lpr_mu;
    end

    % precision
    prop_kappa = prop_kappa + randn(1,obj.idx.P_c)*obj.aux.step.kappa(k);

    % evaluate log-conditional
    % prior
    prop_dkappa = prop_kappa - obj.prior.s_0;
    prop_lpr_kappa = -.5*prop_dkappa.^2*obj.prior.nu_0;
    % log-conditional theta_c given mu and kappa
    prop_dtheta_c = bsxfun(@minus, obj.aux.sample.theta_c, ...
        obj.aux.sample.mu(k,:));
    tmp = obj.aux.l2pi - .5*prop_dtheta_c.^2*exp(prop_kappa') ...
        + .5*sum(prop_kappa);
    if obj.K > 1
        prop_rho = bsxfun(@plus, obj.aux.rho, obj.aux.rho_max);
        prop_rho(:,k) = tmp;
        prop_rho_max = max(prop_rho, [], 2);
        prop_rho = bsxfun(@minus, prop_rho, prop_rho_max);
        prop_lcd = log(exp(prop_rho)*obj.aux.sample.pi') ...
            + prop_rho_max;
    else
        prop_rho_max = tmp;
        prop_lcd = prop_rho_max;
        prop_rho = zeros(obj.N, 1);
    end

    % accept/reject
    a = exp(sum(prop_lcd - obj.aux.lpr.theta_c) ...
          + prop_lpr_kappa - obj.aux.lpr.kappa(k));
%             a = exp(prop_lpr_kappa - obj.aux.lpr.kappa(k));
    if ~isnan(a) && ~isinf(a) && rand()<a
        obj.aux.nAccept.kappa(k) = obj.aux.nAccept.kappa(k) + 1;
        obj.aux.sample.kappa(k,:) = prop_kappa;
        obj.aux.rho = prop_rho;
        obj.aux.rho_max = prop_rho_max;
        obj.aux.lpr.theta_c = prop_lcd;
        obj.aux.lpr.kappa(k) = prop_lpr_kappa;
    end

end

end

%% SAMPLING: DCM parameter (theta)
function [ obj ] = mh_sample_dcm( obj, invTemp )
nProp = obj.aux.nProp.theta + obj.aux.nProp.sp(1) + 1;
bGmm = mod(nProp, obj.options.mh.nSteps.knGmm) == 0;
if bGmm
    obj.aux.nProp.sp(1) = obj.aux.nProp.sp(1) + 1;
else
    obj.aux.nProp.theta = obj.aux.nProp.theta + 1;
end
prec = -.5*exp(obj.aux.sample.kappa);

for n = 1:obj.N

    % propose
    if bGmm % sample from GMM
        k = randsample(obj.K, 1, true, obj.aux.sample.pi);
        prop_theta_c = obj.aux.sample.mu(k,:) + ...
            randn(1, obj.idx.P_c).*exp(-.5*obj.aux.sample.kappa(k,:));
        prop_theta_h = obj.prior.mu_h + randn(1,obj.idx.P_h)./...
            sqrt(diag(obj.prior.Pi_h))'; % assume diagonal precision
    else % sample from Gaussian kernel
%             prop_theta_c = obj.aux.sample.theta_c(n,:) + ...
%                 randn(1, obj.idx.P_c).*obj.aux.step.theta_c(n,:);
        tmp = (randn(1, obj.idx.P_c + obj.idx.P_h).*obj.aux.step.theta(n,:))* ...
            obj.aux.transform.theta;
        prop_theta_c = obj.aux.sample.theta_c(n,:) + tmp(1:obj.idx.P_c);
        prop_theta_h = obj.aux.sample.theta_h(n,:) + tmp(obj.idx.P_c+1:end);
    end

    % evaluate joint
    % log-prior
    prop_dtheta_c = bsxfun(@minus, prop_theta_c, obj.aux.sample.mu);
    prop_rho = obj.aux.l2pi + sum(prec.*prop_dtheta_c.^2, 2)' ...
        + 0.5*sum(obj.aux.sample.kappa, 2)';
    prop_rho_max = max(prop_rho);
    prop_rho = prop_rho - prop_rho_max;
    prop_lpr_c = log(exp(prop_rho)*obj.aux.sample.pi(:)) + prop_rho_max;
    prop_dtheta_h = prop_theta_h - obj.prior.mu_h;
    prop_lpr_h = -.5*prop_dtheta_h*obj.prior.Pi_h*prop_dtheta_h';
    % log-likelihood
    if invTemp
        prop_epsilon = obj.bold_gen( [prop_theta_c, prop_theta_h], ...
            obj.data(n), obj.inputs(n), obj.options.hemo, obj.R, ...
            obj.L, obj.idx );
        tmp = obj.aux.sample.lambda(n,:) - obj.aux.lvarBold(n,:);
        prop_llh = -.5*sum(prop_epsilon.^2*exp(tmp)') ...
           +.5*obj.aux.q_r(n)*sum(tmp);
    else % skip calculation of BOLD signal if inverse temp is zero
        prop_llh = 0;
        prop_epsilon = 0;
    end

    % accept/reject
    a = exp(~bGmm*(prop_lpr_c - obj.aux.lpr.theta_c(n) ...
        + prop_lpr_h - obj.aux.lpr.theta_h(n)) ...
        + invTemp*(prop_llh - obj.aux.lpr.llh(n)));

    if ~isnan(a) && ~isinf(a) && rand()<a
        if bGmm
            obj.aux.nAccept.sp(1) = obj.aux.nAccept.sp(1) + 1;
        else
            obj.aux.nAccept.theta(n) = obj.aux.nAccept.theta(n) + 1;
        end
        obj.aux.sample.theta_c(n,:) = prop_theta_c;
        obj.aux.sample.theta_h(n,:) = prop_theta_h;
        obj.aux.lpr.theta_c(n) = prop_lpr_c;
        obj.aux.lpr.theta_h(n) = prop_lpr_h;
        obj.aux.rho(n,:) = prop_rho;
        obj.aux.rho_max(n) = prop_rho_max;
        obj.aux.epsilon{n} = prop_epsilon;
        obj.aux.lpr.llh(n) = prop_llh;
    end

end
end

%% SAMPLING: Signal-to-noise (lambda)
function [ obj ] = mh_sample_noise( obj, invTemp )

obj.aux.nProp.lambda = obj.aux.nProp.lambda + 1;
for n = 1:obj.N

    % propose in unconstrained space
    prop_lambda = obj.aux.sample.lambda(n,:) + ...
        randn(1, obj.R).*obj.aux.step.lambda(n);

    % evaluate log-conditional
    % prior
    prop_dlambda = prop_lambda - obj.prior.lambda_0;
    prop_lpr = -.5*prop_dlambda.^2*obj.prior.omega_0;
    % likelihood
    tmp = prop_lambda - obj.aux.lvarBold(n,:);
    prop_llh = -.5*sum(obj.aux.epsilon{n}.^2*exp(tmp)') ...
        +.5*obj.aux.q_r(n)*sum(tmp);

    % accept/reject
    a = exp(prop_lpr - obj.aux.lpr.lambda(n) ...
            + invTemp*(prop_llh - obj.aux.lpr.llh(n)));

    if ~isnan(a) && ~isinf(a) && rand()<a
        obj.aux.nAccept.lambda(n) = obj.aux.nAccept.lambda(n) + 1;
        obj.aux.sample.lambda(n,:) = prop_lambda;
        obj.aux.lpr.lambda(n) = prop_lpr;
        obj.aux.lpr.llh(n) = prop_llh;
    end
end
end

%% SAMPLING: k-means-based mode hopping
% A special proposal for clustering part of HUGE model (pi, mu and kappa)
function [ obj ] = mh_sample_kmhop(obj, k)
% track acceptance rate
obj.aux.nProp.sp(2) = obj.aux.nProp.sp(2) + 1;

% --- do kmeans for k = 1,...,K, and define q(...|k) ---
% set up prior
tmpm = exp(obj.prior.s_0+1./2./obj.prior.nu_0');
tmpv = (exp(1./obj.prior.nu_0')-1).*exp(2*obj.prior.s_0+1./obj.prior.nu_0');
b0 = tmpm./tmpv;
a0 = tmpm.^2./tmpv;

% get data
X = obj.aux.sample.theta_c;

% reserve memory
qmm = repmat(obj.prior.m_0,obj.K,1,obj.K);
qms = repmat(diag(obj.prior.T_0)',obj.K,1,obj.K);
qkm = repmat(obj.prior.s_0,obj.K,1,obj.K);
qks = repmat(obj.prior.nu_0',obj.K,1,obj.K);
qpm = zeros(obj.K,obj.K);
for k1 = 1:obj.K
    % do kmeans
    idx = kmeans(X,k1,'Replicates',100);
    % assemble parameters for q
    for k2 = 1:obj.K
        nk = nnz(idx(:)==k2);
        if nk > 0
            mk = mean(X(idx(:)==k2,:),1);
            ak = a0 + (nk + 1)/2;
            bk = b0 + 0.5.*sum(bsxfun(@minus,X(idx(:)==k2,:),mk).^2,1);
            tmpm = ak./bk;
            tmpv = ak./bk.^2;
            % q(kappa)
            qks(k2,:,k1) = 1./log(tmpv./tmpm.^2 + 1);
            qkm(k2,:,k1) = log(tmpm) - 1./2./qks(k2,:,k1);
            % q(mu)
            qms(k2,:,k1) = diag(obj.prior.T_0)' + nk.*exp(obj.prior.s_0);
            qmm(k2,:,k1) = (diag(obj.prior.T_0)'.*obj.prior.m_0 + ...
                nk.*exp(obj.prior.s_0).*mk)./qms(k2,:,k1);
        end
        qpm(k1,k2) = nk + obj.prior.alpha_0(k2);
    end
end

% --- generate proposal for k in 1,...,K, and eval p ---
k1 = k;

prop_lpr_mu = zeros(1,obj.K);
prop_lpr_kappa = zeros(1,obj.K);
prop_rho = obj.aux.rho;

prop_pi = zeros(1,obj.K);
prop_kappa = zeros(obj.K,obj.idx.P_c);
prop_mu = zeros(obj.K,obj.idx.P_c);
for k2 = 1:obj.K
    % draw kappa*
    prop_kappa(k2,:) = qkm(k2,:,k1) + randn(1,obj.idx.P_c)./sqrt(qks(k2,:,k1));
    % draw mu*
    prop_mu(k2,:) = qmm(k2,:,k1) + randn(1,obj.idx.P_c)./sqrt(qms(k2,:,k1));
    % draw pi*
    prop_pi(1,k2) = gamrnd(qpm(k1,k2),1);

    % eval log p(mu*)
    prop_dmu_k = prop_mu(k2,:) - obj.prior.m_0;
    prop_lpr_mu(k2) = -prop_dmu_k*obj.prior.T_0*prop_dmu_k'/2;
    % eval log p(kappa*)
    prop_dkappa = prop_kappa(k2,:) - obj.prior.s_0;
    prop_lpr_kappa(k2) = -.5*prop_dkappa.^2*obj.prior.nu_0;

    % log-conditional theta_c given mu and kappa
    prop_dtheta_c = bsxfun(@minus, obj.aux.sample.theta_c, prop_mu(k2,:));
    prop_rho(:,k2) = obj.aux.l2pi - .5*prop_dtheta_c.^2*exp(prop_kappa(k2,:)') ...
        + .5*sum(prop_kappa(k2,:));

end
prop_pi = prop_pi./sum(prop_pi);

% eval log p(pi*)
prop_pis = [prop_pi(1),prop_pi(2:end-1)./(1 - cumsum(prop_pi(1:end-2)))];
prop_pic = cumprod(1-prop_pis);
prop_piu = tapas_huge_logit(prop_pis) - log(1./(obj.K-1:-1:1));
% log-prior on pi
prop_lpr_pi = log(prop_pi)*(obj.prior.alpha_0 - 1) + ...
   sum(log(prop_pis)) + sum(log(1-prop_pis)) + ...
   sum(log(prop_pic(1:end-1)));
prop_lpr_pi = max(prop_lpr_pi, -realmax);

% eval log p(theta|pi*,mu*,kappa*)
prop_rho_max = max(prop_rho, [], 2);
prop_rho = bsxfun(@minus, prop_rho, prop_rho_max);
prop_lcd = log(exp(prop_rho)*prop_pi') + prop_rho_max; % log-sum-exp

% --- iterate over all permutations and eval q ---
% eval log q(pi*,kappa*,mu*)
[prop_lq_pi,prop_lq_kappa,prop_lq_mu] = eval_lq_perm(prop_pi,...
    prop_kappa,prop_mu,qpm,qkm,qks,qmm,qms,obj.K);

% eval log q(pi,kappa,mu)
[smp_lq_pi,smp_lq_kappa,smp_lq_mu] = eval_lq_perm(obj.aux.sample.pi,...
    obj.aux.sample.kappa,obj.aux.sample.mu,qpm,qkm,qks,qmm,qms,obj.K);

m_prop_lq_mu = logmeanexp(prop_lq_mu);
m_prop_lq_kappa = logmeanexp(prop_lq_kappa);
m_prop_lq_pi = logmeanexp(prop_lq_pi);

m_smp_lq_mu = logmeanexp(smp_lq_mu);
m_smp_lq_kappa = logmeanexp(smp_lq_kappa);
m_smp_lq_pi = logmeanexp(smp_lq_pi);

% --- eval MH acceptance ratio ---
a = [sum(prop_lcd - obj.aux.lpr.theta_c) ...
    ;sum(prop_lpr_kappa - obj.aux.lpr.kappa) ...
    ;sum(prop_lpr_mu - obj.aux.lpr.mu) ...
    ;prop_lpr_pi - obj.aux.lpr.pi ...
    ;sum(m_smp_lq_kappa - m_prop_lq_kappa) ...
    ;sum(m_smp_lq_mu - m_prop_lq_mu) ...
    ;m_smp_lq_pi - m_prop_lq_pi ...
];
% figure(1);clf;stem(a);
a = min(1,exp(sum(a)));

% accept/reject
if ~isnan(a) && ~isinf(a) && rand()<a
    obj.aux.nAccept.sp(2) = obj.aux.nAccept.sp(2) + 1;
    obj.aux.sample.pi_u = prop_piu;
    obj.aux.sample.pi = prop_pi;
    obj.aux.sample.mu = prop_mu;
    obj.aux.sample.kappa = prop_kappa;
    obj.aux.lpr.pi = prop_lpr_pi;
    obj.aux.lpr.mu = prop_lpr_mu;
    obj.aux.lpr.kappa = prop_lpr_kappa;
    obj.aux.lpr.theta_c = prop_lcd;
    obj.aux.rho = prop_rho;
    obj.aux.rho_max = prop_rho_max;
end

end

% evaluate log q(pi*,kappa*,mu*) for all permutations
function [lq_pi,lq_kappa,lq_mu] = eval_lq_perm(p_pi,p_kappa,p_mu,qpm,qkm,qks,qmm,qms,K)
pidx = perms(1:K);

lq_mu = zeros(size(pidx));
lq_kappa = zeros(size(pidx));
lq_pi = zeros(size(pidx));

x = p_pi;
z = [x(1),x(2:end-1)./(1 - cumsum(x(1:end-2)))];
c = cumprod(1-z);

for k1 = 1:K
    m_pi = zeros(K,K);
    m_mu = zeros(size(m_pi));
    m_kappa = zeros(size(m_pi));
    % mesh log q(pi)
    tmp = gammaln(sum(qpm(k1,:))) - sum(gammaln(qpm(k1,:))) + ... % const
    sum(log(z)) + sum(log(1-z)) + ... % jacobian
    sum(log(c(1:end-1))); % jacobian
    m_pi(:,:) = (qpm(k1,:)' - 1)*log(x(:)') + tmp/K; % x

    % mesh log q(mu,kappa)
    for k2 = 1:K
        for k3 = 1:K
            % log p(mu*)
            m_mu(k2,k3) = -.5*(p_mu(k3,:) - qmm(k2,:,k1)).^2*qms(k2,:,k1)' -.5*log(2*pi) +.5*sum(log(qms(k2,:,k1)));
            % log p(kappa*)
            m_kappa(k2,k3) = -.5*(p_kappa(k3,:) - qkm(k2,:,k1)).^2*qks(k2,:,k1)' -.5*log(2*pi) +.5*sum(log(qks(k2,:,k1)));
        end
    end

    linInd = sub2ind([K,K], repmat(1:K,size(pidx,1),1), pidx);
    lq_mu(:,k1) = sum(m_mu(linInd),2);
    lq_kappa(:,k1) = sum(m_kappa(linInd),2);
    lq_pi(:,k1) = sum(m_pi(linInd),2);

end
end

% log(mean(exp(log_probability)))
function lmep = logmeanexp(lpr)
tmp = max(lpr(:));
lmep = log(mean(exp(lpr(:) - tmp))) + tmp;
end

%---------------------------------
%              MISC
%---------------------------------
%% adapt proposal distribution
function [ obj ] = mh_adapt(obj, iIt)

% adapt step size for:
for parameter = {'pi', 'mu', 'kappa', 'theta', 'lambda'}
    % current acceptance rate
    ratio = obj.aux.nAccept.(parameter{1})./obj.aux.nProp.(parameter{1});
    % correction factor
    tmp = ratio - obj.const.mhRate;
    tmp = exp(sign(tmp).*abs(tmp).^3.*obj.const.mhReg);
    % adapt step size
    obj.aux.step.(parameter{1}) = obj.aux.step.(parameter{1}).*tmp;
    % reset counter
    obj.aux.nProp.(parameter{1}) = 0;
    obj.aux.nAccept.(parameter{1}) = ...
        zeros(size(obj.aux.nAccept.(parameter{1})));

end

step = fix(iIt/obj.const.mhTrans);
idx = iIt:-step:1;

% match proposal distribution to posterior covariance for ...
% ... cluster means
mu = reshape([obj.trace.smp(idx).mu], obj.K, obj.idx.P_c, []);
for k = 1:obj.K
    % calculate SVD on empirical covariance of posterior samples
    tmp = cov(permute(mu(k,:,:), [3 2 1]));
    [rotation, scales] = svd(tmp);
    scales = sqrt(diag(scales));
    % limit smallest proposal step size to 5% of maximum step size
    scales(scales < max(scales)*.05) = max(scales)*.05;
    if max(scales) > 0
        obj.aux.transform.mu(:,:,k) = bsxfun(@times, rotation, scales')';
        tmp = sum(log(scales));
        obj.aux.step.mu(k) = obj.aux.step.mu(k)*exp(...
            (obj.aux.logdet.mu(k) - tmp)/obj.idx.P_c);
        obj.aux.logdet.mu(k) = tmp;
    end
end

% ... subject means
theta = [reshape([obj.trace.smp(idx).theta_c], obj.N, obj.idx.P_c, []), ...
         reshape([obj.trace.smp(idx).theta_h], obj.N, obj.idx.P_h, [])];
pooled = zeros(obj.N, size(theta, 3), obj.idx.P_c + obj.idx.P_h);
for n = 1:obj.N
    tmp = permute(theta(n,:,:), [1 3 2]);
    pooled(n, :, :) = bsxfun(@minus, tmp, mean(tmp, 2));
end
pooled = reshape(pooled, [], obj.idx.P_c + obj.idx.P_h);
% calculate SVD on empirical covariance of posterior samples
tmp = cov(pooled);

[rotation, scales] = svd(tmp);
scales = sqrt(diag(scales));
% limit smallest proposal step size to 5% of maximum step size
scales(scales < max(scales)*.05) = max(scales)*.05;
if max(scales) > 0
    obj.aux.transform.theta = bsxfun(@times, rotation, scales')';
    tmp = sum(log(scales));
    tmp = exp((obj.aux.logdet.theta - tmp)/(obj.idx.P_c + obj.idx.P_h));
    for n = 1:obj.N
        obj.aux.step.theta(n) = obj.aux.step.theta(n)*tmp;
    end
    obj.aux.logdet.theta = sum(log(scales));
end

end

%% potential scale reduction factor
function [ psrf ] = mh_psrf( trace, nChains )

nIt = length(trace);
psrf = struct();
for parameter = fieldnames(trace)'
    smpSize = [size(trace(1).(parameter{1})), nIt];
    try
        tmp = permute(reshape([trace(:).(parameter{1})], smpSize), [3 1 2]);

        psrf.(parameter{1}) = tapas_huge_psrf( tmp, nChains );
    catch
        warning('TAPAS:HUGE:convergence', [ 'Potential scale reduction ' ...
            'factor could not be calculated for %s.'], parameter{1});
        psrf.(parameter{1}) = NaN(smpSize);
    end
end

end


function [class,siz]=ChemSiz(Sin)

%% Give Labels and size for plotting chemical size distributions
%% Label definintions:
%% 1=OC, 2=ECOCIn, 3=ECOC, 4=InOC, 5=NoID
%% RC Moffet, 2010
siz=Sin.Size;
class=zeros(1,length(Sin.PartLabel));
for i = 1:length(Sin.PartLabel)
    NoIdidx=strmatch('NoID',Sin.PartLabel{i},'exact');
    OCidx=strmatch('OC',Sin.PartLabel{i},'exact');
    OCAidx=strmatch('OCsp2',Sin.PartLabel{i},'exact');
    ECidx=findstr(Sin.PartLabel{i},'EC');
    Inidx=findstr(Sin.PartLabel{i},'In');
    Kidx=findstr(Sin.PartLabel{i},'K');
    if ~isempty(OCidx) || ~isempty(OCAidx) %% OC
        class(i)=1;
    elseif ~isempty(ECidx) && (~isempty(Inidx) || ~isempty(Kidx)) %% ECOCIn
        class(i)=2;
    elseif ~isempty(ECidx) && (isempty(Inidx) || isempty(Kidx)) %% ECOC
        class(i)=3;
    elseif (~isempty(Inidx) || ~isempty(Kidx)) && isempty(ECidx) %% InOC
        class(i)=4;
    elseif ~isempty(NoIdidx)  %% NoID
        class(i)=5;
    end
    clear OCidx ECidx Inidx NoIdidx;
end


function [cl,cu] = matRad_getConstraintBounds(optiProb,cst)
% matRad IPOPT get constraint bounds wrapper function
%
% call
%   [cl,cu] = matRad_getConstraintBounds(optiProb,cst)
%
% input
%   cst:            matRad cst struct
%
% output
%   cl: lower bounds on constraints
%   cu: lower bounds on constraints
%
% References
%   -
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Copyright 2016 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.
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

BPtype = class(optiProb.BP);
isEffectBP = strcmp(BPtype,'matRad_EffectProjection');

% Initialize bounds
cl = [];
cu = [];

% compute objective function for every VOI.
for  i = 1:size(cst,1)

    % Only take OAR or target VOI.
    if ~isempty(cst{i,4}) && ( isequal(cst{i,3},'OAR') || isequal(cst{i,3},'TARGET') )

        % loop over the number of constraints for the current VOI
        for j = 1:numel(cst{i,6})

            optiFunc = cst{i,6}{j};

            % only perform computations for constraints
            %if ~isempty(strfind(cst{i,6}{j}.type,'constraint'))
            if isa(optiFunc,'DoseConstraints.matRad_DoseConstraint')

                if isEffectBP
                    doses = optiFunc.getDoseParameters();

                    effect = cst{i,5}.alphaX*doses + cst{i,5}.betaX*doses.^2;

                    optiFunc = optiFunc.setDoseParameters(effect);
                end

                 cl = [cl;optiFunc.lowerBounds(numel(cst{i,4}{1}))];
                 cu = [cu;optiFunc.upperBounds(numel(cst{i,4}{1}))];

                %end
            end

        end % over all objectives of structure

    end % if structure not empty and target or oar

end % over all structures


function varargout = bounds(x,lower,upper,aux)
%BOUNDS Adds implicit bounds on variables.
%
% BOUNDS IS OBSOLETE: Use standard constraints
%
% BOUNDS(x,Lower,Upper)   Adds bound constraints on variables.
%                         These bounds are used when performing
%                         big M formulations and similar things.

variables = getvariables(x);
if nargin == 1
    lb = yalmip('getbounds',variables);
    lower = lb(:,1);
    upper = lb(:,2);
else
    lower = lower(:);
    upper = upper(:);
    if length(lower)==1
        lower = repmat(lower,length(variables),1);
    end
    if length(upper)==1
        upper = repmat(upper,length(variables),1);
    end
    % 0 - No problems
    % 1 - Trying to bound nonlinear variable
    % 2 - Trying to bound nonlinear operator
    fail = yalmip('setbounds',variables,lower,upper);
    switch fail
        case {1,2}
            error('BOUNDS can only be applied to linear unitary SDPVAR variables.')
        otherwise
    end
end

if nargout>0
    varargout{1} = lower;
    varargout{2} = upper;
end


function appendYALMIPvariables(lmi_variables,mt,variabletype,hashed_monoms,current_hash);

if nargin == 1
    [mt,variabletype,hashed_monoms,current_hash] = yalmip('monomtable');
end

% Update monomtable and pre-calculated variable type
n_mt = size(mt,1);
m_mt = size(mt,2);
newmt = [];
if min(lmi_variables)>m_mt % New variables
    if size(mt,1)~=size(mt,2)
        mt(size(mt,1),size(mt,1))=0;
    end
    % This was faster before. However in recent versions of matlab, there
    % is a compiled version of blkdiag available
    % fill=spalloc(size(mt,1),length(lmi_variables),0);
    % mt=[mt fill;fill' speye(length(lmi_variables))];
    if isempty(mt)
        mt = speye(length(lmi_variables));
        newmt = mt;
    elseif length(lmi_variables)==1
        % Slightly faster than general case
       newmt = sparse(1);
       mt(end+1,end+1) = newmt;
    else
        newmt = speye(length(lmi_variables));
        mt=blkdiag(mt,newmt);
    end
else
    mt(lmi_variables,lmi_variables) = speye(length(lmi_variables));
end
variabletype(1,size(mt,1)) = 0;
if ~isempty(newmt)
    new_hash = 3*rand_hash(size(mt,2),size(newmt,2),1);
    hashed_monoms = [hashed_monoms;full(newmt*new_hash)];
    current_hash = [current_hash;new_hash];
    yalmip('setmonomtable',mt,variabletype,hashed_monoms,current_hash);
else
    yalmip('setmonomtable',mt,variabletype);
end


%this script demos the usage of evaluation routines
% the result file 'demo.val.pred.txt' on validation data is evaluated
% against the ground truth

fprintf('CLASSIFICATION WITH LOCALIZATION TASK\n');

meta_file = '../data/meta_clsloc.mat';
%pred_file='demo.val.pred.loc.txt';
%pred_file='/data/vision/torralba/deeplearning/visualization/prediction_imagenetValSet_imagenetCNNaveSumDeep.txt'
pred_file='/data/vision/torralba/deeplearning/visualization/prediction_imagenetValSet_googlenet_imagenet.txt'

ground_truth_file='../data/ILSVRC2014_clsloc_validation_ground_truth.txt';
blacklist_file='../data/ILSVRC2014_clsloc_validation_blacklist.txt';
num_predictions_per_image=5;
optional_cache_file = 'cache_groundtruth.mat';

fprintf('pred_file: %s\n', pred_file);
fprintf('ground_truth_file: %s\n', ground_truth_file);
fprintf('blacklist_file: %s\n', blacklist_file);

if isempty(optional_cache_file)
    fprintf(['NOTE: you can specify a cache filename and the ground ' ...
             'truth data will be automatically cached to save loading time ' ...
             'in the future\n']);
end

% num_val_files = -1;
% while num_val_files ~= 50000
%     if num_val_files ~= -1
%         fprintf('That does not seem to be the correct directory. Please try again\n');
%     end
%     %ground_truth_dir=input('Please enter the path to the Validation bounding box annotations directory: ', 's');
     ground_truth_dir='/data/vision/torralba/deeplearning/imagenet_toolkit/val_bbox';
%     %fprintf('ground_truth_dir: %s\n', ground_truth_dir);
%     val_files = dir(sprintf('%s/*.xml',ground_truth_dir));
%     num_val_files = numel(val_files);
% end

error_cls = zeros(num_predictions_per_image,1);
error_loc = zeros(num_predictions_per_image,1);

for i=1:num_predictions_per_image
    [error_cls(i) error_loc(i)] = eval_clsloc(pred_file,ground_truth_file,ground_truth_dir,...
                                              meta_file,i, blacklist_file,optional_cache_file);
end

disp('# guesses vs clsloc error vs cls-only error');
disp([(1:num_predictions_per_image)',error_loc,error_cls]);

%% Result: alexFullConv network
% # guesses vs clsloc error vs cls-only error
%     1.0000    0.7461    0.5077
%     2.0000    0.6859    0.3841
%     3.0000    0.6569    0.3209
%     4.0000    0.6401    0.2835
%     5.0000    0.6270    0.2556

%% Result: googleNet_imagenet
% # guesses vs clsloc error vs cls-only error
%     1.0000    0.6691    0.3450
%     2.0000    0.6158    0.2294
%     3.0000    0.5937    0.1786
%     4.0000    0.5813    0.1503
%     5.0000    0.5734    0.1314


function exportIVH(structNum,scanSet,Opt)
% exportDVH
% This function exports DVH in an EXCEL format. Opt give the option to
% choose if you want Normalised or Absolute DVH
% written DK
%
% Copyright 2010, Joseph O. Deasy, on behalf of the CERR development team.
%
% This file is part of The Computational Environment for Radiotherapy Research (CERR).
%
% CERR development has been led by:  Aditya Apte, Divya Khullar, James Alaly, and Joseph O. Deasy.
%
% CERR has been financially supported by the US National Institutes of Health under multiple grants.
%
% CERR is distributed under the terms of the Lesser GNU Public License.
%
%     This version of CERR is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or
%     (at your option) any later version.
%
% CERR is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
% without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
% See the GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with CERR.  If not, see <http://www.gnu.org/licenses/>.
%
% Usage
% exportDVH(structNum,Opt)

% for command line help document
if ~exist('structNum')& ~exist('scanSet')& ~exist('Opt')
    prompt = {'Enter the structure Number';'Enter the scan number'; 'Enter "abs" for absolute OR "nor" for normalized DVH'};
            dlg_title = 'Export IVH';
            num_lines = 1;
            def = {'';'';''};
            outPutQst = inputdlg(prompt,dlg_title,num_lines,def);
            if isempty(outPutQst{1}) | isempty(outPutQst{2})| isempty(outPutQst{3})
                warning('Need to enter all the inputs');
                return
            else
                structNum = str2num(outPutQst{1});
                scanSet = str2num(outPutQst{2});
                Opt = outPutQst{3};
            end
end

path = uigetdir( cd,'Select destination Directory for DVH export');
global planC
indexS = planC{end};
structureCell = planC{indexS.structures};
optS = opts4Exe([getCERRPath,'CERROptions.json']);
%loop over all the structures that need to be exported
for i = 1:length(structNum)
    name = structureCell(structNum(i)).structureName;
    [scansV, volsV] = getIVH(structNum(i), scanSet(i), planC);
    [scanBinsV, volsHistV] = doseHist(scansV, volsV, optS.DVHBinWidth);
    cumVolsV = cumsum(volsHistV);
    cumVols2V  = cumVolsV(end) - cumVolsV;  %cumVolsV is the cumulative volume lt that corresponding scan
    switch upper(Opt)
        case 'ABS'
%             if abs flag is set just export the values as it is
            fVol = cumVols2V;
        case 'NOR'
            %Normalizing the volume
            fVol = cumVols2V/cumVolsV(end);
    end
    M = [scanBinsV; fVol];
    %Export only NumPts points (for Dr. Myerson)
    NumPts = 200;
    if size(M,2)>NumPts
        indAll = round(linspace(1,size(M,2),NumPts));
        M = M(:,indAll);
    end
    xlswrite(fullfile(path,name), M');
end
clear fVol scansV volsV cumVolsV cumVols2V scanBinsV volsHistV optS


function signal = set_infer_markers(varargin)
% Infers markers from an event channel, if possible.
% Signal = set_infer_markers(Signal)
%
% If the data set has one (or more) channels with relatively few unique values, and relatively few
% off-median values (or plateaus thereof), markers are being generated to encode whenever such a
% channel changes its value. By default, the detected event channels are being removed from the
% data.
%
% This funciton is automatically called by io_loadset if no markers are present in the given
% set. io_loadset has a parameter ('markerchannel') which can be utilized to customize the settings
% below (as a cell array of arguments to set_infer_markers).
%
% In:
%   Signal                    : continuous EEGLAB data set from which an interval should be selected
%
%   MaxEvents                 : a channel is only considered for event generation if it would produce
%                               at most this many distinct events (default: 25000)
%
%   MaxTypes                  : a channel is only considered for event generation if it would produce
%                               at most this many distinct event types (default 300)
%
%   IncludeLabel              : when to include the channel label into the generated event types;
%                               can be 'always', 'multiplechans', 'never' (default: 'multiplechans')
%
%   AllPositive               : whether all events must have positive offsets from the baseline
%                               (median) value (default: true)
%
%   EncodePlateaus            : Whether the trigger channel may contain (non-baseline) plateaus, to
%                               be encoded in event duration. (default: true)
%
%   StrictInteger             : only consider channels that would produce integer event types
%                               (otherwise: round to integer) (default: true)
%
%   RelativeBaselineThreshold : Relative baseline threshold. If the event types would be larger than
%                               this threshold, they will be re-coded to be relative to the smallest
%                               event type in the channel. (default: 10000)
%
%   OmitBaselineThreshold     : Baseline omission threshold. If the baseline event takes up more than
%                               this fraction of the data, it will not be encoded as events.
%                               (default: 0.8)
%
%   RemoveEventChannels       : Whether to remove channels that have been identified as event
%                               channels (default: true)
%
% Out:
%   Signal    : data set restricted to the selected range
%
% Examples:
%   % for a data set with one or more trigger channels, fill in contents of the .event field
%   % and remove the trigger channel(s); do nothing if the data set does not contain trigger channels
%   eeg = set_infer_markers(eeg)
%
%   % fill in contents of the .event field from trigger channels, and do not remove these channels
%   eeg = set_infer_markers('Signal',eeg,'RemoveEventChannels',false)
%
%   % if an event channel is not being detected because it produces more than the default MaxEvents
%   % number of events, use a larger cutoff value
%   eeg = set_infer_markers('Signal',eeg,'MaxEvents',30000)
%
%   % if an event channel is not being detected because it produces more than the default MaxTypes
%   % number of distinct event types, use a larger cutoff value
%   eeg = set_infer_markers('Signal',eeg,'MaxTypes',1000)
%
%   % for event channels that have values that far from zero (say, 30000+), the values are by default
%   % made relative to to the baseline value (most frequent or smallest value, depending on the
%   % EncodePlateaus setting) -- if this is not intended, override the cutoff with a larger value
%   eeg = set_infer_markers('Signal',eeg,'RelativeBaselineThreshold',Inf)
%
%   % if a trigger channel contains many subsequent events of the same type (i.e. plateaus), by
%   % default only one event is generated with the appropriate duration; if instead an event should
%   % be generated for each sample, override that default (and possibly increase MaxEvents)
%   eeg = set_infer_markers('Signal',eeg,'EncodePlateaus',false,'MaxEvents',Inf)
%
% See also:
%   io_loadset
%
%                                Christian Kothe, Swartz Center for Computational Neuroscience, UCSD
%                                2010-01-26

declare_properties('independent_channels',false,'independent_trials',false);

arg_define(varargin, ...
    arg_norep({'signal','Signal'}), ...
    arg({'force_processing','ForceProcessing'},false,[], 'Force marker processing. Scan for marker channels even if a data set already has events.'), ...
    arg({'max_events','MaxEvents'},30000,uint32([0 1000000000]), 'Number of events allowed. If a channel would produce more than this many events, it is not considered an event channel.'), ...
    arg({'max_event_fraction','MaxEventFraction'},0.3,[0 1], 'Maximum fraction of events. If a channel would produce more events than this fraction of its total number of samples, the channel is not considered an event channel.'), ...
    arg({'max_types','MaxTypes'},300,uint32([0 1000000000]), 'Number of eventtypes allowed. If a channel would produce more than this many event types, it is not considered an event channel.'), ...
    arg({'include_label','IncludeLabel'},'multiplechans',{'always','multiplechans','never'},'Integrate channel labels. When to integrate channel labels into the event types - by default. only when there are multiple event channels.'), ...
    arg({'all_positive','AllPositive'},true,[],'Events must be positive. Only if all event types produced by a channel would correspond to positive numbers, the channel is considered an event channel.'), ...
    arg({'encode_plateaus','EncodePlateaus'},true,[],'Plateaus encoded. The trigger channel may contain (non-baseline) plateaus, to be encoded in event duration.'), ...
    arg({'strict_integer','StrictInteger'},true,[],'Strictly integer. Only allow channels that have integer event types (otherwise: round to integers).'), ...
    arg({'relative_baseline_thresh','RelativeBaselineThreshold'},10000,[],'Relative baseline threshold. If the event types would be larger than this threshold, they will be re-coded to be relative to the smallest event type in the channel.'), ...
    arg({'omit_baseline_thresh','OmitBaselineThreshold'},0.8,[0 1],'Baseline omission threshold. If the baseline event takes up more than this fraction of the data, it will not be encoded as events.'), ...
    arg({'remove_eventchns','RemoveEventChannels'},true,[],'Remove event channels. Whether to remove those channels that have been identified as event channels.'));

% obtain the chanlocs
if isfield(signal,{'head','parts'})
    signal = exp_eval(signal); end
utl_check_fields(signal,{'data','chanlocs'},'signal','signal');

% figure out which are the event channels
fprintf('Scanning potential marker channel ');
for k=1:size(signal.data,1) %#ok<*NODEF>
    fprintf('%i ',k);
    X = signal.data(k,:);
    X(isnan(X)) = 0;
    % do a few vectorized computations for speed...
    numtypes(k) = length(unique(X));
    if numtypes(k) <= max_types
        if encode_plateaus
            % in the plateaus case, the baseline is 0 or the smallest value
            baseline(k) = min(X);
            if baseline(k) < relative_baseline_thresh && baseline(k) > 0
                baseline(k) = 0; end
            % whether baseline periods are encoded as events depends on the fraction of time in the
            % data set
            omit_baseline(k) = mean(X == baseline(k)) > omit_baseline_thresh;
            eventmask = ([1; diff(X(:))] ~= 0);
            if omit_baseline(k)
                eventmask = eventmask & (X(:) ~= baseline(k)); end
            numevents(k) = nnz(eventmask);
        else
            % without plateaus, the baseline type is the mode
            baseline(k) = mode(X);
            numevents(k) = nnz(X - baseline(k));
            omit_baseline(k) = true;
        end
    else
        % channel will not be considered
        baseline(k) = 0;
        omit_baseline(k) = true;
        numevents(k) = 0;
    end
    allpositive(k) = all(X >= baseline(k));
    allinteger(k) = all(abs(X-round(X)) <= eps(X));
end
fprintf('\n');

% find the mask of event channels
eventchans = (numtypes <= max_types) & (numevents <= max_events) & (numevents <= size(signal.data,2)*max_event_fraction) & (~all_positive | allpositive) & (~strict_integer | allinteger);

% determine whether to include labels
include_label = hlp_rewrite(include_label,'always',true,'never',false,'multiplechans',nnz(eventchans) > 1);

% initialize events
if ~isfield(signal,'event') || isempty(signal.event)
    signal.event = struct('latency',{},'duration',{},'type',{}); end

% generate events
for k = find(eventchans)
    try
        % find event codes per sample
        X = round(signal.data(k,:) - baseline(k));
        X(isnan(X)) = 0;
        % do a run-length encode into events
        lat = find([1; diff(X(:))] ~= 0);
        % optionally mask out baseline events
        if omit_baseline(k)
            mask = X(lat) ~= 0;
        else
            mask = true(1,length(lat));
        end
        % get values, duration, and latency as cell arrays
        dur = num2cell(diff([lat; numel(X)+1]));
        val = cellfun(@num2str,num2cell(X(lat(mask)),1),'UniformOutput',false);
        if include_label
            val = cellfun(@(n) [signal.chanlocs(k).labels '_' n],val,'UniformOutput',false); end
        lat = num2cell(lat(mask));
        dur = dur(mask);
        if ~isempty(lat)
            % make space
            signal.event(end+length(lat)).latency = [];
            % insert new content
            [signal.event(end-length(lat)+1:end).latency] = lat{:};
            [signal.event(end-length(lat)+1:end).duration] = dur{:};
            [signal.event(end-length(lat)+1:end).type] = val{:};
        else
            % the channel is not actually an event channel (but a flatline)
            eventchans(k) = false;
        end
    catch e
        disp(['Could not process channel ' num2str(k)]);
        env_handleerror(e);
    end
end

% sort events
signal.event = signal.event(hlp_getresult(2,@sort,[signal.event.latency]));

% remove event channels
signal.data = signal.data(~eventchans,:,:,:,:,:,:,:);
signal.chanlocs = signal.chanlocs(~eventchans);
signal.nbchan = size(signal.data,1);


% STD_ERPIMAGE - Compute ERP images and save them on disk.
%
% Usage:
%   >> std_erpimage( EEG, 'key', 'val', ...);
%
% Inputs:
%   EEG          - a loaded epoched EEG dataset structure. May be an array
%                  of such structure containing several datasets.
%
% Optional inputs:
%   'components' - [numeric vector] components of the EEG structure for which
%                  the measure will be computed {default|[] -> all}
%   'channels'   - [cell array] channels of the EEG structure for which
%                  activation ERPs will be computed {default|[] -> none}
%   'trialindices' - [cell array] indices of trials for each dataset.
%                  Default is all trials.
%   'recompute'  - ['on'|'off'] force recomputing data file even if it is
%                  already on disk.
%   'rmcomps'    - [integer array] remove artifactual components (this entry
%                  is ignored when plotting components). This entry contains
%                  the indices of the components to be removed. Default is none.
%   'interp'     - [struct] channel location structure containing electrode
%                  to interpolate ((this entry is ignored when plotting
%                  components). Default is no interpolation.
%   'fileout'    - [string] name of the file to save on disk. The default
%                  is the same name (with a different extension) as the
%                  dataset given as input.
%
% ERPimage options:
%   'concatenate' - ['on'|'off'] concatenate single trial of different
%                  subjects for plotting ERPimages ('on'). The default
%                  ('off') computes an ERPimage for each subject and then
%                  averages these ERPimages. This allows to perform
%                  statistics (the 'on' options does not allow statistics).
%   'smoothing'  - Smootmeter (number of trials). {Default: 10}
%                  ERPIMAGE equivalent: 'avewidth'
%   'nlines'     - Number of lines for ERPimage. ERPIMAGE equivalent is
%                  'decimate'. Note that this parameter must be larger than
%                  the minimum number of trials in each design cell
%                  {Default: 10}
%   'sorttype'   - Sorting event type(s) ([int vector]; []=all). See Notes below.
%                  Either a string or an integer.
%   'sortwin'    - Sorting event window [start, end] in milliseconds ([]=whole epoch)
%   'sortfield'  - Sorting field name. {default: latency}.
%   'erpimageopt'  - ERPIMAGE options, separated by commas (Ex: 'erp', 'cbar').
%                  {Default: none}. For further details see >> erpimage help
% Outputs:
%   erpimagestruct - structure containing ERPimage information that is
%                    been saved on disk.
%
%   Files are saved on disk.
%    [dataset_file].icaerpim     % component ERPimage file
% OR
%    [dataset_file].daterpim     % channel ERPimage file
%
% Author: Arnaud Delorme, SCCN & CERCO, CNRS, 2011-

% Copyright (C) 2011 Arnaud Delorme
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.

function allerpimage = std_erpimage( EEG, varargin)

if nargin < 1
    help std_erpimage;
    return;
end

allerpimage = [];
[opt, moreopts] = finputcheck( varargin, { ...
    'components'    'integer'     []                    [];
    'channels'      { 'cell','integer' }  { [] [] }     {};
    'trialindices' { 'integer','cell' }   []            [];
    'recompute'     'string'      { 'on','off' }        'off';
    'savefile'      'string'      { 'on','off' }        'on';
    'fileout'       'string'      []                    '';
    'rmcomps'       'cell'        []                    cell(1,length(EEG));
    'interp'        'struct'      { }                   struct([]);
    'nlines'        ''            []                    10;
    'smoothing'     ''            []                    10;
    'sorttype'       ''           {}                    '';
    'sortwin'        ''           {}                    [];
    'sortfield'      ''           {}                    'latency';
    'concatenate'   'string'      { 'on'  }             'on';
    'trialinfo'     'struct'      []                    struct([]);
    'savetrials'    'string'      { 'on','off' }        'on'; % obsolete (never used)
    'erpimageopt'   'cell'        {}                    {}}, ...
    'std_erpimage', 'ignore');
if ischar(opt), error(opt); end
if length(EEG) == 1 && isempty(opt.trialindices), opt.trialindices = { [1:EEG.trials] }; end
if isempty(opt.trialindices), opt.trialindices = cell(length(EEG)); end
if ~iscell(opt.trialindices), opt.trialindices = { opt.trialindices }; end
if isempty(opt.channels)
    if isfield(EEG,'icaweights')
        numc = size(EEG(1).icaweights,1);
    else
        error('EEG.icaweights not found');
    end
    if isempty(opt.components)
        opt.components = 1:numc;
    end
end

% filename
% --------
if isempty(opt.fileout), opt.fileout = fullfile(EEG(1).filepath, EEG(1).filename(1:end-4)); end
if ~isempty(opt.channels)
    filenameshort = [ opt.fileout '.daterpim'];
    prefix = 'chan';
    if iscell(opt.channels)
        if ~isempty(opt.interp)
            opt.indices = eeg_chaninds(opt.interp, opt.channels, 0);
        else
            opt.indices = eeg_chaninds(EEG(1), opt.channels, 0);
            for ind = 2:length(EEG)
                if ~isequal(eeg_chaninds(EEG(ind), opt.channels, 0), opt.indices)
                    error([ 'Channel information must be consistent when ' 10 'several datasets are merged for a specific design' ]);
                end
            end
        end
    else
        opt.indices = opt.channels;
    end
else
    opt.indices = opt.components;
    filenameshort = [ opt.fileout '.icaerpim'];
    prefix = 'comp';
end
filename = filenameshort;

% ERP information found in datasets
% ---------------------------------
if exist(filename) && strcmpi(opt.recompute, 'off')
    fprintf('Use existing file for ERSP: %s; check the ''recompute checkbox'' to force recomputing.\n', filenameshort);
    return;
end

allerpimage = [];
if strcmpi(opt.concatenate, 'off')
    % compute ERP images
    % ------------------
    if isempty(opt.channels)
         X = eeg_getdatact(EEG, 'component', opt.indices, 'trialindices', opt.trialindices );
    else X = eeg_getdatact(EEG, 'channel'  , opt.indices, 'trialindices', opt.trialindices, 'rmcomps', opt.rmcomps, 'interp', opt.interp);
    end
    if ~isempty(opt.sorttype)
         events = eeg_getepochevent(EEG, 'type', opt.sorttype, 'timewin', opt.sortwin, 'fieldname', opt.sortfield, 'trials', opt.trialindices);
    else events = [];
    end

    % reverse engeeneering the number of lines for ERPimage
    finallines = opt.nlines;
    if ~isempty(events)
         if all(isnan(events))
             error('Cannot sort trials for one of the dataset');
         end
         lastx  = sum(~isnan(events));
    else lastx  = size(X,3);
    end
    if lastx < finallines + floor((opt.smoothing-1)/2) + 3
        error('The default number of ERPimage lines is too large for one of the dataset');
    end
    firstx = 1;
    xwidth = opt.smoothing;
    %xadv   = lastx/finallines;
    nout   = finallines; %floor(((lastx-firstx+xadv+1)-xwidth)/xadv);
    nlines = (lastx-xwidth)/(nout-0.5)*i; % make it imaginary
    %nlines = ceil(lastx/((lastx-firstx+1-xwidth)/(nout-1)));

    if 0
        % testing conversion back and forth
        % ---------------------------------
        for lastx = 20:300
            for xwidth = 1:19
                for nlines = (xwidth+1):100

                    nout = floor(((lastx+nlines)-xwidth)/nlines);
                    realnlines = (lastx-xwidth)/(nout-0.5);
                    noutreal = floor(((lastx+realnlines)-xwidth)/realnlines);

                    if nout ~= noutreal
                        error('Wrong conversion 2');
                    end

                end
            end
        end
    end

    clear tmperpimage eventvals;
    for index = 1:size(X,1)
        [tmpX, tmpevents] = erpimage(squeeze(X(index,:,:)), events, EEG(1).times, '', opt.smoothing, nlines, 'noplot', 'on', opt.erpimageopt{:}, moreopts{:});
        if isempty(events), tmpevents = []; end
        eventvals{index}   = tmpevents;
        tmperpimage{index} = tmpX';
    end
    allerpimage.events = eventvals{1};
    for index = 1:size(X,1)
        allerpimage.([ prefix int2str(opt.indices(index)) ]) = tmperpimage{index};
    end
else
    % generate dynamic loading commands
    % ---------------------------------
    for dat = 1:length(EEG)
        filenames{dat} = fullfile(EEG(dat).filepath, EEG(dat).filename);
    end
    allerpimage.times = EEG(1).times;
    for index = 1:length(opt.indices)
        if ~isempty(opt.channels)
             com = sprintf('squeeze(eeg_getdatact(%s, ''interp'', chanlocsforinterp));', vararg2str( { filenames 'channel'  , opt.indices(index), 'rmcomps', opt.rmcomps, 'trialindices', opt.trialindices } ));
        else com = sprintf('squeeze(eeg_getdatact(%s));', vararg2str( { filenames 'component', opt.indices(index), 'trialindices', opt.trialindices } ));
        end
        allerpimage = setfield(allerpimage, [ prefix int2str(opt.indices(index)) ], com);
    end
    if ~isempty(opt.channels)
        com = sprintf('squeeze(eeg_getdatact(%s, ''interp'', chanlocsforinterp));', vararg2str( { filenames 'rmcomps', opt.rmcomps, 'trialindices', opt.trialindices } ));
        allerpimage = setfield(allerpimage, [ prefix 'all' ], com);
    end
    allerpimage = setfield(allerpimage, 'chanlocsforinterp', opt.interp);
    if ~isempty(opt.sorttype)
         events = eeg_getepochevent(EEG, 'type', opt.sorttype, 'timewin', opt.sortwin, 'fieldname', opt.sortfield, 'trials', opt.trialindices);
         %geteventcom = sprintf('eeg_getepochevent(%s);', vararg2str( { filenames 'type', opt.sorttype, 'timewin', opt.sortwin, 'fieldname', opt.sortfield } ));
    else events = [];
    end
    allerpimage = setfield(allerpimage, 'events', events);
end
allerpimage.times       = EEG(1).times;
allerpimage.parameters  = varargin;
allerpimage.datatype    = 'ERPIMAGE';
allerpimage.datafiles   = computeFullFileName( { EEG.filepath }, { EEG.filename });
allerpimage.datatrials  = opt.trialindices;
allerpimage.trialinfo   = opt.trialinfo;

% Save ERPimages in file (all components or channels)
% ----------------------------------------------
if strcmpi(opt.savefile, 'on')
    if strcmpi(prefix, 'comp')
        std_savedat(filename, allerpimage);
    else
        tmpchanlocs = EEG(1).chanlocs;
        allerpimage.labels = opt.channels;
        std_savedat(filename, allerpimage);
    end
end

% compute full file names
% -----------------------
function res = computeFullFileName(filePaths, fileNames)
for index = 1:length(fileNames)
    res{index} = fullfile(filePaths{index}, fileNames{index});
end


function [result, M] = warp_pnt(input, target, method);

% WARP_PNT determine intermediate positions using warping (deformation)
% the input cloud of points is warped to match the target.
% The strategy is to start with simpelest linear warp, followed by a more
% elaborate linear warp, which then is followed by the nonlinear warps up
% to the desired order.
%
% [result, M] = warp_pnt(input, target, method)
%     input          contains the Nx3 measured 3D positions
%     target         contains the Nx3 template 3D positions
%     method         should be empty or any of 'nonlin1', 'nonlin2' ... 'nonlin5'
%
% The default is a traditional linear warp with rescaling in each
% dimension. Optionally you can select a nonlinear warp of the 1st (affine)
% up to the 5th order.
%
% This function depends on the OPTIM and WARPING toolboxes.

% Copyright (C) 2000-2005, Robert Oostenveld
%
% 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

% $Log: warp_optim.m,v $
% Revision 1.1  2009/01/30 04:02:13  arno
% *** empty log message ***
%
% Revision 1.7  2006/11/23 11:34:51  roboos
% use optimization toolbox if possible, othewise use the standard fminsearch function
%
% Revision 1.6  2006/09/13 09:55:58  roboos
% fixed bug (typo) in rigidbody
%
% Revision 1.5  2006/04/13 12:55:45  roboos
% added a str2func to solve a problem with feval and private
%
% Revision 1.4  2006/04/13 10:50:34  roboos
% renamed calls to warp3d into warp_apply
%
% Revision 1.3  2006/04/13 10:47:39  roboos
% renamed all calls to warpfun into warp_error
%
% Revision 1.2  2006/04/13 10:38:24  roboos
% fixed a problem due to find/strmatch
%
% Revision 1.1  2005/08/15 08:12:40  roboos
% Renamed warp_pnt into warp_optim for consistency with other functions.
% Also changed the code, the subsequent ordering of the simple to
% more complex warps is handled more clean.
%
% Revision 1.4  2005/03/21 15:43:42  roboos
% fixed bug in output for nonlinear warping
% added support for rigidbody or globalrescale warp
%
% Revision 1.3  2004/05/19 09:57:08  roberto
% added GPL copyright statement, added CVS log item
%

global fb;

if nargin<3
  method='traditional';
end

pos1 = input;
pos2 = target;

% The warp_error function might be located in the private subdirectory fo
% fieldtrip, i.e. only available to functions in the fieldtrip toolbox.
% The following line ensures that the function can also be found by the
% feval that is executed by the optimalization toolbox.
warp_error = str2func('warp_error');

% set the options for the minimalisation routine
if exist('fminunc')
  % use the optimization toolbox
  optimfun = @fminunc;
  options  = optimset('fminunc');
  options  = optimset(options, 'Display', 'off');
  options  = optimset(options, 'MaxIter', 1500);
  % options  = optimset(options, 'MaxFunEvals', '1000*numberOfVariables');
  options  = optimset(options, 'TolFun', 1e-4);
  options  = optimset(options, 'LargeScale', 'off');
else
  % use a standard matlab function, this function converges slower
  optimfun = @fminsearch;
  options  = optimset('fminsearch');
  options  = optimset(options, 'Display', 'off');
  options  = optimset(options, 'MaxIter', 4500);
end

if fb; fprintf('distance = %f\n', warp_error([0 0 0 0 0 0], pos1, pos2, 'rigidbody')); end

% the warp is done in steps, starting simple and progressively getting more complex
level = find(strcmp(method, {
  'rigidbody'          % 1
  'globalrescale'     % 2
  'traditional'       % 3
  'nonlin1'           % 4
  'nonlin2'           % 5
  'nonlin3'           % 6
  'nonlin4'           % 7
  'nonlin5'           % 8
  }));

if isempty(method)
  error('incorrect warping method specified');
end

if level>=1
  % do a rigid-body transformation (6 parameters)
  if fb; disp('rigidbody...'); end
  ri = [0 0 0 0 0 0];
  rf = optimfun(warp_error, ri, options, pos1, pos2, 'rigidbody');
  if fb; fprintf('distance = %f\n', warp_error(rf, pos1, pos2, 'rigidbody')); end
end

if level>=2
  % do a rigid-body + global rescaling transformation (7 parameters)
  if fb; disp('rigidbody + global rescaling...'); end
  gi = [rf 1];
  gf = optimfun(warp_error, gi, options, pos1, pos2, 'globalrescale');
  if fb; fprintf('distance = %f\n', warp_error(gf, pos1, pos2, 'globalrescale')); end
end

if level>=3
  % do a rigid-body + individual rescaling transformation (9 parameters)
  if fb; disp('rigidbody + individual rescaling...'); end
  ti = [gf gf(7) gf(7)];
  tf = optimfun(warp_error, ti, options, pos1, pos2, 'traditional');
  if fb; fprintf('distance = %f\n', warp_error(tf, pos1, pos2, 'traditional')); end
end

if level>=4
  % do a first order nonlinear transformation,
  if fb; disp('1st order nonlinear...'); end
  e1i = traditional(tf);
  e1i = [e1i(1:3,4) e1i(1:3,1:3)];	% reshuffle from homogenous into nonlinear
  e1f = optimfun(warp_error, e1i, options, pos1, pos2);
  if fb; fprintf('distance = %f\n', warp_error(e1f, pos1, pos2, 'nonlinear')); end
end

if level>=5
  % do a second order nonlinear transformation,
  if fb; disp('2nd order nonlinear...'); end
  e2i = [e1f zeros(3,6)];
  e2f = optimfun(warp_error, e2i, options, pos1, pos2);
  if fb; fprintf('distance = %f\n', warp_error(e2f, pos1, pos2, 'nonlinear')); end
end

if level>=6
  % do a third order nonlinear transformation,
  if fb; disp('3rd order nonlinear...'); end
  e3i = [e2f zeros(3,10)];
  e3f = optimfun(warp_error, e3i, options, pos1, pos2);
  if fb; fprintf('distance = %f\n', warp_error(e3f, pos1, pos2, 'nonlinear')); end
end

if level>=7
  % do a fourth order nonlinear transformation,
  if fb; disp('4th order nonlinear...'); end
  e4i = [e3f zeros(3,10)];
  e4f = optimfun(warp_error, e4i, options, pos1, pos2);
  if fb; fprintf('distance = %f\n', warp_error(e4f, pos1, pos2, 'nonlinear')); end
end

if level>=8
  % do a fifth order nonlinear transformation,
  if fb; disp('5th order nonlinear...'); end
  e5i = [e4f zeros(3,10)];
  e5f = optimfun(warp_error, e5i, options, pos1, pos2);
  if fb; fprintf('distance = %f\n', warp_error(e5f, pos1, pos2, 'nonlinear')); end
end

% return the estimated parameters of the highest level warp
% and compute the warped points
switch level
  case 1
    M = rf;
  case 2
    M = gf;
  case 3
    M = tf;
  case 4
    M = e1f;
  case 5
    M = e2f;
  case 6
    M = e3f;
  case 7
    M = e4f;
  case 8
    M = e5f;
end
result = warp_apply(M, input, method);


function a = gt( x, y )

%Disciplined convex programming information for GT (>):
%   The right-hand side of a less-than constraint must be convex. The
%   left-hand side must be concave. Of course, real constant and affine
%   expressions are both convex and concave and can be used on either
%   side as well.
%
%Disciplined geometric programming information for GT (>):
%   The right-hand side of a less-than constraint must be log-convex---
%   including positive constants, monomials, posynomials, generalized
%   posynomials, and products thereof. The left-hand side must be log-
%   concave---including positive constants, monomials, reciprocals of
%   log-convex expressions, and products thereof.
%
%Note that CVX does not distinguish between strict greater-than (>) and
%greater-than-or-equal (>=) constraints; they are treated identically.
%Feasible interior-point solvers tend to return points which satisfy
%strict inequality, but not all solvers do.

warning( 'CVX:StrictInequalities', cvx_error( 'The use of strict inequalities in CVX is strongly discouraged, because solvers treat them as non-strict inequalities. Please consider using ">=" instead.', [66,75], false, '' ) );
b = newcnstr( evalin( 'caller', 'cvx_problem', '[]' ), x, y, '>' );
if nargout, a = b; 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 clout = reparse_continguous(cl)
% Re-define regions in region object based on contiguous blobs
%
% :Usage:
% ::
%
%    clout = reparse_continguous(cl)
%
% ..
%    NEEDS SOME ADDITIONAL WORK/CHECKING
% ..

ivec = region2imagevec(cl);

clout = region(ivec, 'contiguous_regions', 'noverbose');

% ----------------------------------------------
% Add other fields
% ----------------------------------------------

clindx = ivec.volInfo.cluster;  % wh are in new clusters
nvox = length(clindx);

dat = cat(2, cl.all_data);

if size(dat, 2) == nvox

    % matching field; get dat for new clout
    for j = 1:length(clout)
        clout(j).all_data = dat(:, clindx == j);

        clout(j).dat = nanmean(clout(j).all_data', 1)';
    end

elseif isempty(dat)
    % do nothing
else
    disp('Warning!  all_dat field is wrong size.');

end

end


function opts = SRSolverParams(varargin)
%SRSOLVERPARAMS Parameters for numerical optizimation.
%   SRSOLVERPARAMS returns a parameter structure used for numerical
%   optimization and computations in different super-resolution algorithms.
%
%   The parameter structure consist of the following parameters:
%       - maxFunEvals:  Maximum number of objective function evaluations.
%       - maxIter:      Maximum number of iterations for non-linear
%                       optimization.
%       - maxIrlsIter:  Maximum number of iterations for iteratively
%                       re-weighted least squares algorithms.
%       - tolX:         Measure of the absolute precision required for the
%                       super-resolved pixel values (termination
%                       tolerance).
%       - tolF:         Measure of the absolute precision required for
%                       objective functions in non-linear optimization
%                       (termination tolerance).
%       - gradCheck:    Set to 1 to check the analytic derived gradients
%                       for debug purposes.
%       - verbose:      Level for debug messages. Set to -1 for no
%                       messages, to 0 for only warning messages and 1 for
%                       error messages.

    % Set default value to parameter structure
    opts = struct('maxFunEvals',         50, ...    % Maximim number of function evals
                  'maxIter',             50, ...    % Maximum number of iterations
                  'maxIrlsIter',         15, ...    % Maximum number of iteration for iteratively re-weighted least squares
                  'tolX',                1e-3, ...  % Tolerance criterion for image pixels
                  'tolF',                1e-3, ...  % Tolerance criterion for objective function value
                  'verbose',             false, ... % Use debug outputs
                  'gradCheck',           false);    % Check objective function gradient

    % Update with user-defined parameters
    for k = 1:2:(nargin - 1)

        param = varargin{k};
        value = varargin{k+1};
        if isfield(opts, param)
            opts = setfield(opts, param, value);
        else
            error( sprintf('Invalid parameter %s', param) );
        end

    end


classdef TestStructuredEdgeDetection
    %TestStructuredEdgeDetection

    properties (Constant)
        im = fullfile(mexopencv.root(),'test','balloon.jpg');
    end

    methods (Static)
        function test_1
            img = imread(TestStructuredEdgeDetection.im);
            img = single(img) / 255.0;

            modelFilename = get_model_file();
            pDollar = cv.StructuredEdgeDetection(modelFilename);

            E = pDollar.detectEdges(img);
            validateattributes(E, {'single'}, ...
                {'size',[size(img,1) size(img,2)], '>=',0, '<=',1});

            O = pDollar.computeOrientation(E);
            validateattributes(O, {'single'}, ...
                {'size',[size(img,1) size(img,2)]});

            E_nms = pDollar.edgesNms(E, O);
            validateattributes(E_nms, {'single'}, ...
                {'size',[size(img,1) size(img,2)], '>=',0, '<=',1});
        end

        function test_custom_feat_extract
            % skip test if external M-file is not found on the path
            if ~exist('myRFFeatureGetter.m', 'file')
                error('mexopencv:testskip', 'undefined function');
            end

            img = imread(TestStructuredEdgeDetection.im);
            img = single(img) / 255.0;

            modelFilename = get_model_file();
            pDollar = cv.StructuredEdgeDetection(modelFilename, ...
                'myRFFeatureGetter');
            E = pDollar.detectEdges(img);
        end

        function test_get_features
            img = imread(TestStructuredEdgeDetection.im);
            img = single(img) / 255.0;

            opts = struct();
            opts.normRad = 4;
            opts.grdSmooth = 0;
            opts.shrink = 2;
            opts.nChns = 13;
            opts.nOrients = 4;

            features = cv.StructuredEdgeDetection.getFeatures(img, opts);
            validateattributes(features, {'numeric'}, {'real', 'ndims',3});
            sz = size(features);
            %assert(sz(1) * opts.shrink == size(img,1));
            %assert(sz(2) * opts.shrink == size(img,2));
            assert(sz(3) == opts.nChns);
        end
    end

end

function features = myRFFeatureGetter(src, opts)
    if false
        nsize = fix([size(src,1) size(src,2)] ./ opts.shrink);
        features = zeros([nsize opts.nChns], 'single');
        %TODO: ... compute features
    else
        % call opencv's implementation
        features = cv.StructuredEdgeDetection.getFeatures(src, opts);
    end
end

function fname = get_model_file()
    fname = fullfile(mexopencv.root(),'test','model.yml.gz');
    if exist(fname, 'file') ~= 2
        % download model from GitHub
        url = 'https://cdn.rawgit.com/opencv/opencv_extra/3.2.0/testdata/cv/ximgproc/model.yml.gz';
        urlwrite(url, fname);
    end
end


function [contact, fric_coef, geometry] = RightToe(robot)

    param = sys.GetExtraParams();

    r_foot_frame = robot.Joints(getJointIndices(robot, 'r_leg_akx'));
    contact = CoordinateFrame(...
        'Name','RightToe',...
        'Reference',r_foot_frame,...
        'Offset',[param.lt, 0, param.hf],...
        'R',[0,0,0]... % z-axis is the normal axis, so no rotation required
        );

    fric_coef.mu = param.mu;
    fric_coef.gamma = param.gamma;

    geometry.la = param.wf/2;
    geometry.lb = param.wf/2;
    geometry.La = 0;
    geometry.Lb = param.lh+ param.lt;

end


function YLim= visutil_selectYLim(h, varargin)

props = {'Policy',          'auto'          '!CHAR(auto tightest tight)';
         'TightenBorder'     0.03        	'DOUBLE';
         'Symmetrize'       0               '!BOOL';
         'SetLim'           1               '!BOOL';
         };

if nargin==0,
  YLim= props; return
end

opt= opt_proplistToStruct(varargin{:});
[opt, isdefault]= opt_setDefaults(opt, props);
opt_checkProplist(opt, props);
misc_checkType(h,'!GRAPHICS');

switch(opt.Policy),
 case 'auto',
  YLim= get(h, 'YLim');
 case 'tightest',
  visutil_backaxes(h);
  axis('tight');
  YLim= get(h, 'YLim');
 case 'tight',
  visutil_backaxes(h);
  axis('tight');
  yl= get(h, 'YLim');
  % add border not to make it too tight:
  yl= yl + [-1 1]*opt.TightenBorder*diff(yl);
  % determine nicer limits
  dig= floor(log10(diff(yl)));
  if diff(yl)>1,
    dig= max(1, dig);
  end
  YLim= [util_trunc(yl(1),-dig+1,'floor') util_trunc(yl(2),-dig+1,'ceil')];
end

if opt.Symmetrize,
  ma= max(abs(YLim));
  YLim= [-ma ma];
end

if opt.SetLim,
  set(h, 'YLim',YLim);
end

if nargout==0,
  clear YLim;
end


function z = GetVolt()
%
%
w = 0 + 4*randn(1,1);
z = 14.4 + w;


function planC = divideStructureAntPostLeftRight(structNum, planC)
% function divideStructureAntPostLeftRight(structNum, planC)
%
% APA, 08/23/2012

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

global stateS

indexS = planC{end};

scanNum = getStructureAssociatedScan(structNum,planC);

newStructNumT = length(planC{indexS.structures}) + 1;
newStructNumL = length(planC{indexS.structures}) + 2;
newStructNumB = length(planC{indexS.structures}) + 3;
newStructNumR = length(planC{indexS.structures}) + 4;

newStructTS = newCERRStructure(scanNum, planC, newStructNumT);
newStructLS = newCERRStructure(scanNum, planC, newStructNumL);
newStructBS = newCERRStructure(scanNum, planC, newStructNumB);
newStructRS = newCERRStructure(scanNum, planC, newStructNumR);

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

    % Calculate centroid for this slice
    [rasterSegments, planC, isError]    = getRasterSegments(structNum,planC);
    rasterIndices = find(rasterSegments(:,6) == slcNum);

    zValue = planC{indexS.scan}(scanNum).scanInfo(slcNum).zValue;

    if ~isempty(rasterIndices)

        maskM = rasterToMask(rasterSegments(rasterIndices,:), scanNum, planC);

        %Get r,c,s list of all points in mask.
        [rV,cV] = find(maskM);

        %Take the mean of all points... unweighted as this is a mask.
        rowCOM = mean(rV);
        colCOM = mean(cV);
        slcCOM = 1; % dummy value

        %Convert from rcs, to xyz coordinates.
        [xc,yc] = mtoxyz(rowCOM, colCOM, slcCOM, scanNum, planC, 'uniform');

        for segNum = 1:length(planC{indexS.structures}(structNum).contour(slcNum).segments)
            xV = planC{indexS.structures}(structNum).contour(slcNum).segments(segNum).points(:,1);
            yV = planC{indexS.structures}(structNum).contour(slcNum).segments(segNum).points(:,2);
            [xyTV,xyLV,xyBV,xyRV] = dividePolygon(xV,yV,xc,yc);
            newStructTS.contour(slcNum).segments(segNum).points = [xyTV xyTV(:,1).^0*zValue];
            newStructLS.contour(slcNum).segments(segNum).points = [xyLV  xyLV(:,1).^0*zValue];
            newStructBS.contour(slcNum).segments(segNum).points = [xyBV  xyBV(:,1).^0*zValue];
            newStructRS.contour(slcNum).segments(segNum).points = [xyRV  xyRV(:,1).^0*zValue];
        end

    else
        newStructTS.contour(slcNum).segments(1).points = [];
        newStructLS.contour(slcNum).segments(1).points = [];
        newStructBS.contour(slcNum).segments(1).points = [];
        newStructRS.contour(slcNum).segments(1).points = [];
    end

end

stateS.structsChanged = 1;

newStructLS.structureName = [planC{indexS.structures}(structNum).structureName '_RIGHT'];
newStructRS.structureName = [planC{indexS.structures}(structNum).structureName '_LEFT'];
patientPosition = planC{indexS.scan}.scanInfo(1).DICOMHeaders.PatientPosition;
if isequal(patientPosition,'HFP') || isequal(patientPosition,'FFP')
    newStructTS.structureName = [planC{indexS.structures}(structNum).structureName '_POSTERIOR'];
    newStructBS.structureName = [planC{indexS.structures}(structNum).structureName '_ANTERIOR'];
else
    newStructTS.structureName = [planC{indexS.structures}(structNum).structureName '_ANTERIOR'];
    newStructBS.structureName = [planC{indexS.structures}(structNum).structureName '_POSTERIOR'];
end
planC{indexS.structures} = dissimilarInsert(planC{indexS.structures}, newStructTS, newStructNumT);
planC{indexS.structures} = dissimilarInsert(planC{indexS.structures}, newStructLS, newStructNumL);
planC{indexS.structures} = dissimilarInsert(planC{indexS.structures}, newStructBS, newStructNumB);
planC{indexS.structures} = dissimilarInsert(planC{indexS.structures}, newStructRS, newStructNumR);

planC = getRasterSegs(planC, [newStructNumT newStructNumL newStructNumB newStructNumR]);
planC = updateStructureMatrices(planC, newStructNumT);
planC = updateStructureMatrices(planC, newStructNumL);
planC = updateStructureMatrices(planC, newStructNumB);
planC = updateStructureMatrices(planC, newStructNumR);

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


function t_opf_dc_ipopt(quiet)
%T_OPF_DC_MIPS  Tests for DC optimal power flow using MIPS solver.

%   MATPOWER
%   Copyright (c) 2004-2021, 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 nargin < 1
    quiet = 0;
end

num_tests = 43;

t_begin(num_tests, quiet);

[PQ, PV, REF, NONE, BUS_I, BUS_TYPE, PD, QD, GS, BS, BUS_AREA, VM, ...
    VA, BASE_KV, ZONE, VMAX, VMIN, LAM_P, LAM_Q, MU_VMAX, MU_VMIN] = idx_bus;
[GEN_BUS, PG, QG, QMAX, QMIN, VG, MBASE, GEN_STATUS, PMAX, PMIN, ...
    MU_PMAX, MU_PMIN, MU_QMAX, MU_QMIN, PC1, PC2, QC1MIN, QC1MAX, ...
    QC2MIN, QC2MAX, RAMP_AGC, RAMP_10, RAMP_30, RAMP_Q, APF] = idx_gen;
[F_BUS, T_BUS, BR_R, BR_X, BR_B, RATE_A, RATE_B, RATE_C, ...
    TAP, SHIFT, BR_STATUS, PF, QF, PT, QT, MU_SF, MU_ST, ...
    ANGMIN, ANGMAX, MU_ANGMIN, MU_ANGMAX] = idx_brch;

casefile = 't_case9_opf';
if quiet
    verbose = 0;
else
    verbose = 0;
end
if have_feature('octave')
    if have_feature('octave', 'vnum') >= 4
        file_in_path_warn_id = 'Octave:data-file-in-path';
    else
        file_in_path_warn_id = 'Octave:load-file-in-path';
    end
    s1 = warning('query', file_in_path_warn_id);
    warning('off', file_in_path_warn_id);
end

t0 = 'DC OPF (IPOPT): ';
mpopt = mpoption('out.all', 0, 'verbose', verbose);
mpopt = mpoption(mpopt, 'opf.dc.solver', 'IPOPT');

if have_feature('ipopt')
    %% set up indices
    ib_data     = [1:BUS_AREA BASE_KV:VMIN];
    ib_voltage  = [VM VA];
    ib_lam      = [LAM_P LAM_Q];
    ib_mu       = [MU_VMAX MU_VMIN];
    ig_data     = [GEN_BUS QMAX QMIN MBASE:APF];
    ig_disp     = [PG QG VG];
    ig_mu       = (MU_PMAX:MU_QMIN);
    ibr_data    = (1:ANGMAX);
    ibr_flow    = (PF:QT);
    ibr_mu      = [MU_SF MU_ST];
    ibr_angmu   = [MU_ANGMIN MU_ANGMAX];

    %% get solved DC power flow case from MAT-file
    load soln9_dcopf;       %% defines bus_soln, gen_soln, branch_soln, f_soln

    %% run OPF
    t = t0;
    [baseMVA, bus, gen, gencost, branch, f, success, et] = rundcopf(casefile, mpopt);
    t_ok(success, [t 'success']);
    t_is(f, f_soln, 3, [t 'f']);
    t_is(   bus(:,ib_data   ),    bus_soln(:,ib_data   ), 10, [t 'bus data']);
    t_is(   bus(:,ib_voltage),    bus_soln(:,ib_voltage),  3, [t 'bus voltage']);
    t_is(   bus(:,ib_lam    ),    bus_soln(:,ib_lam    ),  3, [t 'bus lambda']);
    t_is(   bus(:,ib_mu     ),    bus_soln(:,ib_mu     ),  2, [t 'bus mu']);
    t_is(   gen(:,ig_data   ),    gen_soln(:,ig_data   ), 10, [t 'gen data']);
    t_is(   gen(:,ig_disp   ),    gen_soln(:,ig_disp   ),  3, [t 'gen dispatch']);
    t_is(   gen(:,ig_mu     ),    gen_soln(:,ig_mu     ),  3, [t 'gen mu']);
    t_is(branch(:,ibr_data  ), branch_soln(:,ibr_data  ), 10, [t 'branch data']);
    t_is(branch(:,ibr_flow  ), branch_soln(:,ibr_flow  ),  3, [t 'branch flow']);
    t_is(branch(:,ibr_mu    ), branch_soln(:,ibr_mu    ),  2, [t 'branch mu']);

    %%-----  test OPF with angle difference limits  -----
    t = [t0 'w/angle diff lims : '];
    mpc = loadcase(casefile);
    mpc.branch(4, ANGMAX) = 3;
    mpc.branch(7, ANGMIN) = -4.5;
    r = rundcopf(mpc, mpopt);
    [bus, gen, branch, f, success] = deal(r.bus, r.gen, r.branch, r.f, r.success);
    t_ok(success, [t 'success']);
    t_is(   f, 6456.7213, 3, [t 'f']);
    t_is(   bus(:,ib_data   ),    bus_soln(:,ib_data   ), 10, [t 'bus data']);
    t_is(   gen(:,ig_data   ),    gen_soln(:,ig_data   ), 10, [t 'gen data']);
    t_is(   gen(:,PG        ),    [99.98497;89.35133;125.66371], 4, [t 'gen dispatch']);
    t_is(branch(:,ibr_data  ), mpc.branch(:,ibr_data   ), 10, [t 'branch data']);
    e = zeros(size(branch, 1), 1);
    e(4) = 297.83776;
    e(7) = -26.94788;
    t_is(branch(:,MU_ANGMAX )-branch(:,MU_ANGMIN ), e, 4, [t 'branch ang diff mu']);

    t = [t0 'w/ignored angle diff lims : '];
    mpopt1 = mpoption(mpopt, 'opf.ignore_angle_lim', 1);
    r = rundcopf(mpc, mpopt1);
    [bus, gen, branch, f, success] = deal(r.bus, r.gen, r.branch, r.f, r.success);
    t_ok(success, [t 'success']);
    t_is(f, f_soln, 3, [t 'f']);
    t_is(   bus(:,ib_data   ),    bus_soln(:,ib_data   ), 10, [t 'bus data']);
    t_is(   bus(:,ib_voltage),    bus_soln(:,ib_voltage),  3, [t 'bus voltage']);
    t_is(   bus(:,ib_lam    ),    bus_soln(:,ib_lam    ),  3, [t 'bus lambda']);
    t_is(   bus(:,ib_mu     ),    bus_soln(:,ib_mu     ),  2, [t 'bus mu']);
    t_is(   gen(:,ig_data   ),    gen_soln(:,ig_data   ), 10, [t 'gen data']);
    t_is(   gen(:,ig_disp   ),    gen_soln(:,ig_disp   ),  3, [t 'gen dispatch']);
    t_is(   gen(:,ig_mu     ),    gen_soln(:,ig_mu     ),  3, [t 'gen mu']);
    t_is(branch(:,ibr_data  ), mpc.branch(:,ibr_data   ), 10, [t 'branch data']);
    t_is(branch(:,ibr_flow  ), branch_soln(:,ibr_flow  ),  3, [t 'branch flow']);
    t_is(branch(:,ibr_mu    ), branch_soln(:,ibr_mu    ),  2, [t 'branch mu']);

    %%-----  run OPF with extra linear user constraints & costs  -----
    %% two new z variables
    %%      0 <= z1, P3 - P1 <= z1
    %%      0 <= z2, P3 - P2 <= z2
    %% with A and N sized for DC opf
    mpc = loadcase(casefile);
    mpc.A = sparse([1;1;1;2;2;2],[10;12;13;12;11;14],[-1;1;-1;1;-1;-1],2,14);
    mpc.u = [0; 0];
    mpc.l = [-Inf; -Inf];
    mpc.zl = [0; 0];

    mpc.N = sparse([1;2], [13;14], [1;1], 2, 14);   %% new z variables only
    mpc.fparm = ones(2,1) * [1 0 0 1];              %% w = r = z
    mpc.H = sparse(2,2);                            %% no quadratic term
    mpc.Cw = [1000;1];

    t = [t0 'w/extra constraints & costs 1 : '];
    [r, success] = rundcopf(mpc, mpopt);
    t_ok(success, [t 'success']);
    t_is(r.gen(1, PG), 116.15974, 4, [t 'Pg1 = 116.15974']);
    t_is(r.gen(3, PG), 116.15974, 4, [t 'Pg3 = 116.15974']);
    t_is(r.var.val.z, [0; 0.3348], 4, [t 'user vars']);
    t_is(r.cost.usr, 0.3348, 3, [t 'user costs']);

    %% with A and N sized for AC opf
    mpc = loadcase(casefile);
    mpc.A = sparse([1;1;1;2;2;2],[19;21;25;21;20;26],[-1;1;-1;1;-1;-1],2,26);
    mpc.u = [0; 0];
    mpc.l = [-Inf; -Inf];
    mpc.zl = [0; 0];

    mpc.N = sparse([1;2], [25;26], [1;1], 2, 26);   %% new z variables only
    mpc.fparm = ones(2,1) * [1 0 0 1];              %% w = r = z
    mpc.H = sparse(2,2);                            %% no quadratic term
    mpc.Cw = [1000;1];

    t = [t0 'w/extra constraints & costs 2 : '];
    [r, success] = rundcopf(mpc, mpopt);
    t_ok(success, [t 'success']);
    t_is(r.gen(1, PG), 116.15974, 4, [t 'Pg1 = 116.15974']);
    t_is(r.gen(3, PG), 116.15974, 4, [t 'Pg3 = 116.15974']);
    t_is(r.var.val.z, [0; 0.3348], 4, [t 'user vars']);
    t_is(r.cost.usr, 0.3348, 3, [t 'user costs']);

    t = [t0 'infeasible : '];
    %% with A and N sized for DC opf
    mpc = loadcase(casefile);
    mpc.A = sparse([1;1], [10;11], [1;1], 1, 14);   %% Pg1 + Pg2
    mpc.u = Inf;
    mpc.l = 600;
    [r, success] = rundcopf(mpc, mpopt);
    t_ok(~success, [t 'no success']);

    %% OPF with all buses isolated
    t = [t0 'all buses isolated : '];
    mpc = loadcase(casefile);
    mpc.bus(:, BUS_TYPE) = NONE;
    try
        r = rundcopf(mpc, mpopt);
        t_is(r.success, 0, 12, [t 'success = 0']);
    catch
        t_ok(0, [t 'unexpected fatal error']);
    end
else
    t_skip(num_tests, 'IPOPT not available');
end

if have_feature('octave')
    warning(s1.state, file_in_path_warn_id);
end

t_end;


function test_suite=test_meeg_chan_neighborhood()
% tests for cosmo_meeg_chan_neighborhood
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #
    try % assignment of 'localfunctions' is necessary in Matlab >= 2016
        test_functions=localfunctions();
    catch % no problem; early Matlab versions can use initTestSuite fine
    end
    initTestSuite;

function test_neighbors()
    % note: this tests assumes that meeg_chan_neighbors works properly
    if cosmo_skip_test_if_no_external('fieldtrip')
        return;
    end

    % only test every test_step-th channel
    test_step=10;

    ds=cosmo_synthetic_dataset('type','meeg','size','big');
    msk=ds.fa.chan<10 | mod(ds.fa.chan,5)==0 | ds.fa.chan>250;
    imsk=find(msk);
    rp=randperm(numel(imsk));
    ds=cosmo_slice(ds,imsk(rp),2);
    ds=cosmo_dim_prune(ds);
    n=numel(ds.a.fdim.values{1});
    ds.a.fdim.values{1}=ds.a.fdim.values{1}(randperm(n));

    nbrs=cosmo_meeg_chan_neighbors(ds,'chantype','meg_planar','radius',0);
    nh=cosmo_meeg_chan_neighborhood(ds,nbrs);

    assertEqual(nh.a.fdim.values,{{nbrs.label}});
    assertEqual(nh.a.fdim.labels,{'chan'});

    n=numel(nh.neighbors);
    for k=1:test_step:n
        idx=nh.neighbors{k};
        chan_label=ds.a.fdim.values{1}(ds.fa.chan(idx));
        assert(all(cosmo_match(chan_label,nh.a.fdim.values{1}{k})));
    end

    % test correspondence with neighbors
    args={'chantype','all','delaunay',true};
    nbrs=cosmo_meeg_chan_neighbors(ds,args{:});
    nh=cosmo_meeg_chan_neighborhood(ds,args{:});
    nh2=cosmo_meeg_chan_neighborhood(ds,nbrs);
    assertEqual(nh,nh2);

    ds_label=ds.a.fdim.values{1};

    n=numel(nh.neighbors);
    for k=1:test_step:n
        idx=nh.neighbors{k};

        chan_label=ds.a.fdim.values{1}(ds.fa.chan(idx));

        % ensure both arguments for intersect are column vectors,
        % because Octave behaves differently than Matlab
        assertEqual(intersect(ds_label(:),nbrs(k).neighblabel(:)),...
                        unique(chan_label'));

    end

    % try with dataset labels
    args={'chantype','all','count',5,'label','dataset'};
    nbrs=cosmo_meeg_chan_neighbors(ds,args{:});
    nh=cosmo_meeg_chan_neighborhood(ds,nbrs);
    n=numel(nh.neighbors);
    assertEqual(n, numel(ds.a.fdim.values{1}));
    assertEqual(1:n, nh.fa.chan);
    ds_label=ds.a.fdim.values{1};
    nh_label=nh.a.fdim.values{1};

    for k=1:test_step:n
        i=find(cosmo_match(nh_label,ds_label{k}));
        assertEqual(nbrs(i).label,ds_label{k});

        overlap=cosmo_overlap({ds_label(ds.fa.chan(nh.neighbors{i}))},...
                                {nbrs(i).neighblabel});
        assertEqual(overlap,1);
    end

    % test for number of channels
    nbrs=cosmo_meeg_chan_neighbors(ds,'count',5,...
                        'chantype','meg_combined_from_planar');
    nh=cosmo_meeg_chan_neighborhood(ds,nbrs);
    assertEqual(numel(nh.neighbors),102);
    h=cellfun(@numel,nh.neighbors)/7;
    assert(all(h>=5 & h<=10));
    mh=mean(h);
    assert(mh>6 && mh<9);

    nbrs=cosmo_meeg_chan_neighbors(ds,'count',5,'chantype','meg_planar');
    nh=cosmo_meeg_chan_neighborhood(ds,nbrs);
    assertEqual(numel(nh.neighbors),204);
    h=cellfun(@numel,nh.neighbors)/7;
    assert(all(h==5));

    % test tiny dataset
    ds=cosmo_synthetic_dataset('type','meeg');
    opt=struct();
    opt.delaunay=true;
    opt.label='dataset';
    opt.chantype='meg_axial';

    nh=cosmo_meeg_chan_neighborhood(ds,opt);
    assertEqual(nh.neighbors,{[1 4]});

    opt.chantype='meg_planar';
    nh=cosmo_meeg_chan_neighborhood(ds,opt);
    assertEqual(nh.neighbors,{[2 5 3 6]; [2 5 3 6]});


function output = callvsdp(interfacedata)

% Retrieve needed data
options = interfacedata.options;
F_struc = interfacedata.F_struc;
c       = interfacedata.c;
K       = interfacedata.K;
x0      = interfacedata.x0;
ub      = interfacedata.ub;
lb      = interfacedata.lb;

% Bounded variables converted to constraints
if ~isempty(ub)
    [F_struc,K] = addStructureBounds(F_struc,K,ub,lb);
end

% Convert from internal (sedumi-like) format to VSDPs sdpt3-like format
[blk,A,C,b]=sedumi2vsdp(F_struc(:,1),F_struc(:,2:end),c,K);

if options.savedebug
    ops = options.sdpt3;
    save sdpt3debug blk A C b ops -v6
end

% Solver to be used in VSDP
options.vsdp.model = interfacedata;

solvertime = tic;
[objt,Xt,yt,Zt,info] = mysdps_yalmip(blk,A',C,b,options);

% Compute rigorous lower bound (default behaviour)
if options.vsdp.verifiedlower
    [fL, Y, dL] = vsdplow_yalmip(blk,A',C,b,Xt,yt,Zt,[],options);
    if isnan(Y)
        info(1) = 11;
    end
    %[fL, Y, dL] = vsdplow(blk,A,C,b,Xt,yt,Zt)
else
    Y = [];
    fL = [];
    dL = [];
end

% Compute rigorous lower bound
if options.vsdp.verifiedupper
    %[fL, Y, dL] = vsdplow_yalmip(blk,A,C,b,[],[],[],[],options)
    % Now compute rigorous lower bound
    [fU, X, lb] = vsdpup_yalmip(blk,A',C,b,Xt,yt,Zt,[],options);
    %[fU, X, lb] = vsdpup(blk,A,C,b,Xt,yt,Zt);
else
    fU = [];
    X = [];
    lb = [];
end

solvertime = toc(solvertime);

Dual = [];
Slack = [];
top = 1;
if K.f>0
    Dual = [Dual;Xt{top}(:)];
    Slack = [Slack;Zt{top}(:)];
    top = top+1;
end
if K.l>0
    Dual = [Dual;[Xt{1:K.l}]'];
    Slack = [Slack;[Zt{1:K.l}]'];
    top = top + K.l;
end
if any(K.q)
    Dual = [Dual;Xt{top}(:)];
    Slack = [Slack;Zt{top}(:)];
    top = top + 1;
end
if any(K.s)
    for i = 1:length(K.s)
        Dual = [Dual;Xt{top+i-1}(:)];
        Slack = [Slack;Zt{top+i-1}(:)];
    end
end

if ~isempty(Y) & ~isnan(Y)
    Primal = Y;  % Primal variable in YALMIP
else
    Primal = yt;  % Primal variable in YALMIP
end

% Convert error code
switch info(1)
    case 0
        problem = 0; % No problems detected
    case {-1,-5}
        problem = 5; % Lack of progress
    case {-2,-3,-4,-7}
        problem = 4; % Numerical problems
    case -6
        problem = 3; % Maximum iterations exceeded
    case -10
        problem = 7; % YALMIP sent incorrect input to solver
    case 1
        problem = 2; % Dual feasibility
    case 2
        problem = 1; % Primal infeasibility
    case 11
        problem = 11;
    otherwise
        problem = -1; % Unknown error
end

% always save output
if options.savesolveroutput
    solveroutput.objt = objt;
    solveroutput.Xt = Xt;
    solveroutput.yt = yt;
    solveroutput.Zt = Zt;
    solveroutput.fL = fL;
    solveroutput.Y  = Y;
    solveroutput.dL  = dL;
    solveroutput.fU = fU;
    solveroutput.X  = X;
    solveroutput.lb  = lb;
    solveroutput.info = info;
 else
    solveroutput = [];
end

if options.savesolverinput
    solverinput.blk = blk;
    solverinput.A   = A;
    solverinput.C   = C;
    solverinput.b   = b;
    solverinput.options   = options.sdpt3;
else
    solverinput = [];
end

% Standard interface
output = createOutputStructure(Primal,Dual,Slack,problem,interfacedata.solver.tag,solverinput,solveroutput,solvertime);

function [blk,A,C,b]=sedumi2vsdp(Cin,Ain,c,K);

C = {};
A = {};
b  = -c;
blk = {};
top = 1;
k = 1;

if any(K.f)
    C{k,1} = Cin(top:top+K.f-1);
%    A{k} = -Ain(top:top+K.f-1,:);
    for j = 1:length(c)
        A{j,k} = -Ain(top:top+K.f-1,j);
    end
    blk{k,1} = 'u';
    blk{k,2} = K.f;
    top = top + K.f;
    k = k + 1;
end

if any(K.l)
    K.s = [repmat(1,1,K.l) K.s];
    K.s(K.s == 0) = [];
%     C{k,1} = Cin(top:top+K.l-1);
% %    A{k} = -Ain(top:top+K.l-1,:);
%     for j = 1:length(c)
%         A{j,k} = -Ain(top:top+K.l-1,j);
%     end
%     blk{k,1} = 'l';
%     blk{k,2} = K.l;
%     top = top + K.l;
%     k = k + 1;
end

if any(K.s)
    for i = 1:length(K.s)
        C{k,1} = reshape(Cin(top:top+K.s(i)^2-1),K.s(i),K.s(i));
        for j = 1:length(c)
            A{j,k} = reshape(-Ain(top:top+K.s(i)^2-1,j),K.s(i),K.s(i));
        end
        blk{k,1} = 's';
        blk{k,2} = K.s(i);
        k = k + 1;
        top = top + K.s(i)^2;
    end
end

A = A';