% FASTRADIAL - Loy and Zelinski's fast radial feature detector
%
% An implementation of Loy and Zelinski's fast radial feature detector
%
% Usage: S = fastradial(im, radii, alpha, beta)
%
% Arguments:
%            im    - Image to be analysed
%            radii - Array of integer radius values to be processed
%                    suggested radii might be [1 3 5]
%            alpha - Radial strictness parameter.
%                    1 - slack, accepts features with bilateral symmetry.
%                    2 - a reasonable compromise.
%                    3 - strict, only accepts radial symmetry.
%                        ... and you can go higher
%            beta  - Gradient threshold.  Gradients below this threshold do
%                    not contribute to symmetry measure, defaults to 0.
%
% Returns    S     - Symmetry map.  Bright points with high symmetry are
%                    marked with large positive values. Dark points of
%                    high symmetry marked with large -ve values.
%
% To localize points use NONMAXSUPPTS on S, -S or abs(S) depending on
% what you are seeking to find.

% Reference:
% Loy, G.  Zelinsky, A.  Fast radial symmetry for detecting points of
% interest.  IEEE PAMI, Vol. 25, No. 8, August 2003. pp 959-973.

% Copyright (c) 2004-2010 Peter Kovesi
% www.peterkovesi.com/matlabfns/
% 
% 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.

% November 2004  - original version
% July     2005  - Bug corrected: magitude and orientation matrices were
%                  not zeroed for each radius value used (Thanks to Ben
%                  Jackson) 
% December 2009  - Gradient threshold added + minor code cleanup
% July     2010  - Gradients computed via Farid and Simoncelli's 5 tap
%                  derivative filters

function [S, So] = fastradial(im, radii, alpha, beta, feedback)
    
    if ~exist('beta','var'),     beta = 0;     end
    if ~exist('feedback','var'), feedback = 0; end    
    
    if any(radii ~= round(radii)) || any(radii < 1)
        error('radii must be integers and > 1')
    end
    
    [rows,cols]=size(im);
    
    % Compute derivatives in x and y via Farid and Simoncelli's 5 tap
    % derivative filters
    [imgx, imgy] = derivative5(im, 'x', 'y');
    mag = sqrt(imgx.^2 + imgy.^2)+eps; % (+eps to avoid division by 0)
    
    % Normalise gradient values so that [imgx imgy] form unit 
    % direction vectors.
    imgx = imgx./mag;   
    imgy = imgy./mag;
    
    S = zeros(rows,cols);  % Symmetry matrix
    So = zeros(rows,cols); % Orientation only symmetry matrix    
    
    [x,y] = meshgrid(1:cols, 1:rows);
    
    for n = radii
        M = zeros(rows,cols);  % Magnitude projection image
        O = zeros(rows,cols);  % Orientation projection image

        % Coordinates of 'positively' and 'negatively' affected pixels
        posx = x + round(n*imgx);
        posy = y + round(n*imgy);
        
        negx = x - round(n*imgx);
        negy = y - round(n*imgy);
        
        % Clamp coordinate values to range [1 rows 1 cols]
        posx( posx<1 )    = 1;
        posx( posx>cols ) = cols;
        posy( posy<1 )    = 1;
        posy( posy>rows ) = rows;
        
        negx( negx<1 )    = 1;
        negx( negx>cols ) = cols;
        negy( negy<1 )    = 1;
        negy( negy>rows ) = rows;
        
        % Form the orientation and magnitude projection matrices
        for r = 1:rows
            for c = 1:cols
                if mag(r,c) > beta
                    O(posy(r,c),posx(r,c)) = O(posy(r,c),posx(r,c)) + 1;
                    O(negy(r,c),negx(r,c)) = O(negy(r,c),negx(r,c)) - 1;
                    
                    M(posy(r,c),posx(r,c)) = M(posy(r,c),posx(r,c)) + mag(r,c);
                    M(negy(r,c),negx(r,c)) = M(negy(r,c),negx(r,c)) - mag(r,c);
                end
            end
        end
        
        % Clamp Orientation projection matrix values to a maximum of 
        % +/-kappa,  but first set the normalization parameter kappa to the
        % values suggested by Loy and Zelinski
        if n == 1, kappa = 8; else kappa = 9.9; end
        
        O(O >  kappa) =  kappa;  
        O(O < -kappa) = -kappa;  
        
        % Unsmoothed symmetry measure at this radius value
        F = M./kappa .* (abs(O)/kappa).^alpha;
        Fo = sign(O) .* (abs(O)/kappa).^alpha;   % Orientation only based measure
        
        % Smooth and spread the symmetry measure with a Gaussian proportional to
        % n.  Also scale the smoothed result by n so that large scales do not
        % lose their relative weighting.
        S  = S + gaussfilt(F,  0.25*n) * n;
        So = So + gaussfilt(Fo, 0.25*n) * n;        
        
    end  % for each radius
    
    S  = S /length(radii);  % Average
    So = So/length(radii); 