%% Cluster Line Points
%
% Cluster the line segments represented by (pts, lineSizeMat) into K
% different groups based on orientation of lines.
% The (pts,lineSizeMat) is modified to reflect new grouping structure.
%

%% Input Arguments

% K - Number of desired groups, in which to cluster line-points.
%     type: positive integer

% 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: 1xL 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

% tol - Optional convergence tolerance of cluster means. K-means iterations
%       terminate if change in "cluster-means" < angular-tolerance.
%       type: positive scalar


%% 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. 


%% Cluster Line Points

function [pts, lineSizeMat] = cluster_line_points(K, pts, lineSizeMat, scale, varargin)

debugEnable = false;

% Check input parameters
[K, pts, lineSizeMat, scale, tol] = input_parser(K, pts, lineSizeMat, scale, varargin{:});

% Undo scaling (from normalization) on the line-points
pts = pts*diag(scale);

% Compute orientation of each line point. Aside, need to treat lineSizeMat
% as L groups of a single line in order to be compatible with 
% line_orientation_group.m
[orientation, ~] = line_orientation_group(pts, transpose(lineSizeMat));

% Cluster line segments into K different groups, and compute group
% associations
assoc = angular_kmeans(K, orientation, 'tol', tol, 'isLines', true);

% Reconfigure (pts, lineSizeMat) into K different groups
nLines = length(lineSizeMat);
endIdx = cumsum(lineSizeMat);
newLineSizeMat = nan(K, nLines);
newPts = cell(K,nLines);
startIdx = 1;

for i = 1:nLines,

    groupIdx = assoc(i);
    newLineSizeMat(groupIdx, i) = endIdx(i) - startIdx + 1;
    newPts{groupIdx, i} = pts(startIdx:endIdx(i), :);
    startIdx = endIdx(i)+1;

end

nGroupLines = sum(~isnan(newLineSizeMat), 2);
nMaxGroupLines = max(nGroupLines);
lineSizeMat = zeros(K, nMaxGroupLines);
for g = 1:K,
    lineSizeMat(g, 1:nGroupLines(g)) = newLineSizeMat(g, ~isnan(newLineSizeMat(g,:))); 
end

pts = transpose(newPts);
pts = cell2mat(pts(:));

% Normalize the line-points
pts = pts*diag(1./scale);

% Plot line points with different group-specific highlighting
if debugEnable
    
    figure;
    ax1 = gca;
    
    idx = 1;
    colors = jet(K);
    for g = 1:K,
        
        nPts = sum(lineSizeMat(g,:));
        hold on; scatter(ax1, pts(idx:idx+nPts-1, 1), pts(idx:idx+nPts-1, 2), 8, colors(g,:));
        idx = idx + nPts;
        
    end    
    
end


end


%% Input Parser

function [K, pts, lineSizeMat, scale, tol] = input_parser(K, pts, lineSizeMat, scale, varargin)

% parse input arguments
p = inputParser;
p.addRequired('K', @(x) ...
              validateattributes(x,{'numeric'},{'real','nonnan','integer','positive','finite'}));
p.addRequired('pts', @(x) ...
              validateattributes(x,{'numeric'},{'real','nonnan','2d','finite'}));
p.addRequired('lineSizeMat', @(x) ...
              validateattributes(x,{'numeric'},{'real','nonnan','2d','finite'}));   
p.addRequired('scale', @(x) ...
              validateattributes(x,{'numeric'},{'real','nonnan','vector','positive','finite','numel',2}));          
p.addParameter('tol', 1e-3, @(x) ...
              validateattributes(x,{'numeric'},{'real','nonnan','scalar','positive','finite'}));          
p.parse(K, pts, lineSizeMat, scale, varargin{:});
inputs       = p.Results;
K            = inputs.K; 
pts          = inputs.pts;
lineSizeMat  = inputs.lineSizeMat;
scale        = inputs.scale;
tol          = inputs.tol;

if size(lineSizeMat,1) > 1
    %warning('ClusterLinePts:InvalidLineSizeMat', 'LineSizeMat divided into groups, clustering will undo prior grouping');
    lineSizeMat = reshape(transpose(lineSizeMat), 1, []);
end

end %end of input parser










