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

% Filename: $RCSfile: prep_valid_mask.m,v $
%
% $Revision: 1.8 $  $Date: 2016/01/21 15:07:41 $
% $Author: guizar_m $
% $Tag: $
%
% Description:
% prepare a list of the linear indices for the valid pixels
%
% Note:
% Call without arguments for a brief help text.
%
% Dependencies: 
% - nexus_read
%
% history:
%
% May 15th 2010, Oliver Bunk:
% add command line argument for ThresholdMedian
%
% September 4th 2009, Oliver Bunk: 
% use find_files rather than dir to find the files
%
% May 9th 2008, Oliver Bunk: 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 matlab 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 [valid_mask] = prep_valid_mask_nexus(data_dir, det , varargin)
import io.nexus_read
import plotting.display_valid_mask
import utils.find_files

% set default values for the variable input arguments:
% filename for loading and saving the valid pixel mask
filename_valid_mask = '~/Data10/analysis/data/pilatus_valid_mask.mat';
% below this threshold intensity a pixel is considered to be dark
threshold_dark = 1;
% above this threshold intensity a pixel is considered to be hot
threshold_hot = 20;
% this value times the square root of the intensity is used as hot pixel
% threshold
threshold_median = 5.0;
% replace the existing mask
extend = 'no';
% save the mask
save_data = 1;
% display result in this figure
fig_no = 200;
% read nexus file
filename_mask = '*.h5';
% save as HDF5 
save_h5 = true;
% adjust orientation
adjustOrientation = false;

