%%
%
% I used a few references to complete this assignment
% http://www.csse.uwa.edu.au/~pk/research/matlabfns/
%    for the homography denormalization
%
% http://en.wikipedia.org/wiki/RANSAC
%    basic ransac overview
%
% I consulted with Zhimin to figure out some of the problems
%
%

function fitting()

    % part 1
    %house_image1 = imread('house1.jpg');
    house_image2 = imread('house2.jpg');
    house_matches = load('house_matches.txt');
    house_camera1 = load('house1_camera.txt');
    house_camera2 = load('house2_camera.txt');

    %library_image1 = imread('library1.jpg');
    library_image2 = imread('library2.jpg');
    library_matches = load('library_matches.txt');
    library_camera1 = load('library1_camera.txt');
    library_camera2 = load('library2_camera.txt');

    get_figure_number(1);
    
    %part 2
    show_simple_fit(@least_squares_fit, @affine_estimate, house_matches, house_image2);
    show_simple_fit(@least_squares_fit, @affine_estimate, library_matches, library_image2);

    %part 3
    show_ransac(@least_squares_fit, @affine_estimate, house_matches, house_image2, ...
                3, 1000, 20, 20);
    show_ransac(@least_squares_fit, @affine_estimate, library_matches, library_image2, ...
                3, 1000, 20, 35);
    
    %part 4
    show_simple_fit(@svd_fit_unnormal, @projective_estimate, house_matches, house_image2);  
    show_simple_fit(@svd_fit_normal, @projective_estimate, house_matches, house_image2);
    show_simple_fit(@svd_fit_unnormal, @projective_estimate, library_matches, library_image2);
    show_simple_fit(@svd_fit_normal, @projective_estimate, library_matches, library_image2);
    
    %part 5
    show_ransac(@svd_fit_unnormal, @projective_estimate, house_matches, house_image2, ...
                4, 2000, 20, 10);
    show_ransac(@svd_fit_unnormal, @projective_estimate, library_matches, library_image2, ...
                4, 1000, 30, 30);
    
    %part 6
    show_epipolar(@fit_fundamental_unnormal, house_matches, house_image2);
    show_epipolar(@fit_fundamental_normal, house_matches, house_image2);
    show_epipolar(@fit_fundamental_unnormal, library_matches, library_image2);
    show_epipolar(@fit_fundamental_normal, library_matches, library_image2);
    
    % part 7
    show_triangulation(house_camera1, house_camera2, house_matches);
    show_triangulation(library_camera1, library_camera2, library_matches);
    
    % extra credit
    show_dominant_ransac(@svd_fit_unnormal, @projective_estimate, house_matches, house_image2, ...
                4, 1000, 30, 20);

end


%% ---------------------------------------------------------------
% entry points
function show_simple_fit(fit_func, estimate_func, points, image)
    M = fit_func( points(:,1:2), points(:,3:4) );
    
    estimated = estimate_func(M, points(:,1:2), points(:,3:4) );
    distance = distance_between(estimated, points(:,3:4));
    residual = mean( distance );

    figure( get_figure_number() ); imshow(image); hold on;
    show_points(points(:,3:4), estimated, '.b', '+r', [0.5 0 1]);
    title( ['residual:' num2str(residual)]);
end

function show_ransac(fit_func, estimate_func, points, image, initial_set_size, ...
                        num_tries, min_set_size, min_distance)
    [M inliers initial inlier_error] = simple_ransac(fit_func, estimate_func, points, ...
        initial_set_size, num_tries, min_set_size, min_distance);
    
    estimated = estimate_func(M, points(:,1:2), points(:,3:4) );
    distance = distance_between(estimated, points(:,3:4));
    residual = mean( distance );

    figure( get_figure_number() ); imshow(image); hold on;
    show_points(points(:,3:4), estimated, '.b', '+r', [0.5 0 1]);
    show_points(points(inliers,3:4), estimated(inliers,:), '.b', '+y', 'g');
    show_points(points(initial,3:4), estimated(initial,:), '.b', 'sy', 'g');
    title( ['inliers:' num2str(length(inliers)) ' inlier\_residual:' num2str(inlier_error) ...
            ' full\_residual:' num2str(residual)] );
end


