function [features, valid_points] = extractFeatures(I, points, varargin)
%extractFeatures Extract interest point descriptors
%   extractFeatures extracts feature vectors, also known as descriptors,
%   from a binary or intensity image. Descriptors are derived from pixels
%   surrounding an interest point. They are needed to describe and match 
%   features specified by a single point location.
%
%   [FEATURES, VALID_POINTS] = extractFeatures(I, POINTS) returns FEATURES,
%   an M-by-N matrix of feature vectors, also known as descriptors, and
%   M number of VALID_POINTS associated with each descriptor of length N.
%   The method used for descriptor extraction depends on class of POINTS:
%
%     Class of POINTS            Descriptor extraction method
%     ---------------            ---------------------------------
%     - SURFPoints object        - Speeded-Up Robust Features (SURF)
%     - M-by-2 matrix of [x y]   - Simple square neighborhood around [x y]
%       coordinates                point location
%
%   [FEATURES, VALID_POINTS] = extractFeatures(I, POINTS, PARAM1, VAL1,
%   PARAM2, VAL2) can be used to specify additional parameters:
%
%   'Method'    - One of the strings: 'Block', 'SURF' or 'Auto'. 
%
%      Method       Feature vector (descriptor)
%      -------      -----------------------------------
%      'Block'      Simple square neighborhood
%      'SURF'       Speeded-Up Robust Features (SURF)
%      'Auto'       Selects the method based on the class of 
%                   input points: 'SURF' when POINTS is a SURFPoints 
%                   object and 'Block' when POINTS is an M-by-2 matrix 
%                   of [x y] coordinates
%
%                 Default: 'Auto'
%
%   'BlockSize' - An odd integer scalar defining the local square 
%                 neighborhood (BlockSize-by-BlockSize) centered at
%                 each interest point. This option is only
%                 applicable to 'Block' method.
%
%                 Default: 11
%
%   'SURFSize'  - Integer scalar set to 64 or 128. Length of the SURF
%                 feature vector (descriptor). This option is only
%                 applicable to 'SURF' method.
%
%                 Default: 64
%
%   Notes
%   -----
%   - When 'Block' method is used, the function extracts only the 
%     neighborhoods fully contained within the image boundary, therefore 
%     VALID_POINTS may contain fewer points than input POINTS.
%
%   - When SURF is used to extract descriptors, the Orientation property
%     of returned VALID_POINTS SURFPoints object is set to the orientation 
%     of extracted features, in radians. This is useful for visualizing 
%     the SURF descriptor orientation.
%
%   - You can increase 'SURFSize' from the default 64 to 128 to increase
%     descriptor matching accuracy at the expense of matching speed.
%
%   Class Support
%   -------------
%   The input image I can be logical, uint8, uint16, int16, single, or 
%   double, and it must be real and nonsparse. POINTS can be int16, uint16,
%   int32, uint32, single or double, and it must be real and nonsparse.
%
%   Example 1
%   ---------
%   % Extract corner features from an image.
%   I = imread('pout.tif');
%  
%   hcornerdet = vision.CornerDetector;
%   points = step(hcornerdet, I);
%  
%   [features, valid_points] = extractFeatures(I, points);
%  
%   figure; imshow(I); hold on 
%   % Plot interest points
%   plot(points(:,1), points(:,2), 'b.')
%   % Overlay valid interest points
%   plot(valid_points(:,1), valid_points(:,2), 'y.')
%
%   Example 2
%   ---------
%   % Extract SURF features
%   I = imread('cameraman.tif');
%   points = detectSURFFeatures(I);
%
%   [features, valid_points] = extractFeatures(I, points);
%
%   % Visualize 10 strongest SURF features, including their scale, and 
%   % orientation which was determined during the descriptor extraction
%   % process.
%   figure; imshow(I); hold on;
%   plot(valid_points.selectStrongest(10),'showOrientation',true);
%
% See also vision.CornerDetector, detectSURFFeatures, matchFeatures.

% Copyright 2010 The MathWorks, Inc.
% $Revision: 1.1.6.6 $  $Date: 2011/05/09 03:42:52 $

% Parse and check inputs
isXY = cvstGetCoordsChoice('fcnExtractFeatures');
[method, blockSize, SURFSize] = parseInputs(I, points, isXY, varargin{:});

