% Call function without arguments for instructions on how to use it

% Filename: $RCSfile: prep_integ_masks.m,v $
%
% $Revision: 1.9 $  $Date: 2016/01/21 14:51:57 $
% $Author:  $
% $Tag: $
%
% Description:
% prepare masks for the radial integration of SAXS patterns
%
% Note:
% Call without arguments for a brief help text.
%
% Dependencies:
% - image_read
% - pilatus_valid_pixel_roi
%
% history:
%
% September 4th 2009:
% correct in help text one of the RadiusFrom to RadiusTo
%
% May 9th 2008: 1st documented version
%

%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [ integ_masks ] = prep_integ_masks(filename, center_xy, varargin)
import beamline.pilatus_valid_pixel_roi
import io.image_read
import plotting.display_valid_mask
import utils.pixel_to_q

% set number of radii
no_of_radii = 0;
% number of angular segments per radius
no_of_segments = 1;
% pixel size in mm
pixel_size_mm = [];%.172;
% detector distance in mm
det_dist_mm = [];%2000;
calculate_q=0; %only calculate q if exact detector distance is given
% wavelength (unit inconsequential, will be reflected in q)
lambda = [];%1;
% output directory for the masks
out_dir = '~/Data10/analysis/data/';
filename_valid_mask = [ out_dir 'pilatus_valid_mask.mat' ];
filename_integ_masks = [ out_dir 'pilatus_integration_masks.mat' ];
% output figure number
fig_no = 240;
% save integration masks
save_data = 1;
% display valid pixel mask
display_valid_mask_flag = 1;
% detector number
det_no= 1;
% angular range to be excluded to cut out the beam stop
bs_angle_from = 0;
bs_angle_to = 0;

