
%% Extract Line Points 
%
% Applies line-segment detection to an image sequence. Subsequently the
% detected lines are resampled uniformly along their lengths and decimated
% to a preset number of points per line.
%

%% Input Arguments

% seqName - Image sequence name.
%           String.

% dirPath - Optional parent directory path (relative userpath) to image sequence.
%           String (default: '.')

% minLinePts - Optional minimum number of line points. Lines below
%              threshold are pruned.
%              Scalar (>0; default: 0)

% ptsPerLine - Optional number of points per line for uniform resampling.
%              Scalar (>0; default: 75) 

% decFactor - Optional decimation factor for uniformly resampled line points.
%             Scalar (>=1; default: 3)

% angleTol - Optional angle tolerance.
%            Scalar ( [0, 90]; default: 20 )

% densityTol - Optional density tolerance.
%              Scalar ([0,1]; default: 0.5 )

% scaleMethod - Optional scaling method designation for normalization: 
%               (a) 'isotropic' scaling will scale (1/N) the coordinates by the
%               largest dimension of the image
%               (b) 'anisotropic' scaling will scale each dimension of the 
%               coordinate by the respective dimension of the image
%               type: String {'isotropic', 'anisotropic' (default)}

% meanMethod - Optional mean method designation for normalization:
%              (a) 'auto' - subtracts the mean of the line point coordinates
%              after scaling        
%              (b) 'fixed' - subtracts [1/2, 1/2] from each scaled
%              coordinate
%              type: String {'auto', 'fixed' (default)}

% roi - Optional region of interest specified as a bounding box: [x, y,
%       width, height]. Bounding box parameters provided in normalized
%       coordinates. Ex. [0.5, 0.5, 0.25, 0.30] => [0.5*nCols, 0.5*nRows,
%       0.25*nCols, 0.30*nRows]
%       type: 4x1 vector

% isNorm - Optional return points normalized.
%          Boolean (default: true)


%% Output Arguments

% pts - Normalized coordinate matrix containing ordered list of all line points
%       grouped hiearchically by line and then group (image)
%       type: Nx2 matrix.

% lineSizeMat - Size of sets matrix. Columns correspond to line index,
%               and rows correspond to group (image) index. To deduce number of points 
%               within line segment with group and line index i and j, 
%               respectively then N = setSize(i,j)
%               type: GxL matrix. 

% scale - Inverses of the scaling factors (i.e. 1/q, q=scaling factor)  
%         along each dimension of image. Format: (x/col-scale; y/row-scale)
%         type: 2x1 vector

% offset - Subtracted 'mean' or 'offset' from the scaled coordinates.
%          Needed for reconstructing the normalized coordinates. 
%          Format: (x/col-offset; y/row-offset)
%          type: 2x1 vector


%% Extract Line Points

function [pts, lineSizeMat, scale, offset] = extract_line_points(seqName, varargin)

%add lsd tools
matlabDir = userpath;
addpath(strcat(matlabDir(1:end-1),'/MASC/Image Processing Tools/Edge Detection/'));
addpath(strcat(matlabDir(1:end-1),'/MASC/Camera Calibration Tools/Nonmetric Calibration/Calibration Harp/'));

%parse input parameters
[~, dirPath, minLinePts, ptsPerLine, decFactor, angleTol, densityTol, isNorm, scaleMethod, meanMethod, roi, nRows, nCols] = input_parser(seqName, varargin{:});


%--------------------------------------------------
%		 <step 1>
%--------------------------------------------------
%description: compute subpixel-accurate line-segments

[pts, lineSizeMat] = subpixel_lsd_group(seqName, 'dirPath', dirPath, 'angleTol', angleTol, 'densityTol', densityTol);


%--------------------------------------------------
%		 <step 2a>
%--------------------------------------------------
%description: refine line-segments

[pts, lineSizeMat] = reduce_points(pts, lineSizeMat, minLinePts, 'isNorm', false);

[pts, lineSizeMat] = subsample_lines(pts, lineSizeMat, ptsPerLine, decFactor); 


%--------------------------------------------------
%		 <step 2b>
%--------------------------------------------------
%description: window-select only lines 
%lying within region of interest (roi)

roiMask = roipoly(zeros(nRows, nCols), ...
                  nCols*[roi(1); roi(1); roi(1)+roi(3); roi(1)+roi(3)], ...
                  nRows*[roi(2); roi(2)+roi(4); roi(2)+roi(4); roi(2)]);
              
[pts, lineSizeMat] = reduce_points(pts, lineSizeMat, minLinePts, 'isNorm', false, 'lineMask', double(roiMask));  


%--------------------------------------------------
%		 <step 2c>
%--------------------------------------------------
%description: normalize points (assume distortion center is at principal
%point initially)

