
%% Sub-pixel Accurate Line Segment Detection
%
% Computes the edges of an image and then subsequently groups pertinent
% edges into 'line segments'. The edges are further deduced to sub-pixel
% accuracy.
%
% References:
%1]Von Gioi, R. Grompone, et al. "LSD: a line segment detector." 
%  Image Processing On Line 2.3 (2012): 5.


%% Input Arguments

% img - Input grayscale or color image.
%       If color, applies gaussian filter on each channel seperately.
%       NxMx(3) matrix.

% angleAlign - Optional alignment angle. Filters out lines which are >angleTol 
%              from alignment with a line at angleAlign deg.
%              Scalar (0,360)

% angleTol - Optional angle tolerance used for LSD algorithm for region-growing.
%            Scalar (0,180)

% densityTol - Optional density tolerance.
%              Scalar [0,1]

%% Output Arguments

% lineLabels - Line label image. Each pixel is assigned a label: {0- no
%              line, 1- first line, ... , N- nth line}
%              NxMx(3) matrix.

% nLines - Number of line segments detected.
%          1x(3) vector.

% xDelta - Sub-pixel offset in x-direction (column)
%          NxMx(3) matrix.

% yDelta - Sub-pixel offset in y-direction (row)
%          NxMx(3) matrix.


%% Sub-pixel Accurate Edge Detector

function [lineLabels, nLines, xDelta, yDelta] = subpixel_lsd(img, varargin)

%general constants
SCALE_IMG    = 1.0;
NCOLOR_COMPS = 3;

%additional tools
addpath('./subpixel_lsd/');

%parse input parameters
[img, ~, ~, isColor, angleAlign, angleTol, densityTol] = input_parser(img, varargin{:});


%--------------------------------------------------
%		 <step 1>
%--------------------------------------------------
%description: scale (sub-sample) image

if SCALE_IMG ~= 1.0
    
    img = imresize(img ...
                   ,'Scale', SCALE_IMG ...
                   ,'Antialiasing', true ...
                   ,'Method', 'bicubic');

    [nRows, nCols, ~] = size(img);      
    
end


%--------------------------------------------------
%		 <step 2>
%--------------------------------------------------
%description: compute sub-pixel edges

[edges, xDelta, yDelta, Ix, Iy] = subpixel_edge(img);


%--------------------------------------------------
%		 <step 3>
%--------------------------------------------------
%description: compute gradient angle (normal-direction) 

angles = atan2(Ix, -Iy);


%--------------------------------------------------
%		 <step 4>
%--------------------------------------------------
%description: perform line-segment detection (LSD)

if isColor
    
    lineLabels = zeros(nRows, nCols, NCOLOR_COMPS);
    nLines     = zeros(1, NCOLOR_COMPS);
    lineStats  = cell(1, NCOLOR_COMPS);
    
    for i = 1:NCOLOR_COMPS,    
        [lineLabels(:,:,i), nLines(i), lineStats{i}] = subpixel_lsd_c( edges(:,:,i), angles(:,:,i), ...
                                                                       angleTol, densityTol);
    end

else
    
    [lineLabels, nLines, lineStats] = subpixel_lsd_c( edges, angles, angleTol, densityTol );
    
end


%--------------------------------------------------
%		 <step 5>
%--------------------------------------------------
%description: prune line segments which are not aligned with angleAlign

if ~isnan(angleAlign)
    
    if isColor
        
        for i = 1:NCOLOR_COMPS,
            [lineLabels(:,:,i), nLines(i)] = refine_lines(lineLabels(:,:,i), nLines(i), lineStats{i}, angleAlign, angleTol);
        end
    
    else
    
        [lineLabels, nLines] = refine_lines(lineLabels, nLines, lineStats, angleAlign, angleTol);
        
    end
    
end

fprintf('Found %d line-segments\n',sum(nLines));

end % end of subpixel edge


%% Helper Functions

function [lineLabels, nLines] = refine_lines(lineLabels, nLines, lineStats, angleAlign, angleTol)

fprintf('Pruned line segments (#): ');
for i = 1:nLines
   
    angle = radtodeg(lineStats(i,7));
    
    if abs(cosd(angle-angleAlign)) < cosd(angleTol)
        
        lineLabels(lineLabels==i) = 0;        
        nLines = nLines - 1;    
        
        fprintf('%d, ',i);
        
    end
    
end

fprintf(';\n');

end


%% Input Parser

function [img, nRows, nCols, isColor, angleAlign, angleTol, densityTol] = input_parser(img, varargin)

% parse input arguments
p = inputParser;
p.addRequired('img', @(x) ...
				validateattributes(x,{'numeric'},{'real','nonnan'}));
p.addParameter('angleAlign', NaN, @(x) ...
				validateattributes(x,{'numeric'},{'real','nonnan','positive','<',360}));            
p.addParameter('angleTol', 22.5, @(x) ...
				validateattributes(x,{'numeric'},{'real','nonnan','positive','<',180}));
p.addParameter('densityTol', 0.7, @(x) ...
				validateattributes(x,{'numeric'},{'real','nonnan','nonnegative','<',1}));            
p.parse(img, varargin{:});
inputs     = p.Results;
img        = inputs.img;
angleAlign = inputs.angleAlign; 
angleTol   = inputs.angleTol;
densityTol = inputs.densityTol;

[nRows, nCols, nComps] = size(img);

if nComps == 3
    isColor = true;
elseif nComps == 1
    isColor = false;
else
    error('subPxlLsd:InvalidImg','Input image should be gray-scale or color image');
end  

end