% check minimum number of input arguments
if (nargin < 1)
    fprintf('\nUsage:\n');
    fprintf('[valid_mask]=%s(data_dir, detector, [[,<name>,<value>]...]);\n',mfilename);
    fprintf('Prepare a list of the linear indices for the valid pixels.\n');
    fprintf('To get reliable data a series of at least 10 frames should be analyzed.\n');
    fprintf('The direct beam region will be regarded as invalid since it is out of the\n');
    fprintf('range for valid pixels. To ''repair'' this one should take a second series of\n');
    fprintf('exposures at a different detector position and call this macro with the ''Extend'',''or''\n');
    fprintf('option.\n');
    fprintf('\n');
    fprintf('The optional <name>,<value> pairs are:\n');
     fprintf('''FilenameMask'',<file specifier>     specify the files to be used from the data directory, empty string for all, default is ''%s''\n',...
        filename_mask);
    fprintf('''ThresholdDark'',<float>             pixels permanently below this value are considered to be dark, default is %d\n',...
        threshold_dark);
    fprintf('''ThresholdHot'',<float>              pixels at least once above this value are considered to be hot, default is %d\n',...
        threshold_hot);
    fprintf('''ThresholdMedian'',<float>           pixels of intensity I above the constant ThresholdHot and above\n');
    fprintf('                                    ThresholdMedian times (I+sqrt(I)) are considered to be hot, 0 to deactivate this additional threshold,\n');
    fprintf('                                    default is %.1f\n',...
        threshold_median);
    fprintf('''SaveData'',<0-no,1-yes>             save the valid pixel mask, default is %d\n',save_data);
    fprintf('''FilenameValidMask'',<path and filename>  Matlab file with the valid pixel indices,\n');
    fprintf('                                    default is %s\n',filename_valid_mask);
    fprintf('''Extend'',<''and'', ''or'' or ''no''>  update an existing mask using the specified conjunction, default is %s\n',...
        extend);
    fprintf('''AdjustOrientation'', <0-no,1-yes>   adjust the raw data orientation,\n');
    fprintf('''FigNo'',<integer>                   number of the figure in which the result is displayed, default is %d\n',...
        fig_no);


    fprintf('\n');
    fprintf('Examples:\n');
    fprintf('[valid_mask]=%s(''~/Data10/pilatus/air_scattering/'');\n',...
        mfilename);
    fprintf('[valid_mask]=%s(''~/Data10/pilatus/air_scattering_det_pos_2/'',''Extend'',''or'');\n',...
        mfilename);

    error('At least the data directory has to be specified as input parameter.');
end

% accept cell array with name/value pairs as well
no_of_in_arg = nargin;
if (nargin == 2)
    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 = no_of_in_arg -1 + length(varargin);
        end
    end
end

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


% parse the variable input arguments:
% initialize the list of unhandled parameters
vararg_remain = cell(0,0);
for ind = 1:2:length(varargin)
    name = varargin{ind};
    value = varargin{ind+1};
    switch name
        case 'ThresholdDark'
            threshold_dark = value;
        case 'ThresholdHot'
            threshold_hot = value;
        case 'ThresholdMedian'
            threshold_median = value;
        case 'FilenameMask' 
            filename_mask = value;
        case 'FilenameValidMask' 
            filename_valid_mask = value;
        case 'SaveData' 
            save_data = value;
        case 'FigNo' 
            fig_no = value;
        case 'Extend' 
            extend = value;
        case 'AdjustOrientation'
            adjustOrientation = value;
        otherwise
            vararg_remain{end+1} = name; %#ok<AGROW>
            vararg_remain{end+1} = value; %#ok<AGROW>
    end
end

vararg_remain{end+1} = 'UnhandledParError';
vararg_remain{end+1} = 0;

% check extend parameter
if ((~strcmp(extend,'no')) && ...
    (~strcmp(extend,'and')) && (~strcmp(extend,'or')))
    error('extend must be ''and'', ''or'' or ''no''\n');
end

% set some default values for the plot window
set(0, 'DefaultAxesfontsize', 12);
set(0, 'DefaultAxeslinewidth', 1, 'DefaultAxesfontsize', 12);
set(0, 'DefaultLinelinewidth', 1);

% get all matching filenames
if (data_dir(end) ~= '/')
    data_dir(end+1) = '/';
end
[data_dir,fnames,vararg_remain] = ...
    find_files( [ data_dir filename_mask ], vararg_remain );

if (length(fnames) < 1)
    error('No matching files found for %s%s.\n',data_dir,filename_mask);
end

if (~strcmp(extend,'no'))
    if exist(filename_valid_mask,'file')
        fprintf('loading the existing valid mask %s\n', ...
            filename_valid_mask);
        [~, ~, ext] = fileparts(filename_valid_mask);
        switch ext
            case '.h5'
                mask = io.HDF.hdf5_load(filename_valid_mask, '/entry/collection/mask');
                valid_mask = beamline.mask2ind(mask);
                ind_existing_valid = valid_mask.indices;
            case '.mat'
                mask = load(filename_valid_mask);
                valid_mask = mask.valid_mask;
                ind_existing_valid = valid_mask.indices;
            otherwise
                error('Unknown file extension');
        end
    else
        fprintf('no prior valid mask %s found\n', ...
            filename_valid_mask);
        ind_existing_valid = '';
    end
end


% process the frames
ind_hot = [];
ind_dark = [];
fprintf('data directory is %s\n',data_dir);
for (f_ind=1:length(fnames)) 
    fprintf('%3d/%3d: reading %s%s\n',f_ind,length(fnames),...
        data_dir,fnames(f_ind).name);
    if ~adjustOrientation
        [frame] = nexus_read([data_dir fnames(f_ind).name ],'filter',det, 'orientation', [0 0]);
    else
        [frame] = nexus_read([data_dir fnames(f_ind).name ],'filter',det);
    end
    frame.data = double(frame.data);
    for (frame_ind = 1:size(frame.data,3))
        % median filtered data for comparison
        if (threshold_median ~= 0)
            data_med = frame.data(:,:,frame_ind);

            % add pixels at the module boundary to ease median filtering
            ind = find(data_med == 0);
            data_med_shift = circshift(data_med,[2 2]);
            data_med(ind) = data_med_shift(ind);

            ind = find(data_med == 0);
            data_med_shift = circshift(data_med,[-2 -2]);
            data_med(ind) = data_med_shift(ind);

            ind = find(data_med == 0);
            data_med_shift = circshift(data_med,[-2 2]);
            data_med(ind) = data_med_shift(ind);

            ind = find(data_med == 0);
            data_med_shift = circshift(data_med,[2 -2]);
            data_med(ind) = data_med_shift(ind);

            % median filter the data
            data_med = medfilt2(data_med,[5 5]);

            % the square root of the intensity estimates the standard deviation
            data_med_sqrt = data_med.^0.5;
        end

        if (f_ind == 1)
            framesize1 = size(frame.data,1);
            framesize2 = size(frame.data,2);
            framesize = framesize1 * framesize2;
        end

        % check that the file have identical dimensions
        if ((framesize1 ~= size(frame.data,1)) || ...
            (framesize2 ~= size(frame.data,2)))
                error('The previous file(s) had %d x %d pixels, this frame has %d x %d pixels',...
                framesize1,framesize2,size(frame.data,1),size(frame.data,2));
        end

        % pixels are considered to be dark if the intensity is below the
        % constant threshold
        ind = find(frame.data(:,:,frame_ind) < threshold_dark);
        fprintf('%6d dark pixels below %10.3e counts, ', ...
            length(ind),threshold_dark);
        if (f_ind == 1)
            ind_dark = ind;
        else
            % dark pixels must be dark in all frames
            ind_dark = intersect(ind_dark,ind);
        end

        % hot pixels are hot if they are above the threshold
        ind = find(frame.data(:,:,frame_ind) > threshold_hot);
        % and, if active, above the intensity plus a threshold times the square
        % root of the intensity as an estimation of the countin statistics
        % error
        if (threshold_median ~= 0.0)
            ind = intersect(ind,find((frame.data(:,:,frame_ind) > data_med+threshold_median*data_med_sqrt)));
            fprintf('%4d hot pixels above %d and %.1f * sqrt(intensity) counts\n', ...
                length(ind),threshold_hot,threshold_median);
        else
            fprintf('%4d hot pixels above %d counts\n', ...
                length(ind),threshold_hot);
        end
        % for hot pixels it is enough to be above the threshold in one frame
        ind_hot = union(ind_hot,ind);   
    end
end

% calculate the complementary masks of the valid pixels
valid_mask.indices = setdiff(1:framesize,union(ind_dark,ind_hot));

fprintf('In total %d dark and %d hot pixels found.\n',...
    length(ind_dark),length(ind_hot));
fprintf('%d valid pixels remain.\n',length(valid_mask.indices));

if (~strcmp(extend,'no'))
     fprintf('Extending the existing valid pixel mask of %d pixels\n',...
             length(ind_existing_valid));
     if (strcmp(extend,'and'))
         fprintf('using the and conjugation\n');
         valid_mask.indices = ...
             intersect(valid_mask.indices,ind_existing_valid);
     else
         fprintf('using the or conjugation\n');
         if ~isempty(ind_existing_valid)
             valid_mask.indices = ...
                 union(valid_mask.indices,ind_existing_valid);
         end
     end
     fprintf('The combined mask has %d valid pixels.\n',...
             length(valid_mask.indices));
end

% store the frame size in the return data
valid_mask.framesize = [framesize1 framesize2];

if (save_data)
    % create a backup of the mask
    if (exist(filename_valid_mask,'file'))
        filename_mask_backup = [ filename_valid_mask '.bak' ];
        fprintf('Copying the current mask %s to %s\n',filename_valid_mask,...
            filename_mask_backup);
        copyfile(filename_valid_mask,filename_mask_backup);
    end

    % save the masks
    fprintf('Saving valid_mask to %s\n',filename_valid_mask);
    if save_h5
        io.save_mask_nexus(filename_valid_mask, beamline.ind2mask(valid_mask));
    else
        save(filename_valid_mask,'valid_mask');
    end
end

if fig_no>0
    figure(fig_no)
    imagesc(math.applyTransform(beamline.ind2mask(valid_mask), ...
        [frame.orientation.transpose frame.orientation.rot90], 'transpose_rot90')); axis equal tight xy
    colormap(plotting.colormaps.magma)

end

% % plot new valid pixel mask
% if (fig_no > 0)
%   display_valid_mask('FilenameValidMask',filename_valid_mask,...
%     'NoHelp',1,'FigNo',fig_no);
% end
