% [PSD, freq] = power_spectral_density(img, varargin)
% Computes the power spectral density of the provided 3D image.
% Can handle non-cube arrays but assumes the voxel is isotropic
%
% Inputs:
% img               input image (2D or 3D)
%
% Parameters: 
% thickring         Normally the pixels get assigned to the closest integer pixel ring in Fourier domain. 
%                   With thickring the thickness of the rings is increased by
%                   thickring, so each ring gets more pixels and more statistics
% auto_binning      apply binning if dimensions are significanlty different along each axis
% mask              bool array equal to false for ignored pixels of the fft space  
%
% Outputs: 
% PSD               PSD curve values
% freq              normalized spatial frequencies to 1
%
% Example of use: 
% img = randn(512,512,512); 
% utils.power_spectral_density(img, 'thickring', 3);



%*-----------------------------------------------------------------------*
%|                                                                       |
%|  Except where otherwise noted, this work is licensed under a          |
%|  Creative Commons Attribution-NonCommercial-ShareAlike 4.0            |
%|  International (CC BY-NC-SA 4.0) license.                             |
%|                                                                       |
%|  Copyright (c) 2017 by Paul Scherrer Institute (http://www.psi.ch)    |
%|                                                                       |
%|       Author: CXS group, PSI                                          |
%*-----------------------------------------------------------------------*
% You may use this code with the following provisions:
%
% If the code is fully or partially redistributed, or rewritten in another
%   computing language this notice should be included in the redistribution.
%
% If this code, or subfunctions or parts of it, is used for research in a 
%   publication or if it is fully or partially rewritten for another 
%   computing language the authors and institution should be acknowledged 
%   in written form in the publication: “Data processing was carried out 
%   using the “cSAXS software package” developed by the CXS group,
%   Paul Scherrer Institut, Switzerland.” 
%   Variations on the latter text can be incorporated upon discussion with 
%   the CXS group if needed to more specifically reflect the use of the package 
%   for the published work.
%
% A publication that focuses on describing features, or parameters, that
%    are already existing in the code should be first discussed with the
%    authors.
%   
% This code and subroutines are part of a continuous development, they 
%    are provided “as they are” without guarantees or liability on part
%    of PSI or the authors. It is the user responsibility to ensure its 
%    proper use and the correctness of the results.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [PSD, freq] = power_spectral_density(img, air, varargin)
import math.isint
import utils.*

%%%%%%%%%%%%%%%%%%%%% PROCESS PARAMETERS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

parser = inputParser;
parser.addParameter('thickring',  3 , @isnumeric )  % thick ring in Fourier domain
parser.addParameter('auto_binning',  true , @islogical )  % bin FRC before calculating rings, it makes calculations faster 
parser.addParameter('max_rings',  200 , @isnumeric )  % maximal number of rings if autobinning is used 
parser.addParameter('mask',  true, @islogical  )     % bool array, equal to false for ignored pixels of the fft space  
parser.addParameter('windowautopos',  true, @islogical  )     % automatically position plotted window
parser.addParameter('figure_id',  101, @isint)     % call figure(figure_id)


parser.parse(varargin{:})
param = parser.Results;


disp('Calculating PSD');

% remove masked values from consideration (i.e. for laminography)
Fimg = abs(bsxfun(@times,fftn(img) , param.mask+eps)).^2;

[ny,nx,nz] = size(img);
nmin = min(size(img));

% avoid edge artefacts 
img = img .* tukeywin(size(img,1),0.5) .* tukeywin(size(img,2),0.5)' .* reshape(tukeywin(size(img,3),0.5),1,1,[]);


thickring = param.thickring; 

if param.auto_binning 
    % bin the correlation values to speed up the following calculations 
    % find optimal binning to make the volumes roughly cubic 
    bin = ceil(thickring/4) * floor(size(img)/ nmin); 
    % avoid too large number of rings 
    bin = max(bin, floor(nmin ./ param.max_rings)); 
    
    if any(bin > 1)
        fprintf('Autobinning %ix%ix%i \n', bin)
        thickring = ceil(thickring / min(bin));
        % fftshift and crop the arrays to make their size dividable by binning number 
        if ismatrix(img); bin(3) = 1; end
        % force the binning to be centered 
        subgrid = {fftshift(ceil(bin(1)/2):(floor(ny/bin(1))*bin(1)-floor(bin(1)/2)-1)), ...
                   fftshift(ceil(bin(2)/2):(floor(nx/bin(2))*bin(2)-floor(bin(2)/2)-1)), ...
                   fftshift(ceil(bin(3)/2):(floor(nz/bin(3))*bin(3)-floor(bin(3)/2)-1))}; 
        if ismatrix(img); subgrid(3) = [] ; end 
        % binning makes the shell / ring calculations much faster
        Fimg = ifftshift(utils.binning_3D(Fimg(subgrid{:}), bin));
    end
else
    bin = 1;
end


[ny,nx,nz] = size(Fimg);
nmax = max([nx ny nz]);
nmin = min(size(img));


% empirically tested that thickring should be >=3 along the smallest axis to avoid FRC undesampling 
thickring = max(thickring, ceil(nmax/nmin)); 

param.thickring  = thickring;

rnyquist = floor(nmax/2);
freq = [0:rnyquist];

x = ifftshift([-fix(nx/2):ceil(nx/2)-1])*floor(nmax/2)/floor(nx/2);
y = ifftshift([-fix(ny/2):ceil(ny/2)-1])*floor(nmax/2)/floor(ny/2);
if nz ~= 1
    z = ifftshift([-fix(nz/2):ceil(nz/2)-1])*floor(nmax/2)/floor(nz/2);
else
    z = 0;
end
[X,Y,Z] = meshgrid(single(x),single(y),single(z));
index = (sqrt(X.^2+Y.^2+Z.^2));

clear X Y Z


Nr = length(freq); 
for ii = 1:Nr
    r = freq(ii);
    progressbar(ii,Nr)
    % calculate always thickring, min ring thickness is given by the smallest axis
    ind = index>=r-thickring/2 & index<=r+thickring/2 ; 
    ind = find(ind);  % find seems to be faster then indexing 
    auxFimg = Fimg(ind); 
    C(ii)  = sum(auxFimg);
    n(ii) = numel(ind);  % Number of points
end

n = n*prod(bin);  % account for larger number of elements in the binned voxels

PSD = abs(C) ./ n;
freq = freq/freq(end); 

figure(param.figure_id)
hold all 
plot(freq, PSD)
hold off 
set(gca, 'yscale', 'log')
ylabel('Power spectral density')
xlabel('Spatial frequency/Nyquist')
grid on 

    
end