% Extract features from image I
if strcmp(method, 'Block')
    if isa(points,'SURFPoints')
        points = points.Location;
    elseif ~isXY
        % Convert points from R/C, 0-based coordinates to X/Y, 1-based
        % coordinates.
        points = fliplr(points' + 1);
    end
    
    [features, valid_points] = cvalgExtractFeatures(I, points, blockSize);
                                                  
    if ~isXY
        features = features';
        valid_points = flipud(valid_points' - 1);
    end
else % SURF
    [Iu8,ptsStruct] = parseSURFInputs(I,points);
    % we are using cvExtractSurf with point inputs, therefore only
    % the "extended" parameter matters; the other can remain unset
    params.extended = SURFSize == 128;
    [vPts, features] = ocvExtractSurf(Iu8, ptsStruct, params);
    valid_points = SURFPoints(vPts.Location, vPts);    
end

%==========================================================================
% Parse and check inputs
%==========================================================================
function [method, blockSize,SURFSize] = ...
    parseInputs(I, points, isXY, varargin)

% Check image
checkImage(I);

% Setup parser
parser = inputParser;
parser.CaseSensitive = true;
parser.FunctionName  = mfilename;

parser.addParamValue('Method', 'Auto', @checkMethod);
parser.addParamValue('BlockSize', 11,  @checkBlockSize);
parser.addParamValue('SURFSize',  64,  @checkSURFSize);

% Parse input
parser.parse(varargin{:});

% Assign outputs
r = parser.Results;
[method blockSize SURFSize] = deal(r.Method, r.BlockSize, r.SURFSize);

% Handle 'Auto' choice
if strcmp(method,'Auto')
    if isa(points,'SURFPoints')
        method = 'SURF';
    else
        method = 'Block';
    end
end

% Check points
if ~isa(points, 'SURFPoints');    
    checkPoints(points, method, isXY);
end;

%==========================================================================
% Parse SURF inputs
%==========================================================================
function [Iu8, ptsStruct] = parseSURFInputs(I, points)

if isa(I,'uint8')
    Iu8 = I;
else
    Iu8 = im2uint8(I);
end

% If needed, convert raw [X,Y] coordinates to SURFPoints
if ~isa(points, 'SURFPoints')
    ptsObj = SURFPoints(points);
else
    ptsObj = points;
end

% convert SURFPoints object back to structure required by
% the ocvExtractSurf built-in function
ptsStruct.Location         = ptsObj.Location;
ptsStruct.Scale            = ptsObj.Scale;
ptsStruct.Metric           = ptsObj.Metric;
ptsStruct.SignOfLaplacian  = ptsObj.SignOfLaplacian;

%==========================================================================
function tf = checkImage(I)

validateattributes(I,{'logical', 'uint8', 'uint16', 'int16', 'single',...
                    'double'}, {'2d', 'nonempty', 'nonsparse', ...
                    'real'}, mfilename, 'I', 1);

tf = true;

%==========================================================================
function checkPoints(points, method, isXY)

if isXY || strcmp(method,'SURF')
    sz = [NaN 2]; % SURF doesn't use coordinate system compatibility layer
else
    sz = [2 NaN];
end

validateattributes(points,{'int16', 'uint16', 'int32', 'uint32', ...
    'single', 'double'}, {'2d', 'nonsparse', 'real', 'size', sz},...
    mfilename, 'POINTS', 2);

%==========================================================================
function tf = checkMethod(method)
validatestring(method,{'Block','SURF','Auto'},mfilename,'Method');
tf = true;

%==========================================================================
function tf = checkBlockSize(blockSize)
validateattributes(blockSize,{'numeric'}, {'nonempty', ...
    'finite', 'nonsparse', 'real', 'positive', 'integer', 'odd', ...
    'scalar'}, mfilename, 'BLOCK_SIZE');
tf = true;

%==========================================================================
function tf = checkSURFSize(SURFSize)
validateattributes(SURFSize,{'numeric'}, {'scalar'}, mfilename,...
    'SURF_SIZE');

assert(SURFSize == 64 || SURFSize == 128, ...
    'vision:extractFeatures:invalidSURFSize', ...
    '''SURF_SIZE'' must be set to 64 or 128');

tf = true;

