function multiview(varargin) 
%MULTIVIEW Combines up to three image stacks recorded from different angles

%   multiview(SourceFolderPathA, SourceFolderPathB, [SourceFolderPathC],
%   ResultFolderPath) example of usage: multiview('data\Tiff0degree',
%   'data\Tiff90degree', 'data\results')

%   This program combines light ultramicroscopy recordings obtained from up
%   to 3 different angles. The input stacks consist of series of 16bit or
%   32bit grayscale 2D-images stored in TIFF format (required file
%   extension is *.tif). Each of the 2-3 source folders must contain the
%   same number of images and all images from a stack have to be be named
%   in ascending numerical order (eg. im0001.tif to im1000.tif) and to be
%   of identical size. The image stacks have to be precisely registered
%   prior to multiview combining and to be rescliced in a way that all the
%   slices are orientated in the same direction (i.e. the structures shown
%   in image n from stackA must exaxtly correspond to the structures in
%   image n from stackB. The required image registration and resclicing of
%   the source data can e.g. be done using the 3D visualization software
%   Amira (FEI, Berlin, Germany). The program combines the images from all
%   source folders using the FFT-based appoach described in the
%   publication. After combining, the results are stored under
%   ResultFolderPath as a series of single TIFF-files.
       
    global stack1 stack2 stack3;

    if nargin < 3 || nargin > 4
        disp('wrong number of parameters');
        disp('usage: multiview InDir1 InDir2 [InDir3] OutDir');
        error('');
    elseif nargin == 3
        InDir1 = (varargin{1});
        InDir2 = (varargin{2});
        InDir3 = '';
        OutDir = (varargin{3});
    elseif nargin == 4        
        InDir1 = (varargin{1});
        InDir2 = (varargin{2});
        InDir3 = (varargin{3});
        OutDir = (varargin{4});
    end
            
    %load stack1 
    disp('loading stack1...');
    stack1 = loadStack(InDir1);     
    maxinp1 = max(stack1(:));
    
    %padding
    padval = 50;
    stack1 = padarray(stack1, [padval, padval, padval], 'symmetric');
    
    %rescale input1 
    stack1 = stack1 ./ percentile(stack1, 95);   
    
    %load stack2 
    disp('loading stack2...');
    stack2 = loadStack(InDir2);   
    maxinp2 = max(stack2(:));  
  
    %padding
    stack2 = padarray(stack2, [padval, padval, padval], 'symmetric');
    
    if size(stack1) ~= size(stack2)
        error('Image stack 2 has different size!');        
    end
    
    %rescale input2     
    stack2 = stack2 ./ percentile(stack2, 95);   
        
    if isempty(InDir3)
        maxinp3 = single(0);
        combined = combine2; %function call
    else
        %load stack3
        disp('loading stack3...');
        stack3 = loadStack(InDir3);
        maxinp3 = max(stack3(:));
        
        %padding
        stack3 = padarray(stack3, [padval, padval, padval], 'symmetric');
        
        if size(stack1) ~= size(stack3)
            error('Image stack 3 has different size!');            
        end
        
        %rescale input3
        stack3 = stack3 ./ percentile(stack3, 95);
        
        combined = combine3; %function call
    end
    clearvars stack1 stack2 stack3;
        
    %rescale combined stack to [0..1]
    maximum = max(combined(:));
    minimum = min(combined(:));          
    combined = (combined - minimum) ./ (maximum - minimum);
                    
    %remove padding
    combined = combined(padval:size(combined,1) - padval-1, padval:size(combined,2) - padval-1, padval: size(combined,3) - padval-1);
        
    %save combined stack
    disp('saving results...');  
    globalmax = max([maxinp1, maxinp2, maxinp3]);
    for i = 1:size(combined, 1)
        image = squeeze(combined(i, :, :));
        fname = [OutDir '\' 'combined' num2str(i-1) '.tif'];
               
        if globalmax <= 65535
            imwrite(uint16(image * 65535), fname, 'tif');
        else
            writeTiff32(uint32(image * globalmax, fname));
        end
    end
    disp('multiview combining successfully finished!');
end

function stack = loadStack(datadir)    
    %generate list of files to be processed
    filelist = dir(fullfile(datadir, '\*.tif'));
    if numel(filelist) == 0
        error('Folder contains no files!')
    end
    
    %preallocate memory
    [y, x] = size(imread([datadir '\' filelist(1).name]));
    stack = zeros(numel(filelist), y, x, 'single');
    
    %read image data   
    for i = 1:numel(filelist)
       stack(i,:,:) = imread([datadir '\' filelist(i).name]);
    end
end

function writeTiff32(img, fname)
    t = Tiff(fname, 'w');
    tagstruct.ImageLength = size(img, 1);
    tagstruct.ImageWidth = size(img, 2);
    tagstruct.Compression = Tiff.Compression.LZW;
    tagstruct.SampleFormat = Tiff.SampleFormat.IEEEFP;
    tagstruct.Photometric = Tiff.Photometric.MinIsBlack;
    tagstruct.BitsPerSample = 32;
    tagstruct.SamplesPerPixel = 1;
    tagstruct.PlanarConfiguration = Tiff.PlanarConfiguration.Chunky;
    t.setTag(tagstruct);
    t.write(img);
    t.close();
end

function Combined = combine2
    global stack1 stack2;
    
    %do forward FFT of both stacks    
    disp('calculating FFT of stack 1...');
    FFTstack1 = fftn(stack1);
    clearvars -global stack1;
        
    disp('calculating FFT of stack 2...');
    FFTstack2 = fftn(stack2);
    clearvars -global stack2;
    
    disp('multiview combining...');
    %combine the phases
    CombinedPhase = atan2(imag(FFTstack1) + imag(FFTstack2), real(FFTstack1) + real(FFTstack2));
        
    %calculate the magnitudes
    mag1 = abs(FFTstack1);
    clearvars FFTstack1;
    mag2 = abs(FFTstack2);
    clearvars FFTstack2;
      
    %combine magnitudes
    CombinedMag = max(mag1, mag2);
    clearvars mag1 mag2
    
    %recombine magnitudes and phases and do backward transform;
    Combined = ifftn(cos(CombinedPhase) .* CombinedMag + 1i .* sin(CombinedPhase) .* CombinedMag);
        
    clearvars CombinedMag CombinedPhase;
    
    %clip negative vlaues
    Combined(Combined < 0) = 0;
end

function Combined = combine3 
    global stack1 stack2 stack3;
       
    %FFT of stack 1
    disp('calculating FFT of stack 1...');
    FFTstack1 = fftn(stack1);    
    clearvars -global stack1;
        
    %FFT of stack 2
    disp('calculating FFT of stack 2...');
    FFTstack2 = fftn(stack2);
    clearvars -global stack2;
        
    %FFT of stack 3
    disp('calculating FFT of stack 3...');
    FFTstack3 = fftn(stack3);
    clearvars -global stack3;
        
    disp('multiview combining...');
    
    %combine the phases
    CombinedPhase = atan2(imag(FFTstack1) + imag(FFTstack2) + imag(FFTstack3), real(FFTstack1) + real(FFTstack2) + real(FFTstack3));
            
    %calculate the magnitudes
    mag1 = abs(FFTstack1);
    clearvars FFTstack1;
    mag2 = abs(FFTstack2);
    clearvars FFTstack2;
    mag3 = abs(FFTstack3);
    clearvars FFTstack3;
      
    %combine magnitudes
    CombinedMag = max(max(mag1, mag2), mag3);   
    clearvars mag1 mag2 mag3;
    
    %recombine magnitudes and phases and do backward transform;
    Combined = ifftn(cos(CombinedPhase) .* CombinedMag + 1i .* sin(CombinedPhase) .* CombinedMag);
    clearvars CombinedMag CombinedPhase;
       
    %clip negative values 
    Combined(Combined < 0) = 0;   
end

function R = percentile(X, p)
	X = sort(X(:));
	index = round(0.01 * p * numel(X));
	R = X(index);
end