if isNorm
   
    nPts  = size(pts,1);
    
    if strcmp(scaleMethod, 'isotropic')  
        
        div = max(nCols, nRows);
        scale = [div; div];
                           
    else
        
        scale = [nCols; nRows];
        
    end
    
    pts(:,1) = pts(:, 1)/scale(1);
    pts(:,2) = pts(:, 2)/scale(2);
    
    if strcmp(meanMethod, 'fixed')
        
        offset = [1/2; 1/2];
        
    else
        
        offset = transpose(mean(pts, 1));       
        
    end    
        
    pts(:,1) = pts(:,1) - offset(1)*ones(nPts, 1);
    pts(:,2) = pts(:,2) - offset(2)*ones(nPts, 1);    
    
end


end


%% Input Parser

function [imgSeqFileNames, dirPath, minLinePts, ptsPerLine, decFactor, angleTol, densityTol, isNorm, scaleMethod, meanMethod, roi, nRows, nCols] = input_parser(seqName, varargin)

matlabDir = userpath;
matlabDir = matlabDir(1:end-1);

% parse input arguments
p = inputParser;
p.addRequired('seqName', @(x) ischar(x));	
p.addParameter('dirPath', '', @(x) ischar(x));  
p.addParameter('minLinePts', 0, @(x) ...
                validateattributes(x,{'numeric'},{'real','nonnegative','finite','scalar'}));
p.addParameter('ptsPerLine', 75, @(x) ...
                validateattributes(x,{'numeric'},{'real','positive','finite','scalar','integer'}));
p.addParameter('decFactor', 3, @(x) ...
                validateattributes(x,{'numeric'},{'real','positive','finite','scalar','>',1,'integer'}));
p.addParameter('angleTol', 20, @(x) ...
                validateattributes(x,{'numeric'},{'real','nonnegative','nonnan','scalar','<',90}));            
p.addParameter('densityTol', 20, @(x) ...
                validateattributes(x,{'numeric'},{'real','nonnegative','nonnan','scalar','<',1}));
p.addParameter('scaleMethod', 'anisotropic', @(x) ...
                ischar(validatestring(x, {'isotropic', 'anisotropic'})));
p.addParameter('meanMethod', 'fixed', @(x) ...
                ischar(validatestring(x, {'auto', 'fixed'})));            
p.addParameter('isNorm', true, @(x) ...
                validateattributes(x,{'logical'},{'nonnan'}));            
p.addParameter('roi', [0,0,1,1], @(x) ...
                validateattributes(x,{'numeric'},{'real', 'nonnegative', 'finite', 'vector', '<=', 1, 'numel', 4}));
            
p.parse(seqName, varargin{:});
inputs           = p.Results;
seqName          = inputs.seqName;
dirPath          = inputs.dirPath;
minLinePts       = inputs.minLinePts;
ptsPerLine       = inputs.ptsPerLine;
decFactor        = inputs.decFactor;
angleTol         = inputs.angleTol;
densityTol       = inputs.densityTol;
scaleMethod      = inputs.scaleMethod;
meanMethod       = inputs.meanMethod;
isNorm           = inputs.isNorm;
roi              = inputs.roi;

if isempty(dirPath)
    fullDirPath = '.';
else
    if regexp(dirPath, matlabDir)
        fullDirPath = dirPath;
    elseif strncmpi(dirPath, '.', 1)
        fullDirPath = dirPath;
    else
        fullDirPath = fullfile(matlabDir, dirPath);
    end
end

%parse sequence name for excluded files
% ex. 'hi ^bye ^hello' = {'hi','bye','hello'}; where ^ is used to indicate
% exlcuded files
comps = strsplit(seqName, '\s*\^', 'DelimiterType', 'RegularExpression');
seqName = comps{1};

%check image sequence name exists
seqPath         = fullfile(fullDirPath, strcat(seqName,'*'));
dirStruct       = dir(seqPath);
imgSeqFileNames = {dirStruct.name};

if isempty(imgSeqFileNames)
    error('Image sequence cannot be found at %s', fullDirPath)
end

nImgs = length(imgSeqFileNames);

%exclude any files specified in sequence name
if length(comps) > 1

    tmp = cell(nImgs, 1);
    idx = 1;
    for j = 1:nImgs,
        exclImg = false;
        for i = 2:length(comps),
            if strfind(imgSeqFileNames{j}, comps{i})
                exclImg = true;
                break;
            end
        end
        if ~exclImg
            tmp{idx} = imgSeqFileNames{j};
            idx = idx + 1;
        end
    end
    imgSeqFileNames = tmp(1:idx-1);
    
end

firstImg = imread(fullfile(fullDirPath, imgSeqFileNames{1}));
[nRows, nCols, ~] = size(firstImg);

%check roi region is within bounds of image
if sum(roi([1,3])) > 1 || sum(roi([2,4])) > 1
    error('ExtractLinePts:InvalidRoi', 'Invalid region of interest');
end

end
