function out = spm_groupwise_ls(Nii, output, prec, w_settings, b_settings, s_settings, ord)
% Groupwise registration via least squares
% FORMAT out = spm_groupwise_ls(Nii, output, prec, w_settings, b_settings, s_settings, ord)
% Nii    - a nifti object for two or more image volumes.
% output - a cell array of output options (as scharacter strings).
%          'avg'   - return average in out.avg
%          'wavg'  - write average to disk, and return filename in out.avg
%          'def'   - return mappings from average to individuals in out.def
%          'wdef'  - write mappings to disk, and return filename in out.def
%          'div'   - return divergence of initial velocities in out.div
%          'wdiv'  - write divergence images to disk and return filename
%          'jac'   - return Jacobian determinant maps in out.jac
%          'wjac'  - write Jacobians to disk and return filename
%          'vel'   - return initial velocities
%          'wvel'  - write velocities to disk and return filename
%          'rigid' - return rigid-body transforms
%
% prec       - reciprocal of noise variance on images.
% w_swttings - regularisation settings for warping.
% b_settings - regularisation settings for nonuniformity field.
% s_settings - number of time steps for geodesic shooting.
% ord        - degree of B-spline interpolation used for sampline images.
%
% This function requires an obscene amount of memory.  If it crashes
% with an "Out of memory" error, then do not be too surprised.
%__________________________________________________________________________

% John Ashburner
% Copyright (C) 2012-2022 Wellcome Centre for Human Neuroimaging


% Get handles to NIfTI data
%--------------------------------------------------------------------------
if ~isa(Nii,'nifti')
    if isa(Nii,'char')
        Nii = nifti(Nii);
    else
        error('Unrecognised NIfTI images');
    end
end

% Specify default settings
%--------------------------------------------------------------------------
if nargin<3, prec       = NaN; end
if nargin<4, w_settings = [0 1 80 20 80]; end
if nargin<5, b_settings = [0 0 1e6]; end
if nargin<6, s_settings = 6; end
if nargin<7, ord        = [3 3 3 0 0 0]; end

% If settings are not subject-specific, then generate
%--------------------------------------------------------------------------
if size(w_settings,1)==1, w_settings = repmat(w_settings,numel(Nii),1); end
if size(s_settings,1)==1, s_settings = repmat(s_settings,numel(Nii),1); end
if size(b_settings,1)==1, b_settings = repmat(b_settings,numel(Nii),1); end
if numel(prec)       ==1, prec       = repmat(prec,1,numel(Nii));       end

% Determine noise estimates when unknown
for i=find(~isfinite(prec))
    prec(i) = 1/spm_noise_estimate(Nii(i)).^2;
end

% Basis functions for algebra of rigid-body transform
%--------------------------------------------------------------------------
B = se3_basis;

% Set boundary conditions 
%--------------------------------------------------------------------------
spm_field('boundary',1); % Bias correction - Neumann
spm_diffeo('boundary',0);     % Diffeomorphism  - circulant

% Computations for figuring out how many grid levels are likely to work
%--------------------------------------------------------------------------
d = [0 0 0];
for i=1:numel(Nii)
    dm = [size(Nii(i).dat) 1];
    d  = max(d, dm(1:3));
end
%d = prod(d-2)^(1/3);
d  = min(d);

% Specify highest resolution data
%--------------------------------------------------------------------------
clear pyramid
pyramid(max(ceil(log2(d)-log2(4)),1)) = struct('d',[1 1 1],'mat',eye(4),'img',[]);
for i=numel(Nii):-1:1
    pyramid(1).img(i).f   = single(Nii(i).dat(:,:,:,1,1));
    pyramid(1).img(i).mat = Nii(i).mat;
end

% Generate sucessively lower resolution versions
%--------------------------------------------------------------------------
for level = 2:numel(pyramid)
    for i=numel(Nii):-1:1
        pyramid(level).img(i).f   = spm_diffeo('restrict',pyramid(level-1).img(i).f);
        pyramid(level).img(i).f(~isfinite(pyramid(level).img(i).f)) = 0;
        s1 = [size(pyramid(level-1).img(i).f) 1];
        s2 = [size(pyramid(level  ).img(i).f) 1];
        s  = s1(1:3)./s2(1:3);
        pyramid(level).img(i).mat = pyramid(level-1).img(i).mat*[diag(s), (1-s(:))*0.5; 0 0 0 1];
        clear s1 s2
    end
end

% Convert all image data into B-spline coefficients (for interpolation)
%--------------------------------------------------------------------------
for level=1:numel(pyramid)
    for i=1:numel(Nii)
        pyramid(level).img(i).f = spm_diffeo('bsplinc',pyramid(level).img(i).f,ord);
    end
end

% Adjust precision for number of subjects
%--------------------------------------------------------------------------
%nscan = numel(pyramid(1).img);
%prec  = prec*(nscan-1)/nscan;

% Stuff for figuring out the orientation, dimensions etc of the highest resolution template
%--------------------------------------------------------------------------
Mat0 = cat(3,pyramid(1).img.mat);
dims = zeros(numel(Nii),3);
for i=1:size(dims,1)
    dims(i,:) = Nii(i).dat.dim(1:3);
end
[pyramid(1).mat,pyramid(1).d] = spm_compute_avg_mat(Mat0,dims);
pyramid(1).sc   = abs(det(pyramid(1).mat(1:3,1:3)));                  % FIX THIS FOR NEXT MAJOR RELEASE
pyramid(1).prec = prec;