function show_dominant_ransac(fit_func, estimate_func, all_points, image, initial_set_size, ...
                        num_tries, min_set_size, min_distance)

    % first set
    points1 = all_points;
    [M1 inliers1 initial1 inlier_error] = simple_ransac(fit_func, estimate_func, points1, ...
        initial_set_size, num_tries, min_set_size, min_distance);    
    estimated1 = estimate_func(M1, points1(:,1:2), points1(:,3:4) );
    
    figure( get_figure_number() ); imshow(image); hold on;
    show_dominant_all(points1, estimated1, 'r', inliers1, initial1);
    pause;
    
    % remove inliers
    points2 = points1;
    points2(inliers1,:) = [];
    
    % second set
    [M2 inliers2 initial2 inlier_error] = simple_ransac(fit_func, estimate_func, points2, ...
        initial_set_size, num_tries, min_set_size, min_distance);    
    estimated2 = estimate_func(M2, points2(:,1:2), points2(:,3:4) );
    
    figure( get_figure_number() ); imshow(image); hold on;
    show_dominant_all(points2, estimated2, 'y', inliers2, initial2);
    pause;
    
    % remove inliers
    points3 = points2;
    points3(inliers2,:) = [];
    
    % second set
    [M3 inliers3 initial3 inlier_error] = simple_ransac(fit_func, estimate_func, points3, ...
        initial_set_size, num_tries, min_set_size, min_distance);    
    estimated3 = estimate_func(M3, points3(:,1:2), points3(:,3:4) );
    
    figure( get_figure_number() ); imshow(image); hold on;
    show_dominant_all(points3, estimated3, 'g', inliers3, initial3);
    
    figure( get_figure_number() ); imshow(image); hold on;
    show_dominant_inliers(points1, estimated1, 'r', inliers1, initial1);
    show_dominant_inliers(points2, estimated2, 'y', inliers2, initial2);
    show_dominant_inliers(points3, estimated3, 'g', inliers3, initial3);
    
end


function show_epipolar(fit_func, matches, image)
    N = size(matches,1);
    
    % first, fit fundamental matrix to the matches
    F = fit_func(matches(:, 1:2), matches(:, 3:4)); % this is a function that you should write
    L = [matches(:,1:2) ones(N,1)] * F; % transform points from 
    % the first image to get epipolar lines in the second image

    % find points on epipolar lines L closest to matches(:,3:4)
    L = L ./ repmat(sqrt(L(:,1).^2 + L(:,2).^2), 1, 3); % rescale the line
    pt_line_dist = sum(L .* [matches(:,3:4) ones(N,1)],2);
    closest_pt = matches(:,3:4) - L(:,1:2) .* repmat(pt_line_dist, 1, 2);

    % find endpoints of segment on epipolar line (for display purposes)
    pt1 = closest_pt - [L(:,2) -L(:,1)] * 10; % offset from the closest point is 10 pixels
    pt2 = closest_pt + [L(:,2) -L(:,1)] * 10;
    
    % error
    distance = distance_between(closest_pt, matches(:,3:4));
    residual = mean( distance );

    % display points and segments of corresponding epipolar lines
    figure( get_figure_number() ); imshow(image); hold on;
    plot(matches(:,3), matches(:,4), '.b');
    line([matches(:,3) closest_pt(:,1)]', [matches(:,4) closest_pt(:,2)]', 'Color', [0.5 0 1]);
    line([pt1(:,1) pt2(:,1)]', [pt1(:,2) pt2(:,2)]', 'Color', 'r');
    title( ['residual: ' num2str(residual)] );
end