% check minimum number of input arguments
if (nargin < 2)
    fprintf('\nUsage:\n');
    fprintf('[integ_masks]=%s( filename, center_xy [[,<name>,<value>]...]);\n',mfilename);
    fprintf('Prepare the masks for an efficient radial integration.\n');
    fprintf('The optional angular range in degree can be used to cut out a beam stop.\n');
    fprintf('Angle 0 is horizontally to the left, positive in counterclockwise direction.\n');
    fprintf('The specified data file is loaded and some of the integration masks are plotted into that frame.\n');
    fprintf('\n');
    fprintf('The optional <name>,<value> pairs are:\n');
    
    fprintf('''NormalXY'',[x y]                    pixel coordinates, from where the detector normal points\n');
    fprintf('                                    toward the sample.  Default is equal to center_xy\n');
    fprintf('''PixelSize_mm'',<double>             pixel size in mm, default is %.3f\n',pixel_size_mm);
    fprintf('''DetDist_mm'',<double>               detector distance in mm, default is %.1f\n',det_dist_mm);
    fprintf('''Wavelength'',<double>               wavelength. The units chosen here will determine the units of q\n');
    fprintf('                                    The defaults is %.1f\n',lambda);
    fprintf('''NoOfRadii'',<integer>               radial integration start radius, default is %d\n',no_of_radii);
    fprintf('        or ,<vector>,              defining the limits of radius bins\n');
    fprintf('''NoOfSegments'',<integer>            Number of angular segments. If an integer, this number of equally wide azimuthal\n')
    fprintf('                                    bins over 360 degrees are created. default is %d\n',no_of_segments);
    fprintf('        or ,<vector>,              defining the limits of angular bins\n');
    fprintf('''SaveData'',<0-no,1-yes>             save the integration masks, default is %d\n',save_data);
    fprintf('''FilenameValidMask'',<path and filename>  Matlab file with the valid pixel indices ind_valid,\n');
    fprintf('                                    default is %s\n',filename_valid_mask);
    fprintf('''FilenameIntegMasks'',<path and filename> output file name for the structure integ_masks,\n');
    fprintf('                                    default is %s\n',filename_integ_masks);
    fprintf('''FigNo'',<integer>                   number of the figure in which the result is displayed\n');
    fprintf('''DetNo'',<integer>                   number of detector 1 for SAXS and 2 for WAXS\n');
    fprintf('                                     Default is 1 (SAXS)\n');
    fprintf('''BeamstopAngleFrom'',<float>         exclude an angular region from the integration, default for the start value is %d\n',...
        bs_angle_from);
    fprintf('''BeamstopAngleTo'',<float>           exclude an angular region from the integration, default for the end value is %d\n',...
        bs_angle_to);
    fprintf('\n');
    fprintf('\n');
    fprintf('The file name should be the name of a single file without wildcards\n');
    fprintf('that is displayed as an example.\n');
    fprintf('The image file has no other function beyond being displayed as example.\n');
    fprintf('Example:\n');
    fprintf('[integ_masks]=%s(''~/Data10/pilatus/image.cbf'',[512 512]);\n',...
        mfilename);
    
    error('At least the filename and the beam center have to be specified as input parameter.');
end

% check number of center coordinates
if (length(center_xy) ~= 2)
    error('The beam center needs to be specified as a two component vector [cen_x cen_y].\n');
end
center_x = center_xy(1);
center_y = center_xy(2);
norm_x = center_x;
norm_y = center_y;

% accept cell array with name/value pairs as well
no_of_in_arg = nargin;
if (nargin == 3)
    if (isempty(varargin))
        % ignore empty cell array
        no_of_in_arg = no_of_in_arg -1;
    else
        if (iscell(varargin{1}))
            % use a filled one given as first and only variable parameter
            varargin = varargin{1};
            no_of_in_arg = 2 + length(varargin);
        end
    end
end

% check number of input arguments
if (rem(no_of_in_arg,2) ~= 0)
    error('The optional parameters have to be specified as ''name'',''value'' pairs');
end


% parse the variable input arguments
vararg = cell(0,0);
for ind = 1:2:length(varargin)
    name = varargin{ind};
    value = varargin{ind+1};
    switch name
        case 'NormalXY'
            if (numel(value)==2)
                norm_x = value(1);
                norm_y = value(2);
            end
        case 'PixelSize_mm'
            pixel_size_mm = value;
        case 'DetDist_mm'
            det_dist_mm = value;
            calculate_q=1;
        case 'Wavelength_nm'
            lambda = value;
        case 'Wavelength'
            lambda = value/10;
        case 'NoOfRadii'
            no_of_radii = value;
        case 'NoOfSegments'
            no_of_segments = value;
        case 'FilenameValidMask'
            filename_valid_mask = value;
        case 'FilenameIntegMasks'
            filename_integ_masks = value;
        case 'SaveData'
            save_data = value;
        case 'DisplayValidMask'
            display_valid_mask_flag = value;
        case 'FigNo'
            fig_no = value;
        case 'DetNo'
            det_no = value;
        case 'BeamstopAngleFrom' 
            bs_angle_from = value;
        case 'BeamstopAngleTo' 
            bs_angle_to = value; 
        otherwise
            vararg{end+1} = name;
            vararg{end+1} = value;
    end
end



% MGS - The fact that the detector number is dictating whether to have a
% radius variable or q is not ideal. Additional arguments should be given
% for this such as calculate_q or save_radius_var.
% initialize return arguments
if (det_no == 1)||(det_no == 3)
    calculate_radius = true;
elseif (det_no == 2)
    calculate_radius = false;
else
    error('Only det_no 1, 2, and 3 are recognized')
end
if ( calculate_radius ) && (calculate_q)
integ_masks = struct('radius',[], 'indices',[], 'norm_sum', [],'q',[]);
elseif (calculate_radius) && (calculate_q == 0)
  integ_masks = struct('radius',[], 'indices',[], 'norm_sum', []); 
elseif (~calculate_radius)
  integ_masks = struct('indices',[], 'norm_sum', [],'q',[]); 
end


% check radius
%if (exist('r_from','var'))
if (size(no_of_radii)>1)
    if (no_of_radii(1) < 1)
        %if (r_from < 1)
        error('The minimum radius is 1, %d is invalid.',r_from);
    end
    %end
    %if (exist('r_from','var') && exist('r_to','var'))
    if  (no_of_radii(end) < no_of_radii(1))
        %if ((r_to ~= 0) && (r_to < r_from))
        error('The maximum radius must be greater than the minimum one, %d is invalid.\n',no_of_radii(end));
    end
end

% check number of angular segments
if (no_of_segments < 1)
    error('The number of angular segments must be at least 1');
end
if (numel(no_of_segments)>1)
    angular_segments = no_of_segments;
    no_of_segments = numel(no_of_segments)-1;
else
    angular_segments = 360/no_of_segments * (0:no_of_segments);
end
angular_segments = mod(angular_segments, 360);

% check beamstop region
if ((bs_angle_from < 0.0) || (bs_angle_to > 360.0))
    error('The angular range for the beam stop region is 0 to 360 degree.');
end
if (bs_angle_to < bs_angle_from)
    error('The maximum beam stop angle must be less than or equal to the minimum one.\n');
end

% load the indices of valid pixels
fprintf('loading the valid pixel mask %s\n',filename_valid_mask);
load(filename_valid_mask);
dim_x = valid_mask.framesize(2);
dim_y = valid_mask.framesize(1);


if (~isempty(filename))
    % load test frame
    frame = image_read(filename,vararg);
    % select the first frame for display
    frame.data = frame.data(:,:,1);
    % in case of less than full detector readout cut out the right part of
    % the valid pixel mask
    valid_mask = pilatus_valid_pixel_roi(valid_mask,'RoiSize',size(frame.data));
    dim_x = size(frame.data,2);
    dim_y = size(frame.data,1);
end

% plot valid pixel mask
if (display_valid_mask_flag)
    figure(fig_no);
    vpm = zeros(dim_y,dim_x);
    vpm(valid_mask.indices) = 1;
    imagesc(vpm);
    axis xy;
    axis equal;
    axis tight;
    title('valid pixels');
    set(gcf,'Name','valid pixels');
    drawnow;
end

if calculate_radius
    %if (exist('r_to','var'))
    % choose maximum radius, if specified via r_to=0
    if (no_of_radii < 1)
        no_of_radii = max( [ sqrt(center_x^2+center_y^2) ...
            sqrt((dim_x-center_x)^2+center_y^2) ...
            sqrt(center_x^2+(dim_y-center_y)^2) ...
            sqrt((dim_x-center_x)^2+(dim_y-center_y)^2) ] );
    end
    if size(no_of_radii)==1
        no_of_radii=1:1:no_of_radii;
    end
end

fprintf('preparing the integration masks ...\n');

% create an array of the (x,y) coordinates relative to the beam center and
% convert it to polar coordinates
%
if calculate_radius % For SAXS detector - MGS, should be fixed, why is it neded different calculation for different detectors?
    % angular range to be excluded to cut out the beam stop

    [ x, y ] = meshgrid( (1:dim_x)-center_x, (1:dim_y)-center_y );
    [ theta, rho ] = cart2pol( x, y );
    % convert angular range from -pi/pi to 0/360
    theta = (theta/pi +1) * 180.0;
    
    % prepare circular masks of the integer width r_step (in pixel)
    integ_masks.radius = no_of_radii;
    r_step=no_of_radii(2)-no_of_radii(1);
    if calculate_q
        integ_masks.q = pixel_to_q(no_of_radii,pixel_size_mm,det_dist_mm, 12.39852/lambda);
    end
    no_of_radii = length(integ_masks.radius);
    integ_masks.indices = cell( no_of_radii, no_of_segments );
    integ_masks.norm_sum = zeros( no_of_radii, no_of_segments );
    seg_inds = cell(no_of_segments,1);
    for ind_seg = 1:no_of_segments
        seg_from = angular_segments(ind_seg);
        seg_to   = angular_segments(ind_seg+1);
        if (seg_from >= seg_to)
            ind_curr = find( ((theta > seg_from) | (theta <= seg_to) ) & ...
                ((theta <= bs_angle_from) | (theta >= bs_angle_to)) );
        else
            ind_curr = find( ((theta > seg_from) & (theta <= seg_to) ) & ...
                ((theta <= bs_angle_from) | (theta >= bs_angle_to)) );
        end
        % only take valid pixels into account
        ind_curr = intersect(ind_curr, valid_mask.indices);
        seg_inds{ind_seg} = ind_curr;
    end
    
    for ind_r=1:no_of_radii
        if (rem(ind_r,100) == 0)
            fprintf('%4d / %d',ind_r,no_of_radii);
            if (ind_r <= no_of_radii-100)
                fprintf(', ');
            end
        end
        r_inds = find( (rho >= integ_masks.radius(ind_r)) & ...
            (rho < integ_masks.radius(ind_r)+r_step) );
        for ind_seg = 1:no_of_segments
            integ_masks.indices{ind_r, ind_seg} = intersect( r_inds, seg_inds{ind_seg} );
            % calculate the normalization value (sum of the pixels within the mask)
            integ_masks.norm_sum(ind_r, ind_seg) = ...
                length( integ_masks.indices{ind_r, ind_seg} );
        end
    end
    fprintf('\n');
    
else 
    [ x, y ] = meshgrid( (1:dim_x)-norm_x, (1:dim_y)-norm_y );
    if (norm_x == center_x && norm_y == center_y)
        [ theta, rho ] = cart2pol( x, y );
        q = 4*pi/lambda*sin(atan2(rho,det_dist_mm/pixel_size_mm)/2);
        % convert angular range from -pi/pi to 0/360
        theta = theta/pi*180.0;
    else
        if (norm_x ~= center_x)
            angle = atan((norm_x - center_x) / (det_dist_mm/pixel_size_mm));
            z = -x*sin(angle) + det_dist_mm/pixel_size_mm*cos(angle);
            x =  x*cos(angle) + det_dist_mm/pixel_size_mm*sin(angle);
        else
            fprintf('not implemented yet!!!\n');
            exit
        end
        q = 4*pi/lambda*sin(atan2(sqrt(x.^2 + y.^2),z)/2);
        theta = atan2(y,x)/pi*180;
    end
    t_1d = reshape(theta(valid_mask.indices),1,[]);
    q_1d = reshape(q(valid_mask.indices),1,[]);
    
    t_ed = linspace(     -180,      180,1e0+1);
    integ_masks.theta = t_ed(1:end-1);
    integ_masks.theta_end = t_ed(end);
    q_ed = linspace(min(q_1d),max(q_1d),1e3+1);
    integ_masks.q = q_ed(1:end-1);
    integ_masks.q_end = q_ed(end);
    
    [~,t_bin] = histc(t_1d,t_ed);
    [~,q_bin] = histc(q_1d,q_ed);
    
    integ_masks.indices = cell(numel(q_ed)-1,numel(t_ed)-1);
    integ_masks.norm_sum = zeros(size(integ_masks.indices));
    for q_i=1:numel(q_ed)-1
        for t_i=1:numel(t_ed)-1
            integ_masks.indices{q_i,t_i} = ...
                valid_mask.indices(and(q_bin==q_i,t_bin==t_i));
            integ_masks.norm_sum(q_i,t_i) = numel(integ_masks.indices{q_i,t_i});
        end
    end
end


% save integration masks
if (save_data)
    fprintf('Saving center_xy, no_of_segments, integ_masks to %s\n',...
        filename_integ_masks);
    if angular_segments(end) == 0
        angular_segments(end) = 360;
    end
    phi_det = (angular_segments(2:end) + angular_segments(1:end-1))/2; %% Center of the angular sector in degrees
    save(filename_integ_masks,'center_xy','no_of_segments','integ_masks','angular_segments','phi_det');
end

% display some integration circles
if (~isempty(filename))
    figure(fig_no+1);
    hold off;
    clf;
    frame_plot = double(frame.data);
    frame_plot( frame_plot < 1 ) = 1;
    
    plot_step = round(length(integ_masks.indices)/50);
    if (plot_step < 2)
        plot_step = 2;
    end
    for (ind_r = 1:plot_step:size(integ_masks.indices,1))
        for (ind_seg = 1:2:no_of_segments)
            frame_plot(integ_masks.indices{ind_r,ind_seg}) = 10^(6*ind_seg/no_of_segments);
        end
    end
    
    imagesc(log10(frame_plot));
    axis xy;
    axis equal;
    axis tight;
    colorbar;
    title([ 'integration segment test plot for ' strrep(filename,'_','\_') ]);
    set(gcf,'Name','integration masks');
end
