% PLOTGABORFILTERS - Plots log-Gabor filters
%
% The purpose of this code is to see what effect the various parameter
% settings have on the formation of a log-Gabor filter bank.
%
% Usage: [Ffilter, Efilter, Ofilter] = plotgaborfilters(sze,  nscale, norient,...
%                                       minWaveLength, mult, sigmaOnf, dThetaOnSigma)
%
% Arguments:
% Many of the parameters relate to the specification of the filters in the frequency plane.  
%
%   Variable       Suggested   Description
%   name           value
%  ----------------------------------------------------------
%    sze             = 200     Size of image grid on which the filters
%                              are calculated.  Note that the actual size
%                              of the filter is really specified by its
%                              wavelength. 
%    nscale          = 4;      Number of wavelet scales.
%    norient         = 6;      Number of filter orientations.
%    minWaveLength   = 3;      Wavelength of smallest scale filter.
%    mult            = 1.7;    Scaling factor between successive filters.
%    sigmaOnf        = 0.65;   Ratio of the standard deviation of the
%                              Gaussian describing the log Gabor filter's
%                              transfer function in the frequency domain
%                              to the filter center frequency. 
%    dThetaOnSigma   = 1.3;    Ratio of angular interval between filter
%                              orientations and the standard deviation of
%                              the angular Gaussian function used to
%                              construct filters in the freq. plane.
%
% Note regarding the specification of norient: In the default case it is assumed
% that the angles of the filters are evenly spaced at intervals of pi/norient
% around the frequency plane.  If you want to visualize filters at a specific
% set of orientations that are not necessarily evenly spaced you can set the
% orientations by passing a CELL array of orientations as the argument to
% norient. In this case the value supplied for dThetaOnSigma will be used as
% thetaSigma - the angular standard deviation of the filters.  Yes, this is
% an ugly abuse of the argument list, but there it is!
% Example:
% View filters over 3 scales with orientations of -0.3 and +0.3 radians,
% minWaveLength of 6, mult of 2, sigmaOnf of 0.65 and thetaSigma of 0.4 
%   plotgaborfilters2(200, 3, {-.3 .3}, 6, 2, 0.65, 0.4);
%
% Returns:
%    Ffilter - a 2D cell array of filters defined in the frequency domain.
%    Efilter - a 2D cell array of even filters defined in the spatial domain.
%    Ofilter - a 2D cell array of odd filters defined in the spatial domain.
%
%    Ffilter{s,o} = filter for scale s and orientation o.
%    The even and odd filters in the spatial domain for scale s,
%    orientation o, are obtained using.              
%
%    Efilter = ifftshift(real(ifft2(fftshift(filter{s,o}))));
%    Ofilter = ifftshift(imag(ifft2(fftshift(filter{s,o}))));
%
% Plots:
%    Figure 1         -  Sum of the filters in the frequency domain
%    Figure 2         -  Cross sections of Figure 1
%    Figures 3 and 4  -  Surface and intensity plots of filters in the
%                        spatial domain at the smallest and largest
%                        scales respectively.
%
% See also: GABORCONVOLVE, PHASECONG

% Copyright (c) 2001-2008 Peter Kovesi
% www.peterkovesi.com
% 
% Permission is hereby granted, free of charge, to any person obtaining a copy
% of this software and associated documentation files (the "Software"), to deal
% in the Software without restriction, subject to the following conditions:
% 
% The above copyright notice and this permission notice shall be included in 
% all copies or substantial portions of the Software.
%
% The Software is provided "as is", without warranty of any kind.

% May 2001      - Original version.
% February 2005 - Cleaned up.
% August   2005 - Ffilter,Efilter and Ofilter corrected to return with scale
%                 varying as the first index in the cell arrays.
% July 2008     - Allow specific filter orientations to be specified in
%                 norient via a cell array.
% March 2013    - Restored use of dThetaOnSigma to control angular spread of filters.