function show_triangulation(camera1, camera2, matches)
    [points reproj1 reproj2] = get_triangulation(camera1, camera2, matches);
    
    % get error in reprojections
    distance = distance_between(reproj1, matches(:,1:2));
    residual1 = mean( distance );
    distance = distance_between(reproj2, matches(:,3:4));
    residual2 = mean( distance );
    
    % get camera centers
    [U, S, V] = svd(camera1);
    center1 = V(:,end);
    center1 = unhomogenize(center1');
    [U, S, V] = svd(camera2);
    center2 = V(:,end);
    center2 = unhomogenize(center2');
    
    figure( get_figure_number() );
    
    axis equal;
    hold on;  % don't fall!
    %camproj('perspective');
    
    plot3(points(:,1), points(:,2), points(:,3), '+r');
    plot3(center1(1), center1(2), center1(3), '.b');
    plot3(center2(1), center2(2), center2(3), '.m');

    title(['residual 1:' num2str(residual1) ' residual 2:' num2str(residual2) ]);
end

%% ---------------------------------------------------------------
% graph functions

function show_points(points1, points2, point_style1, point_style2, line_style)
    plot(points1(:,1), points1(:,2), point_style1);
    plot(points2(:,1), points2(:,2), point_style2);
    line([points1(:,1) points2(:,1)]', [points1(:,2) points2(:,2)]', 'Color', line_style);
end

function f = get_figure_number(set_val)
    persistent figure_count;
    
    if(nargin == 1)
        figure_count = 0;
    else
        figure_count = figure_count+1;
        f = figure_count;
    end
end

function show_dominant_all(points, estimated, color, inliers, initial)
    show_points(points(:,3:4), estimated, '.b', '+b', 'b');
    show_dominant_inliers(points, estimated, color, inliers, initial);
end

function show_dominant_inliers(points, estimated, color, inliers, initial)
    show_points(points(inliers,3:4), estimated(inliers,:), '.b', ['+' color], 'b');
    show_points(points(initial,3:4), estimated(initial,:), '.b', ['s' color], 'b');
end

%% ---------------------------------------------------------------
% shared estimation functions

function [M, inliers, initial inlier_error] = simple_ransac(fitting_func, estimate_func, ...
            all_points, num_pts_needed, num_tries, min_set_size, min_distance)
    [rows, npts] = size(all_points);
    best_M = NaN;
    best_error =  inf;

    for i=1 : num_tries
        initial_points = randsample(rows, num_pts_needed);
        M = fitting_func( all_points(initial_points,1:2), all_points(initial_points,3:4) );
        estimated = estimate_func(M, all_points(:,1:2), all_points(:,3:4) );
        distance = distance_between(estimated, all_points(:,3:4));
        
        consensus_set = get_inliers(distance, initial_points, min_distance);

        if( length(consensus_set) >= min_set_size)
            consensus_from = all_points(consensus_set,1:2);
            consensus_to = all_points(consensus_set,3:4);
            M = fitting_func( consensus_from, consensus_to );
            estimated = estimate_func(M, consensus_from, consensus_to );
            error = mean( distance_between(estimated, consensus_to) );
            
            if error < best_error;
                best_error = error
                best_set = consensus_set;
                best_M = M;
                best_initial = initial_points;
            end
        end
    end

    if isnan(best_M)
        M = [];
        inliers = [];
        initial = [];
        inlier_error = inf;
        warning ( 'No matches found- time to explode' );
    else
        M = best_M;
        inliers = best_set;
        initial = best_initial;
        inlier_error = best_error;
    end
end

function I = get_inliers(distance, start_set, min_distance)
    inliers = [];
    num_points = size(distance);
    
    for i=1 : num_points(1)
        in_start_set = 0;
        for j=1 : length(start_set)
            if( i == start_set(j) )
                in_start_set = 1;
            end
        end
        
        if( distance(i) < min_distance || in_start_set)
            inliers = [inliers; i];
        end
    end
    
    I = inliers;
end

function distance = distance_between(points1, points2)
    distance = points1 - points2;
    distance = distance.^2;
    distance = sum(distance, 2);
    distance = sqrt(distance);
end

function [normalized, T] = get_normalize(points)
    num_points = length(points);
    
    center = mean(points(:,1:2));
    center_list = [center(1)*ones(num_points, 1) center(2)*ones(num_points, 1)];
    distance = mean(distance_between(center_list, points(:,1:2)));
    
    scale = sqrt(2)/distance;
    
    T = [scale 0 -scale*center(1); 0 scale -scale*center(2); 0 0 1];
    normalized = (T * points')';
end

function out_p = unhomogenize(in_p)
    [rows, cols] = size(in_p);
    
    out_p = zeros(rows, cols-1);
    
    for i=1 : cols-1
        out_p(:,i) = in_p(:,i)./in_p(:,cols);
    end
end

function out_p = homogenize(in_p)
    [rows, cols] = size(in_p);
    
    out_p = ones(rows, cols+1);
    
    for i=1 : cols
        out_p(:,i) = in_p(:,i);
    end
end

%% ---------------------------------------------------------------
% affine functions

function [Model Affine] = least_squares_fit(from_points, to_points)
    B = [to_points(:,1); to_points(:,2)];
    A = get_affine_equations(from_points(:,1:2));

    Model = A\B;
    
    if( nargout > 1)
        Affine = A;
    end
end

function A = get_affine_equations(image_points)
    num_points = size(image_points);
    num_points = num_points(1);
    
    A = [image_points(:,1)   image_points(:,2)    zeros(num_points, 1) ...
        zeros(num_points, 1) ones(num_points, 1)  zeros(num_points, 1);
        zeros(num_points, 1) zeros(num_points, 1) image_points(:,1) ...
        image_points(:,2)    zeros(num_points, 1) ones(num_points, 1)];
end

function estimated = affine_estimate(M, from_points, to_points)
    A = get_affine_equations( from_points );
    estimated_points = A*M;
    length = size(estimated_points);
    ex = estimated_points(1:length(1)/2,:);
    ey = estimated_points(length(1)/2+1:end,:);
    estimated = [ex ey];
end

%% ---------------------------------------------------------------
% projective functions

function H = svd_fit_normal(from_points, to_points)
    H = svd_fit(from_points, to_points, 1);
end

function H = svd_fit_unnormal(from_points, to_points)
    H = svd_fit(from_points, to_points, 0);
end

function H = svd_fit(from_points, to_points, normalize)
    num_points = length(from_points);
    homo_from = homogenize(from_points);
    homo_to = homogenize(to_points);
    
    % normalize
    if (normalize == 1)
        [homo_from, T_from] = get_normalize(homo_from);
        [homo_to, T_to] = get_normalize(homo_to);
    end
    
    A = get_projective_equations(homo_from, homo_to);
    
    [U, S, V] = svd(A);
    H = V(:,end);
    H = reshape(H, 3, 3)';
    
    % denormalize
    % I don't really know what's going on here
    % I tried the T1'*X*T2 formulation from the lecture and it doesn't
    % work at all. I then looked at the homography stuff here:
    % http://www.csse.uwa.edu.au/~pk/research/matlabfns/
    if (normalize == 1)
        H = inv(T_to) * H * T_from;
        %H = T_to \ H * T_from;
    end
end

function A = get_projective_equations(from_points, to_points)
    num_points = length(from_points);
    
    A = zeros(num_points*3, 9);
    for i=1 : num_points
        X = from_points(i,:);
        x = to_points(i,1);
        y = to_points(i,2);
            
        A(i*3-2,:) = [0 0 0  -X    y*X ];
        A(i*3-1,:) = [  X   0 0 0 -x*X ];
        A(i*3-0,:) = [ -y*X  x*X  0 0 0];
        %A(i*2-1,:) = [0 0 0  X    -y*X ];
        %A(i*2-0,:) = [  X   0 0 0 -x*X ];
    end
end

function estimated = projective_estimate(H, from_points, to_points );
    num_points = length(from_points);
    homo_from = homogenize(from_points);
    
    estimated = (H * homo_from')';
    estimated = unhomogenize(estimated);
end

%% ---------------------------------------------------------------
% fundamental matching

function F = fit_fundamental_normal(from_points, to_points)
    F = fit_fundamental(from_points, to_points, 1);
end

function F = fit_fundamental_unnormal(from_points, to_points)
    F = fit_fundamental(from_points, to_points, 0);
end

function F = fit_fundamental(from_points, to_points, normalize)
    num_points = length(from_points);
    homo_from = homogenize(from_points);
    homo_to = homogenize(to_points);
    
    % normalize
    if (normalize == 1)
        [homo_from, T_from] = get_normalize(homo_from);
        [homo_to, T_to] = get_normalize(homo_to);
    end
    
    A = get_fundamental_equations(homo_from, homo_to);
    
    [U, S, V] = svd(A);
    
    F = V(:,end);
    F = reshape(F, 3, 3)';
    
    %constraint time
    [U, S, V] = svd(F);
    S(3,3) = 0;
    F = U * S * V';
    
    % denormalize
    if (normalize == 1)
        F = T_from' * F * T_to;
    end
end

function A = get_fundamental_equations(from, to)
    num_points = length(from);
  
    A = [from(:,1).*to(:,1)  from(:,1).*to(:,2)  from(:,1)  from(:,2).*to(:,1) ...
         from(:,2).*to(:,2)      from(:,2)       to(:,1)       to(:,2) ...
         ones(num_points, 1) ];
end

%% ---------------------------------------------------------------
% triangulation

function [X, C1_points, C2_points] = get_triangulation(camera1, camera2, in_points)
    num_points = size(in_points, 1);
    
    X = zeros(num_points, 3);
    C1_points = zeros(num_points, 2);
    C2_points = zeros(num_points, 2);
    
    for i=1 : num_points
        A = get_triangulation_equations(camera1, camera2, in_points(i,:));
        [U, S, V] = svd(A);
        point = V(:,end);
        
        % reproject
        C1_reproject = camera1 * point;
        C2_reproject = camera2 * point;
        C1_points(i,:) =  unhomogenize(C1_reproject');
        C2_points(i,:) =  unhomogenize(C2_reproject');
        
        point = unhomogenize(point');
        X(i,:) = point;
    end
end

function c = get_cross_product(p)
    c = [  0   -p(3)  p(2);
          p(3)   0   -p(1);
         -p(2)  p(1)   0  ];
end

function A = get_triangulation_equations(camera1, camera2, points)  
    point1 = homogenize(points(:,1:2));
    point2 = homogenize(points(:,3:4));
    
    cross1 = get_cross_product(point1);
    cross2 = get_cross_product(point2);
    
    eq1 = cross1 * camera1;
    eq2 = cross2 * camera2;
    
    A = [eq1 ; eq2];
    %A = [point1(1)*camera1(3,:) - camera1(1,:);
    %     point1(2)*camera1(3,:) - camera1(2,:);
    %     point2(1)*camera2(3,:) - camera2(1,:);
    %     point2(2)*camera2(3,:) - camera2(2,:)];
end

%%