% Figure out template info for each sucessively lower resolution version
%--------------------------------------------------------------------------
for level=2:numel(pyramid)
    pyramid(level).d    = ceil(pyramid(level-1).d/2);
    s                   = pyramid(level-1).d./pyramid(level).d;
    pyramid(level).mat  = pyramid(level-1).mat*[diag(s), (1-s(:))*0.5; 0 0 0 1];

    % Relative scaling of regularisation
    pyramid(level).sc   = abs(det(pyramid(level).mat(1:3,1:3)));      % FIX THIS FOR NEXT MAJOR RELEASE
    pyramid(level).prec = prec*sqrt(pyramid(level).sc/pyramid(1).sc); % Note that the sqrt is ad hoc
end


nlevels = numel(pyramid);

for level=nlevels:-1:1 % Loop over resolutions, starting with the lowest

    % Collect data
    %----------------------------------------------------------------------
    img       = pyramid(level).img;
    M_avg     = pyramid(level).mat;
    d         = pyramid(level).d;
    vx        = sqrt(sum(pyramid(level).mat(1:3,1:3).^2));
    sc        = pyramid(level).sc;
    prec      = pyramid(level).prec;

    if level==nlevels
        % If lowest resolution, initialise parameter estimates to zero
        %------------------------------------------------------------------
        clear param
        bias_est = zeros(numel(Nii),1);
        for i=numel(Nii):-1:1
            bias_est(i) = log(mean(mean(mean(img(i).f))));
        end
        bias_est = bias_est - mean(bias_est);
        for i=numel(Nii):-1:1
            param(i) = struct('R',   eye(4), 'r', zeros(6,1),...
                              'bias',[],     'eb',0,...
                              'v0',  [],     'ev',0, 'y',[], 'J',[],...
                              's2',  1,      'ss',1);

            if all(isfinite(b_settings(i,:)))
                param(i).bias = zeros(size(img(i).f),'single')+bias_est(i);
            end

            if all(isfinite(w_settings(i,:)))
                param(i).v0   = zeros([d 3],'single');
                param(i).y    = identity(d);
                param(i).J    = repmat(reshape(eye(3,'single'),[1 1 1 3 3]),[d(1:3),1,1]);
            end

        end
    else
        % Initialise parameter estimates by prolongation of previous lower resolution versions.
        %------------------------------------------------------------------
        for i=1:numel(Nii)

            if all(isfinite(b_settings(i,:)))
                vxi           = sqrt(sum(img(i).mat(1:3,1:3).^2));
                spm_diffeo('boundary',1);
                param(i).bias = spm_diffeo('resize',param(i).bias,size(img(i).f));
                spm_diffeo('boundary',0);
                bmom          = spm_field('vel2mom', param(i).bias, [vxi b_settings(i,:)*sc]);
                param(i).eb   = sum(bmom(:).*param(i).bias(:));
                clear bmom
            else
                param(i).bias = [];
                param(i).eb   = 0;
            end

            if all(isfinite(w_settings(i,:)))
                param(i).v0   = spm_diffeo('resize',param(i).v0,d);
                for i1=1:3
                    s = pyramid(level).d(i1)/pyramid(level+1).d(i1);
                    param(i).v0(:,:,:,i1) = param(i).v0(:,:,:,i1)*s;
                end
                m0          = spm_diffeo('vel2mom',param(i).v0,[vx w_settings(i,:)*sc]);
                param(i).ev = sum(sum(sum(sum(m0.*param(i).v0))));
                clear m0
            else
                param(i).v0 = [];
                param(i).ev = 0;
                param(i).y  = [];
                param(i).J  = [];
            end
        end

        % Remove lower resolution versions that are no longer needed
        pyramid = pyramid(1:(end-1));
    end

    spm_plot_convergence('Clear');
    spm_plot_convergence('Init',['Optimising (level ' num2str(level) ')'],'Objective Function','Step');
    for iter=1:(2*2^(level-1)+1) % Use more iterations at lower resolutions (its faster, so may as well)


        % Compute deformations from initial velocities
        %------------------------------------------------------------------
        for i=1:numel(param)
            if all(isfinite(w_settings(i,:)))
                [param(i).y,param(i).J] = spm_shoot3d(param(i).v0,[vx w_settings(i,:)*sc],s_settings(i,:));
            end
        end

        if true
            % Rigid-body
            %==============================================================
            % Recompute template data (with gradients)
            %--------------------------------------------------------------
            [mu,ss,nvox,D] = compute_mean(pyramid(level), param, ord);
            % for i=1:numel(param), fprintf('  %12.5g %12.5g %12.5g', prec(i)*ss(i), param(i).eb, param(i).ev); end; fprintf('  0\n');

            % Compute objective function (approximately)
            %--------------------------------------------------------------
            ll = 0;
            for i=1:numel(param)
                param(i).ss = ss(i);
                ll          = ll - 0.5*prec(i)*param(i).ss - 0.5*param(i).eb - 0.5*param(i).ev;
            end
            spm_plot_convergence('set',ll);

            for i=1:numel(img)
                % Gauss-Newton update of logs of rigid-body matrices
                %----------------------------------------------------------
                [R,dR]        = spm_dexpm(param(i).r,B);
                M             = img(i).mat\R*M_avg;
                dtM           = abs(det(M(1:3,1:3)));
                [x1a,x2a,x3a] = ndgrid(1:d(1),1:d(2),1);

                Hess = zeros(12);
                gra  = zeros(12,1);
                for m=1:d(3)
                    if all(isfinite(w_settings(i,:)))
                        dt    = spm_diffeo('det',param(i).J(:,:,m,:,:));
                        y     = transform_warp(M,param(i).y(:,:,m,:));
                    else
                        dt    = ones(d(1:2),'single');
                        y     = zeros([d(1:2) 1 3],'single');
                        [y(:,:,1),y(:,:,2),y(:,:,3)] = ndgrid(single(1:d(1)),single(1:d(2)),single(m));
                        y     = transform_warp(M,y);
                    end

                    f     = spm_diffeo('bsplins',img(i).f,y,ord);

                    if all(isfinite(b_settings(i,:)))
                        ebias = exp(spm_diffeo('pullc',param(i).bias,y));
                    else
                        ebias = ones(size(f),'single');
                    end

                    b     = f-mu(:,:,m).*ebias;

                    msk   = isfinite(b);
                    ebias = ebias(msk);
                    b     = b(msk);

                    if ~all(isfinite(w_settings(i,:)))
                        % No need to account for the nonlinear warps when estimating
                        % the rigid body transform.
                        x1 = x1a(msk);
                        x2 = x2a(msk);
                        x3 = x3a(msk)*m;
                        d1 = D{1}(:,:,m);
                        d2 = D{2}(:,:,m);
                        d3 = D{3}(:,:,m);

                    else
                        % The rigid-body transform should register the nonlinearly warped
                        % template to match the individual.


                        % Positions where the template voxels are mapped to via the nonlinear warp.
                        x1 = param(i).y(:,:,m,1); x1 = x1(msk);
                        x2 = param(i).y(:,:,m,2); x2 = x2(msk);
                        x3 = param(i).y(:,:,m,3); x3 = x3(msk);


                        % Gradients of nonlinear warped template, warped back to its original space.
                        % Multiply by transpose of inverse of Jacobian determinants.
                        % First, get the Jacobians of the nonlinear warp.
                        J           = reshape(param(i).J(:,:,m,:,:),[d(1:2) 3 3]);

                        % Reciprocal of the determinants.  Note that for the gradients (gra)
                        % idt cancels out with dt. For the Hessians (Hess), it doesn't.
                        idt         = 1./max(dt,0.001);

                        % Compute cofactors of Jacobians, where cJ = det(J)*inv(J) 
                        cJ          = zeros(size(J),'single');
                        cJ(:,:,1,1) = J(:,:,2,2).*J(:,:,3,3) - J(:,:,2,3).*J(:,:,3,2);
                        cJ(:,:,1,2) = J(:,:,1,3).*J(:,:,3,2) - J(:,:,1,2).*J(:,:,3,3);
                        cJ(:,:,1,3) = J(:,:,1,2).*J(:,:,2,3) - J(:,:,1,3).*J(:,:,2,2);

                        cJ(:,:,2,1) = J(:,:,2,3).*J(:,:,3,1) - J(:,:,2,1).*J(:,:,3,3);
                        cJ(:,:,2,2) = J(:,:,1,1).*J(:,:,3,3) - J(:,:,1,3).*J(:,:,3,1);
                        cJ(:,:,2,3) = J(:,:,1,3).*J(:,:,2,1) - J(:,:,1,1).*J(:,:,2,3);

                        cJ(:,:,3,1) = J(:,:,2,1).*J(:,:,3,2) - J(:,:,2,2).*J(:,:,3,1);
                        cJ(:,:,3,2) = J(:,:,1,2).*J(:,:,3,1) - J(:,:,1,1).*J(:,:,3,2);
                        cJ(:,:,3,3) = J(:,:,1,1).*J(:,:,2,2) - J(:,:,1,2).*J(:,:,2,1);

                        % Premultiply gradients by transpose of inverse of J
                        d1 = idt.*(cJ(:,:,1,1).*D{1}(:,:,m) + cJ(:,:,2,1).*D{2}(:,:,m) + cJ(:,:,3,1).*D{3}(:,:,m));
                        d2 = idt.*(cJ(:,:,1,2).*D{1}(:,:,m) + cJ(:,:,2,2).*D{2}(:,:,m) + cJ(:,:,3,2).*D{3}(:,:,m));
                        d3 = idt.*(cJ(:,:,1,3).*D{1}(:,:,m) + cJ(:,:,2,3).*D{2}(:,:,m) + cJ(:,:,3,3).*D{3}(:,:,m));

                        clear idt cJ
                    end

                    dt = dt(msk);
                    d1 = d1(msk).*ebias;
                    d2 = d2(msk).*ebias;
                    d3 = d3(msk).*ebias;

                    % Derivatives w.r.t. an affine transform
                    A     = [x1(:).*d1(:) x1(:).*d2(:) x1(:).*d3(:) ...
                             x2(:).*d1(:) x2(:).*d2(:) x2(:).*d3(:) ...
                             x3(:).*d1(:) x3(:).*d2(:) x3(:).*d3(:) ...
                                    d1(:)        d2(:)        d3(:)];

                    if ~all(isfinite(w_settings(i,:)))
                        Hess  = Hess + dtM*double(A'*A);
                        gra   = gra  + dtM*double(A'*b);
                    else
                        Hess  = Hess + dtM*double(A'*bsxfun(@times,A,dt));
                        gra   = gra  + dtM*double(A'*(dt.*b));
                    end

                    clear dt y f ebias b msk x1 x2 d1 d2 d3 A 
                end

                % For converting from derivatives w.r.t. an affine transform
                % to derivatives w.r.t. the rigid-body transform parameters.
                dA = zeros(12,6);
                for m=1:6
                    tmp     = (R*M_avg)\dR(:,:,m)*M_avg;
                    dA(:,m) = reshape(tmp(1:3,:),12,1);
                end

                Hess       = dA'*Hess*dA*prec(i);
                gra        = dA'*gra*prec(i);
                param(i).r = param(i).r - Hess\gra;

                clear R dR M x1a x2a dA tmp Hess gra
            end
            clear mu D

            % Mean correct the rigid-body transforms and compute exponentials
            % Note that this gives us a Karcher mean.
            %--------------------------------------------------------------
            r_avg = mean(cat(2,param.r),2);
            for i=1:numel(param)
                param(i).r = param(i).r-r_avg;
                param(i).R = spm_dexpm(param(i).r,B);
            end
            clear r_avg
        end

        if any(all(isfinite(b_settings),2))
            % Bias field
            %==============================================================
            % Recompute template data
            %--------------------------------------------------------------
            [mu,ss] = compute_mean(pyramid(level), param, ord);
            % for i=1:numel(param), fprintf('  %12.5g %12.5g %12.5g', prec(i)*ss(i), param(i).eb, param(i).ev); end; fprintf('  1\n');

            % Compute objective function (approximately)
            %--------------------------------------------------------------
            ll = 0;
            for i=1:numel(param)
                param(i).ss = ss(i);
                ll          = ll - 0.5*prec(i)*param(i).ss - 0.5*param(i).eb - 0.5*param(i).ev;
            end
            spm_plot_convergence('set',ll);

            for i=1:numel(img)
                if all(isfinite(b_settings(i,:)))
                    % Gauss-Newton update of logs of bias field.
                    % Note that 1st and second derivatives are computed in
                    % template spaceand subsequently pushed back to native
                    % space for re-estimation.
                    %------------------------------------------------------
                    M    = img(i).mat\param(i).R*M_avg;
                    gra  = zeros(d,'single');
                    Hess = zeros(d,'single');

                    for m=1:d(3)
                        if all(isfinite(w_settings(i,:)))
                            dt    = spm_diffeo('det',param(i).J(:,:,m,:,:))*abs(det(M(1:3,1:3)));
                            y     = transform_warp(M,param(i).y(:,:,m,:));
                        else
                            dt    = ones(d(1:2),'single')*abs(det(M(1:3,1:3)));
                            y     = zeros([d(1:2) 1 3],'single');
                            [y(:,:,1),y(:,:,2),y(:,:,3)] = ndgrid(single(1:d(1)),single(1:d(2)),m);
                            y     = transform_warp(M,y);
                        end

                        f           = spm_diffeo('bsplins',img(i).f,y,ord);
                        ebias       = exp(spm_diffeo('pullc',param(i).bias,y));

                        msk         = isfinite(f) & isfinite(ebias);
                        smu         = mu(:,:,m).*ebias;
                        f(~msk)     = 0;
                        smu(~msk)   = 0;
                        gra(:,:,m)  = smu.*(smu-f).*dt*prec(i);
                        Hess(:,:,m) = smu.*smu.*dt*prec(i);

                        clear dt y f ebias msk smu
                    end

                    % Push derivatives to native space
                    %------------------------------------------------------
                    if all(isfinite(w_settings(i,:)))
                        y    = transform_warp(M,param(i).y);
                    else
                        y    = transform_warp(M,identity(d));
                    end
                    gra  = spm_diffeo('push',gra,y,size(param(i).bias));
                    Hess = spm_diffeo('push',Hess,y,size(param(i).bias));
                    clear y

                    vxi           = sqrt(sum(img(i).mat(1:3,1:3).^2));
                    gra           = gra + spm_field('vel2mom', param(i).bias, [vxi b_settings(i,:)*sc]);
                    param(i).bias = param(i).bias - spm_field(Hess,gra,[vxi b_settings(i,:)*sc 2 2]); % Gauss-Newton update
                    clear M gra Hess

                    % Compute part of objective function
                    %------------------------------------------------------
                    bmom          = spm_field('vel2mom', param(i).bias, [vxi b_settings(i,:)*sc]);
                    param(i).eb   = sum(bmom(:).*param(i).bias(:));
                    clear bmom vxi
                end
            end
            clear mu
        end


        if any(all(isfinite(w_settings),2))
            % Deformations
            %==============================================================
            % Recompute template data (with gradients)
            %-----------------------------------------------------------------------
            [mu,ss,nvox,D] = compute_mean(pyramid(level), param, ord);
            % for i=1:numel(param), fprintf('  %12.5g %12.5g %12.5g', prec(i)*ss(i), param(i).eb, param(i).ev); end; fprintf('  2\n');

            % Compute objective function (approximately)
            %--------------------------------------------------------------
            ll = 0;
            for i=1:numel(param)
                param(i).ss = ss(i);
                ll          = ll - 0.5*prec(i)*param(i).ss - 0.5*param(i).eb - 0.5*param(i).ev;
            end
            spm_plot_convergence('set',ll);

            for i=1:numel(img) % Update velocity for each image in turn
                if all(isfinite(w_settings(i,:)))
                    % Gauss-Newton update of velocity fields.
                    % These are parameterised in template space.
                    %------------------------------------------------------
                    gra  = zeros([d,3],'single');
                    Hess = zeros([d,6],'single');
                    M    = img(i).mat\param(i).R*M_avg;

                    for m=1:d(3)
                        dt    = spm_diffeo('det',param(i).J(:,:,m,:,:))*abs(det(M(1:3,1:3)));
                        y     = transform_warp(M,param(i).y(:,:,m,:));
                        f     = spm_diffeo('bsplins',img(i).f,y,ord);

                        if all(isfinite(b_settings(i,:)))
                            ebias = exp(spm_diffeo('pullc',param(i).bias,y));
                        else
                            ebias = ones(size(f),'single');
                        end

                        b             = f-mu(:,:,m).*ebias;
                        msk           = ~isfinite(b);
                        b(msk)        = 0;
                        dt(msk)       = 0;

                        d1            = D{1}(:,:,m).*ebias; % Spatial gradient of -b.
                        d2            = D{2}(:,:,m).*ebias;
                        d3            = D{3}(:,:,m).*ebias;

                        gra(:,:,m,1)  =  b.*d1.*dt; % 1st derivatives of objecive function
                        gra(:,:,m,2)  =  b.*d2.*dt; % w.r.t. velocity field.
                        gra(:,:,m,3)  =  b.*d3.*dt;

                        Hess(:,:,m,1) = d1.*d1.*dt; % 2nd derivatives (approximately) of
                        Hess(:,:,m,2) = d2.*d2.*dt; % objective function w.r.t. velocity.
                        Hess(:,:,m,3) = d3.*d3.*dt;
                        Hess(:,:,m,4) = d1.*d2.*dt;
                        Hess(:,:,m,5) = d1.*d3.*dt;
                        Hess(:,:,m,6) = d2.*d3.*dt;

                        clear dt y f ebias b msk d1 d2 d3
                    end

                    param(i).y = []; % No longer needed, so free up some memory.
                    param(i).J = [];

                    Hess        = Hess*prec(i);
                    gra         = gra*prec(i);

                    gra         = gra + spm_diffeo('vel2mom',param(i).v0,[vx w_settings(i,:)*sc]);
                    param(i).v0 = param(i).v0 - spm_diffeo('fmg',Hess, gra, [vx w_settings(i,:)*sc 2 2]); % Gauss-Newton

                    clear Hess gra
                end
            end
            clear mu D

            % If regularisation is the same for each image (apart from scaling), then adjust velocities.
            %--------------------------------------------------------------
            if sum(var(diag(sqrt(sum(w_settings.^2,2)))\w_settings,0,1)./(mean(w_settings,1).^2+eps)) < 1e-12
                wt      = sqrt(sum(w_settings.^2,2));
                wt      = wt/sum(wt);
                v0_mean = zeros(size(param(1).v0),'single');
                for i=1:numel(param)
                    v0_mean = v0_mean + wt(i)*param(i).v0;
                end
                for i=1:numel(param)
                    param(i).v0 = param(i).v0 - v0_mean;
                end
                clear v0_mean
            end

            % Compute part of objective function
            %--------------------------------------------------------------
            for i=1:numel(param)
                if all(isfinite(w_settings(i,:)))
                    m0          = spm_diffeo('vel2mom',param(i).v0,[vx w_settings(i,:)*sc]);
                    param(i).ev = sum(sum(sum(sum(m0.*param(i).v0))));
                    clear m0
                end
            end

        end
    end
end

% Figure out what needs to be saved
%--------------------------------------------------------------------------
need_avg = false;
need_vel = false;
need_def = false;
need_div = false;
need_jac = false;
need_mom = false;
need_bia = false;

if any(strcmp('avg',output)) || any(strcmp('wavg',output)), need_avg = true; end
if any(strcmp('vel',output)) || any(strcmp('wvel',output)), need_vel = true; end
if any(strcmp('div',output)) || any(strcmp('wdiv',output)), need_div = true; end
if any(strcmp('def',output)) || any(strcmp('wdef',output)), need_def = true; end
if any(strcmp('jac',output)) || any(strcmp('wjac',output)), need_jac = true; end
if any(strcmp('mom',output)) || any(strcmp('wmom',output)), need_mom = true; end
if any(strcmp('bia',output)) || any(strcmp('wbia',output)), need_bia = true; end

out = struct;
if need_avg || need_def || need_jac
    for i=numel(param):-1:1
        if all(isfinite(w_settings(i,:)))
            [param(i).y,param(i).J] = spm_shoot3d(param(i).v0,[vx w_settings(i,:)*sc],s_settings(i,:));
        end
    end
end

clear out
out.mat = M_avg;

if any(strcmp('rigid',output))
    out.rigid = {};
    for i=1:numel(param)
        out.rigid{i} = param(i).R;
    end
end


if need_avg || need_mom
    mu = compute_mean(pyramid(1), param, ord);
end

if need_avg
    if any(strcmp('wavg',output))
        [pth,nam]   = fileparts(Nii(1).dat.fname);
        nam         = fullfile(pth,['avg_' nam '.nii']);
        Nio         = nifti;
        Nio.dat     = file_array(nam,size(mu),'int16',0,max(max(mu(:))/32767,-min(mu(:))/32768),0);
        Nio.mat     = M_avg;
        Nio.mat0    = Nio.mat;
        Nio.mat_intent  = 'Aligned';
        Nio.mat0_intent = Nio.mat_intent;
        Nio.descrip = sprintf('Average of %d', numel(param));
        create(Nio);
        Nio.dat(:,:,:) = mu;
        out.avg        = nam;
    else
        out.avg        = mu;
    end
end

if need_mom
    out.mom = {};
    for i=1:numel(param)
        if all(isfinite(w_settings(i,:)))

            mom = zeros(d,'single');
            M   = img(i).mat\param(i).R*M_avg;

            for m=1:d(3)
                dt    = spm_diffeo('det',param(i).J(:,:,m,:,:));
                y     = transform_warp(M,param(i).y(:,:,m,:));
                f     = spm_diffeo('bsplins',img(i).f,y,ord);
                ebias = exp(spm_diffeo('pullc',param(i).bias,y));
                b     = (f-mu(:,:,m).*ebias).*ebias.*dt;
                b(~isfinite(b)) = 0;
                mom(:,:,m) = b;
                clear dt y f ebias b msk 
            end

            if any(strcmp('wmom',output))
                [pth,nam]   = fileparts(Nii(i).dat.fname);
                nam         = fullfile(pth,['a_' nam '.nii']);
                Nio         = nifti;
                Nio.dat     = file_array(nam,d,'float32',0,1,0);
                Nio.mat     = M_avg;
                Nio.mat0    = Nio.mat;
                Nio.mat_intent  = 'Aligned';
                Nio.mat0_intent = Nio.mat_intent;

                Nio.descrip = sprintf('Scalar Mom (%.3g %.3g %.3g %.3g %.3g) (%d)',w_settings(i,:)*sc,s_settings(i,1));
                create(Nio);
                Nio.dat(:,:,:,1,1) = mom;
                out.mom{i}         = nam;
            else
                out.mom{i}         = mom;
            end
            clear mom
        end
    end
end

clear mu;

if need_bia
    out.bia = {};
    for i=1:numel(param)
        if all(isfinite(b_settings(i,:)))

            if any(strcmp('wbia',output))
                [pth,nam]   = fileparts(Nii(i).dat.fname);
                nam         = fullfile(pth,['BiasField_' nam '.nii']);
                Nio         = nifti;
                dm          = [Nii(i).dat.dim 1]; dm = dm(1:3);
                Nio.dat     = file_array(nam,dm,'float32',0,1,0);
                Nio.mat     = Nii(i).mat;
                Nio.mat0    = Nii(i).mat0;
                Nio.mat_intent  = Nii(i).mat_intent;
                Nio.mat0_intent = Nii(i).mat0_intent;

                Nio.descrip     = 'Bias Field';
                create(Nio);
                Nio.dat(:,:,:)  = exp(param(i).bias);
                out.bia{i}      = nam;
            else
                out.bia{i}      = exp(param(i).bias);
            end
            clear mom
        end
    end
end

if need_def
    out.def = {};
    for i=numel(param):-1:1
        if all(isfinite(w_settings(i,:)))

            M   = param(i).R*M_avg;
            for m=1:d(3)
                param(i).y(:,:,m,:) = transform_warp(M,param(i).y(:,:,m,:));
            end
            if any(strcmp('wdef',output))
                [pth,nam]   = fileparts(Nii(i).dat.fname);
                nam         = fullfile(pth,['y_' nam '.nii']);
                Nio         = nifti;
                Nio.dat     = file_array(nam,[d 1 3],'float32',0,1,0);
                Nio.mat     = M_avg;
                Nio.mat0    = Nio.mat;
                Nio.mat_intent  = 'Aligned';
                Nio.mat0_intent = Nio.mat_intent;

                Nio.descrip = 'Deformation (templ. to. ind.)';
                create(Nio);
                Nio.dat(:,:,:,1,1) = param(i).y(:,:,:,1);
                Nio.dat(:,:,:,1,2) = param(i).y(:,:,:,2);
                Nio.dat(:,:,:,1,3) = param(i).y(:,:,:,3);
                out.def{i}         = nam;
            else
                out.def{i}         = param(i).y;
            end
            param(i).y = [];
        end
    end
end

if need_jac
    out.jac = {};
    for i=numel(param):-1:1
        if all(isfinite(w_settings(i,:)))
            dt = spm_diffeo('det',param(i).J);
            if any(strcmp('wjac',output))
                [pth,nam]   = fileparts(Nii(i).dat.fname);
                nam         = fullfile(pth,['j_' nam '.nii']);
                Nio         = nifti;
                Nio.dat     = file_array(nam,d,'float32',0,1,0);
                Nio.mat     = M_avg;
                Nio.mat0    = Nio.mat;
                Nio.mat_intent  = 'Aligned';
                Nio.mat0_intent = Nio.mat_intent;

                Nio.descrip = 'Jacobian det (templ. to. ind.)';
                create(Nio);
                Nio.dat(:,:,:) = dt;
                out.jac{i}     = nam;
            else
                out.jac{i}     = dt;
            end
            clear dt
            param(i).J = [];
        end
    end
end

if need_div
    out.div = {};
    for i=1:numel(param)
        if all(isfinite(w_settings(i,:)))
            dv = spm_diffeo('div',param(i).v0);
            if any(strcmp('wdiv',output))
                [pth,nam]   = fileparts(Nii(i).dat.fname);
                nam         = fullfile(pth,['dv_' nam '.nii']);
                Nio         = nifti;
                Nio.dat     = file_array(nam,d,'float32',0,1,0);
                Nio.mat     = M_avg;
                Nio.mat0    = Nio.mat;
                Nio.mat_intent  = 'Aligned';
                Nio.mat0_intent = Nio.mat_intent;

                Nio.descrip = sprintf('Div (%.3g %.3g %.3g %.3g %.3g) (%d)',w_settings(i,:)*sc,s_settings(i,1));
                create(Nio);
                Nio.dat(:,:,:) = dv;
                out.div{i}     = nam;
            else
                out.div{i}     = dv;
            end
            clear dv
        end
    end
end

if need_vel
    out.vel = {};
    for i=1:numel(param)
        if all(isfinite(w_settings(i,:)))
            if any(strcmp('wvel',output))
                [pth,nam]   = fileparts(Nii(i).dat.fname);
                nam         = fullfile(pth,['v_' nam '.nii']);
                Nio         = nifti;
                Nio.dat     = file_array(nam,[d 1 3],'float32',0,1,0);
                Nio.mat     = M_avg;
                Nio.mat0    = Nio.mat;
                Nio.mat_intent  = 'Aligned';
                Nio.mat0_intent = Nio.mat_intent;

                Nio.descrip = sprintf('Vel (%.3g %.3g %.3g %.3g %.3g) (%d)',w_settings(i,:)*sc,s_settings(i,1));
                create(Nio);
                Nio.dat(:,:,:,1,1) = param(i).v0(:,:,:,1);
                Nio.dat(:,:,:,1,2) = param(i).v0(:,:,:,2);
                Nio.dat(:,:,:,1,3) = param(i).v0(:,:,:,3);
                out.vel{i}         = nam;
            else
                out.vel{i}         = param(i).v0;
            end
        end
    end
end
spm_plot_convergence('Clear');
%__________________________________________________________________________


%__________________________________________________________________________
function [mu,ss,nvox,D] = compute_mean(data, param, ord)
d     = data.d;
M_avg = data.mat;
img   = data.img;
prec  = data.prec;

mu   = zeros(d,'single');
nvox = zeros(numel(img),1);
ss   = zeros(numel(img),1);
if nargout>=4 % Compute gradients of template
    D  = {zeros(d,'single'),zeros(d,'single'),zeros(d,'single')};
end

for m=1:d(3)
    if nargout>=4
        Dm1  = {zeros(d(1:2),'single'),zeros(d(1:2),'single'),zeros(d(1:2),'single')};
        Dm2  = {zeros(d(1:2),'single'),zeros(d(1:2),'single'),zeros(d(1:2),'single')};
        Df   = cell(3,1);
        Db   = cell(3,1);
    end
    F  = cell(1,numel(img));
    Dt = cell(1,numel(img));
    Bf = cell(1,numel(img));
    Msk= cell(1,numel(img));

    mum = zeros(d(1:2),'single');
    mgm = zeros(d(1:2),'single');

    for i=1:numel(img)
        M = img(i).mat\param(i).R*M_avg;
        if ~isempty(param(i).y)
            y     = transform_warp(M,param(i).y(:,:,m,:));
            Dt{i} = spm_diffeo('det',param(i).J(:,:,m,:,:))*abs(det(M(1:3,1:3)));
        else
            Dt{i} = ones(d(1:2),'single')*abs(det(M(1:3,1:3)));
            y     = zeros([d(1:2) 1 3],'single');
            [y(:,:,1),y(:,:,2),y(:,:,3)] = ndgrid(single(1:d(1)),single(1:d(2)),m);
            y     = transform_warp(M,y);
        end

        if nargout>=4
            % Sample image and bias field, along with their gradients.  Gradients are
            % then transformed by multiplying with the transpose of the Jacobain matrices
            % of the deformation.
            if ~isempty(param(i).J)
                Jm = reshape(param(i).J(:,:,m,:,:),[d(1)*d(2),3,3]);
                Jm = reshape(reshape(permute(Jm,[1 2 3]),d(1)*d(2)*3,3)*M(1:3,1:3),[d(1) d(2) 3 3]);
            else
                Jm = repmat(reshape(single(M(1:3,1:3)),[1 1 3 3]),[d(1) d(2) 1 1]);
            end

            [F{i} ,d1,d2,d3]  = spm_diffeo('bsplins',img(i).f,y,ord); 
            Df{1} = Jm(:,:,1,1).*d1 + Jm(:,:,2,1).*d2 + Jm(:,:,3,1).*d3;
            Df{2} = Jm(:,:,1,2).*d1 + Jm(:,:,2,2).*d2 + Jm(:,:,3,2).*d3;
            Df{3} = Jm(:,:,1,3).*d1 + Jm(:,:,2,3).*d2 + Jm(:,:,3,3).*d3;

            if ~isempty(param(i).bias)
                [Bf{i},d1,d2,d3]  = spm_diffeo('bsplins',param(i).bias,y,[1 1 1 ord(4:end)]); % Trilinear
                Bf{i} = exp(Bf{i});
                Db{1} = Jm(:,:,1,1).*d1 + Jm(:,:,2,1).*d2 + Jm(:,:,3,1).*d3;
                Db{2} = Jm(:,:,1,2).*d1 + Jm(:,:,2,2).*d2 + Jm(:,:,3,2).*d3;
                Db{3} = Jm(:,:,1,3).*d1 + Jm(:,:,2,3).*d2 + Jm(:,:,3,3).*d3;
            else
                Bf{i} =  ones(d(1:2),'single');
                Db{1} = zeros(d(1:2),'single');
                Db{2} = zeros(d(1:2),'single');
                Db{3} = zeros(d(1:2),'single');
            end
            clear d1 d2 d3
        else
            F{i}  = spm_diffeo('bsplins',img(i).f,y,ord);
            if ~isempty(param(i).bias)
                Bf{i} = exp(spm_diffeo('bsplins',param(i).bias,y,[1 1 1 ord(4:end)])); % Trilinear
            else
                Bf{i} = ones(d(1:2),'single');
            end
        end

        msk      = isfinite(F{i}) & isfinite(Bf{i});
        Msk{i}   = msk;
        f        = F{i}(msk);
        ebias    = Bf{i}(msk);
        dt       = Dt{i}(msk);
        scal     = ebias.*dt*prec(i);
        mum(msk) = mum(msk) + f.*scal;
        mgm(msk) = mgm(msk) + ebias.*scal;

        if nargout>=4
            % For computing gradients
            Dm1{1}(msk) = Dm1{1}(msk) + (Df{1}(msk) + f.*Db{1}(msk)).*scal;
            Dm1{2}(msk) = Dm1{2}(msk) + (Df{2}(msk) + f.*Db{2}(msk)).*scal;
            Dm1{3}(msk) = Dm1{3}(msk) + (Df{3}(msk) + f.*Db{3}(msk)).*scal;

            scal        = ebias.*scal;
            Dm2{1}(msk) = Dm2{1}(msk) + Db{1}(msk).*scal;
            Dm2{2}(msk) = Dm2{2}(msk) + Db{2}(msk).*scal;
            Dm2{3}(msk) = Dm2{3}(msk) + Db{3}(msk).*scal;
        end
    end
    mgm       = mgm + eps;
    mu(:,:,m) = mum./mgm; % Weighted mean

    if false
        % Some stuff is displayed to help with debugging
        pl = ceil(size(mu,3)/2);
        if m==pl
            vx = sqrt(sum(data.mat(1:3,1:3).^2));
            dm = data.d;
            sc = {(1:dm(1))*vx(1),(1:dm(2))*vx(2)};
            subplot(4,2,1); imagesc(sc{:},mu(:,:,m)'); axis image xy off
            if ~isempty(param(1).J)
                subplot(4,2,3); dt = spm_diffeo('det',param(1).J(:,:,m,:,:)); imagesc(sc{:},dt'); axis image xy off
                subplot(4,2,4); dt = spm_diffeo('det',param(2).J(:,:,m,:,:)); imagesc(sc{:},dt'); axis image xy off
            end
            subplot(4,2,5); imagesc(sc{:},Bf{1}'); axis image xy off
            subplot(4,2,6); imagesc(sc{:},Bf{2}'); axis image xy off

            subplot(4,2,7); imagesc(sc{:},(F{1}-mu(:,:,m).*Bf{1})'); axis image xy off
            subplot(4,2,8); imagesc(sc{:},(F{2}-mu(:,:,m).*Bf{2})'); axis image xy off

            drawnow;
        end
    end

    if nargout>=2
        if nargout>=4
            % Compute "gradients of template (mu)".  Note that the true gradients
            % would incorporate the gradients of the Jacobians, but we do not want
            % these to be part of the "template gradients".
            wt          = 2*mum./(mgm.*mgm);
            D{1}(:,:,m) = Dm1{1}./mgm - Dm2{1}.*wt;
            D{2}(:,:,m) = Dm1{2}./mgm - Dm2{2}.*wt;
            D{3}(:,:,m) = Dm1{3}./mgm - Dm2{3}.*wt;
        end

        % Compute matching term
        for i=1:numel(img)
            msk      = Msk{i};
            f        = F{i}(msk);
            ebias    = Bf{i}(msk);
            dt       = Dt{i}(msk);
            mum      = mu(:,:,m);
            mum      = mum(msk);
            nvox(i)  = nvox(i) + sum(dt);
            ss(i)    = ss(i) + sum((f-mum.*ebias).^2.*dt);
        end
    end
end

for i=1:numel(img)
    ss(i) = ss(i)/nvox(i)*numel(img(i).f);
end
%__________________________________________________________________________

%__________________________________________________________________________
function y1 = transform_warp(M,y)
% Affine transformation of a deformation
d  = size(y);
y1 = reshape(bsxfun(@plus,reshape(y,[prod(d(1:3)),3])*single(M(1:3,1:3)'),single(M(1:3,4)')),d);
%__________________________________________________________________________

%__________________________________________________________________________
function y = identity(d)
% Generate an identity transform of size d(1) x d(2) x d(3)
y = zeros([d(1:3) 3],'single');
[y(:,:,:,1),y(:,:,:,2),y(:,:,:,3)] = ndgrid(single(1:d(1)),single(1:d(2)),single(1:d(3)));
%__________________________________________________________________________

%__________________________________________________________________________
function B = se3_basis
% Basis functions for the lie algebra of the special Euclidean group
% (SE(3)).
B        = zeros(4,4,6);
B(1,4,1) = 1;
B(2,4,2) = 1;
B(3,4,3) = 1;
B([1,2],[1,2],4) = [0 1;-1 0];
B([3,1],[3,1],5) = [0 1;-1 0];
B([2,3],[2,3],6) = [0 1;-1 0];
%__________________________________________________________________________

%__________________________________________________________________________