function [Ffilter, Efilter, Ofilter, filtersum] = ...
	plotgaborfilters(sze, nscale, norient, minWaveLength, mult, ...
				   sigmaOnf, dThetaOnSigma) 

    if ~exist('dThetaOnSigma','var') || isempty(dThetaOnSigma), dThetaOnSigma = 1.3;  end    

    if length(sze) == 1
        rows = sze; cols = sze;
    else
        rows = sze(1); cols = sze(2);
    end
    
    if iscell(norient)   % Filter orientations and spread have been specified
                         % explicitly
        filterOrient = cell2mat(norient);
        thetaSigma = dThetaOnSigma;  % Use dThetaOnSigma directly as thetaSigma 
        norient = length(filterOrient);
	
    else                 % Usual setup with filters evenly oriented
	filterOrient = [0 : pi/norient : pi-pi/norient];
	
	% Calculate the standard deviation of the angular Gaussian function
	% used to construct filters in the frequency plane.     
	thetaSigma = pi/norient/dThetaOnSigma;
    end
    
    % Double up all the filter orientations by adding another set offset by
    % pi.  This allows us to see the overall orientation coverage of the
    % filters a bit more easily.
    filterOrient = [filterOrient filterOrient+pi];  
    
    % Pre-compute some stuff to speed up filter construction
    
    % Set up X and Y matrices with ranges normalised to +/- 0.5
    % The following code adjusts things appropriately for odd and even values
    % of rows and columns.
    if mod(cols,2)
	xrange = [-(cols-1)/2:(cols-1)/2]/cols;
    else
	xrange = [-cols/2:(cols/2-1)]/cols;	
    end
    
    if mod(rows,2)
	yrange = [-(rows-1)/2:(rows-1)/2]/rows;
    else
	yrange = [-rows/2:(rows/2-1)]/rows;	
    end
    
    [x,y] = meshgrid(xrange, yrange);
    
    radius = sqrt(x.^2 + y.^2);       % Normalised radius (frequency) values 0.0 - 0.5

    % Get rid of the 0 radius value in the middle so that taking the log of
    % the radius will not cause trouble.
    radius(fix(rows/2+1),fix(cols/2+1)) = 1; 
    
    theta = atan2(-y,x);              % Matrix values contain polar angle.
				      % (note -ve y is used to give +ve
				      % anti-clockwise angles)
    sintheta = sin(theta);
    costheta = cos(theta);
    clear x; clear y; clear theta;    % save a little memory

    % Define a low-pass filter that is as large as possible, yet falls away to zero
    % at the boundaries.  All log Gabor filters are multiplied by this to ensure
    % that filters are as similar as possible across orientations (Eliminate the
    % extra frequencies at the 'corners' of the FFT)
    lp = fftshift(lowpassfilter([rows,cols],.45,10));   % Radius .4, 'sharpness' 10

    % The main loop...

    filtersum = zeros(rows,cols);

    for o = 1:2*norient,                   % For each orientation.
	angl = filterOrient(o);
	wavelength = minWaveLength;        % Initialize filter wavelength.

	% Compute filter data specific to this orientation
	% For each point in the filter matrix calculate the angular distance from the
	% specified filter orientation.  To overcome the angular wrap-around problem
	% sine difference and cosine difference values are first computed and then
	% the atan2 function is used to determine angular distance.
	
	ds = sintheta * cos(angl) - costheta * sin(angl); % Difference in sine.
	dc = costheta * cos(angl) + sintheta * sin(angl); % Difference in cosine.
	dtheta = abs(atan2(ds,dc));                       % Absolute angular distance.

        if ~dThetaOnSigma  % If set to 0 use cosine angular weight spread
                           % function. (I do not think this is a good spread function)
                           
            dtheta = min(dtheta*norient/2,pi);
            spread = (cos(dtheta)+1)/2;
        else               % Use a Gaussian angular spread function
            spread = exp((-dtheta.^2) / (2 * thetaSigma^2));  
        end
        
	for s = 1:nscale,                  % For each scale.
	   
	    % Construct the filter - first calculate the radial filter component.
	    fo = 1.0/wavelength;                  % Centre frequency of filter.
	    
	    logGabor = exp((-(log(radius/fo)).^2) / (2 * log(sigmaOnf)^2));  
	    logGabor(round(rows/2+1),round(cols/2+1)) = 0; % Set value at center of the filter
							   % back to zero (undo the radius fudge).
							   
            logGabor = logGabor.*lp;             % Apply low-pass filter 
	    Ffilter{s,o} = logGabor .* spread;   % Multiply by the angular
						 % spread to get the filter.
					       
            filtersum = filtersum + Ffilter{s,o}.^2;
						 
	    Efilter{s,o} = ifftshift(real(ifft2(fftshift(Ffilter{s,o}))));
	    Ofilter{s,o} = ifftshift(imag(ifft2(fftshift(Ffilter{s,o}))));
    					  
	    wavelength = wavelength*mult;
	end
    end

    % Plot sum of filters and slices radially and tangentially
    figure(1), clf, show(filtersum,1), title('sum of filters');
    
    figure(2), clf
    subplot(2,1,1), plot(filtersum(round(rows/2+1),:))
    title('radial slice through sum of filters');

    hold on
    for s = 1:nscale
        plot(Ffilter{s,1}(round(rows/2+1),:), 'r-')
    end
    hold off
    
    ang = [0:pi/32:2*pi];
    r = rows/4;
    tslice = improfile(filtersum,r*cos(ang)+cols/2,r*sin(ang)+rows/2);
    subplot(2,1,2), plot(tslice), axis([0 length(tslice) 0 1.1*max(tslice)]);
    title('tangential slice through sum of filters at f = 0.25');	   

    % Plot Even and Odd filters at the largest and smallest scales
    h = figure(3); clf
    set(h,'name',sprintf('Filters: Wavelenth = %.2f',minWaveLength));
    subplot(3,2,1), surfl(Efilter{1,1}), shading interp, colormap(gray), 
    title('Even Filter');
    subplot(3,2,2), surfl(Ofilter{1,1}), shading interp, colormap(gray)
    title('Odd Filter');
    subplot(3,2,3),imagesc(Efilter{1,1}), axis image, colormap(gray)
    subplot(3,2,4),imagesc(Ofilter{1,1}), axis image, colormap(gray)
    subplot(3,2,5),imagesc(Ffilter{1,1}), axis image, colormap(gray)
    title('Frequency Domain');
    
    h = figure(4); clf
    set(h,'name',sprintf('Filters: Wavelenth = %.2f',minWaveLength*mult^(nscale-1)));
    subplot(3,2,1), surfl(Efilter{nscale,1}), shading interp, colormap(gray)
    title('Even Filter');
    subplot(3,2,2), surfl(Ofilter{nscale,1}), shading interp, colormap(gray)
    title('Odd Filter');
    subplot(3,2,3),imagesc(Efilter{nscale,1}), axis image, colormap(gray)
    subplot(3,2,4),imagesc(Ofilter{nscale,1}), axis image, colormap(gray)
    subplot(3,2,5),imagesc(Ffilter{nscale,1}), axis image, colormap(gray)
    title('Frequency Domain');