function [B, W] = spm_robust_glm(Y, X, dim, ks)
% Apply robust GLM
% FORMAT [B, W] = spm_robust_glm(Y, X, dim, ks)
% Y      - data matrix
% X      - design matrix
% dim    - the dimension along which the function will work
% ks     - offset of the weighting function (default: 3)
%
% OUTPUT:
% B      - parameter estimates
% W      - estimated weights
%
% Implementation of:
%   Wager TD, Keller MC, Lacey SC, Jonides J.
%   Increased sensitivity in neuroimaging analyses using robust regression.
%   Neuroimage. 2005 May 15;26(1):99-113
%__________________________________________________________________________

% James Kilner,  Vladimir Litvak
% Copyright (C) 2008-2022 Wellcome Centre for Human Neuroimaging


if nargin < 3 || isempty(ks)
    ks = 3;
end

if nargin < 2 || isempty(dim)
    dim = 1;
end

%-Remember the original data size and size of the mean
%--------------------------------------------------------------------------
origsize       = size(Y);
borigsize      = origsize;
borigsize(dim) = size(X, 2);

%-Convert the data to repetitions x points matrix
%--------------------------------------------------------------------------
if dim > 1
    Y  = shiftdim(Y, dim-1);  
end

if length(origsize) > 2
    Y  = reshape(Y, size(Y, 1), []);
end

%-Check the design matrix and compute leverages
%--------------------------------------------------------------------------

if size(X, 1) ~= size(Y, 1)
    error('The number of rows in the design matrix should match dimension of interest.');
end

H = diag(X*inv(X'*X)*X');
H = repmat(H(:), 1, size(Y, 2));

%-Rescale the data
%--------------------------------------------------------------------------
[Y, scalefactor] = spm_cond_units(Y);

%-Actual robust GLM
%--------------------------------------------------------------------------
ores=1;
nres=10;
n=0;

YY = Y;
YY(isnan(YY)) = 0;

while max(abs(ores-nres))>sqrt(1E-8)

    ores=nres;
    n=n+1;

    if n == 1
        W = ones(size(Y));
        W(isnan(Y))  =  0;
    end
    
    B = zeros(size(X, 2), size(Y, 2));
            
    for i = 1:size(Y, 2)
        B(:, i) = inv(X'*diag(W(:, i))*X)*X'*diag(W(:, i))*YY(:, i);
    end

    if n > 200
        warning('Robust GLM could not converge. Maximal number of iterations exceeded.');
        break;
    end

    res = Y-X*B;

    mad = nanmedian(abs(res-repmat(nanmedian(res), size(res, 1), 1)));
    res = res./repmat(mad, size(res, 1), 1);
    res = res.*H;
    res = abs(res)-ks;
    res(res<0)=0;
    nres= (sum(res(~isnan(res)).^2));
    W = (abs(res)<1) .* ((1 - res.^2).^2);
    W(isnan(Y)) = 0;
    W(Y == 0)   = 0; %Assuming X is a real measurement
end

disp(['Robust GLM finished after ' num2str(n) ' iterations.']); 

%-Restore the betas and weights to the original data dimensions
%--------------------------------------------------------------------------
B = B./scalefactor;

if length(origsize) > 2   
    B  = reshape(B, circshift(borigsize, [1 -(dim-1)]));
    W  = reshape(W, circshift(origsize,  [1 -(dim-1)]));
end

if dim > 1
    B  = shiftdim(B, length(origsize)-dim+1);
    W  = shiftdim(W, length(origsize)-dim+1);
end

%-Helper function
%--------------------------------------------------------------------------
function Y = nanmedian(X)
if ~any(any(isnan(X)))
    Y = median(X);
else
    Y = zeros(1, size(X,2));
    for i = 1:size(X, 2)
        Y(i) = median(X(~isnan(X(:, i)), i));
    end
end
