function cornell_preprocess(raw_data_dir, data_dir, ...
                            det_results, rec_results)
fprintf('Preprocessing for Cornell Detection and Recognition.\n');
token_expr = '^pcd(\d+).txt$';
file_info = make_file_info(raw_data_dir, token_expr);

det_paths.raw_data = fullfile(data_dir, 'det_data');
det_paths.depth = fullfile(det_results, 'img_feats', 'depth');
det_paths.normals = fullfile(det_results, 'img_feats', 'normals');
det_paths.curvature = fullfile(det_results, 'img_feats', 'curvature');
make_directory_structure(det_paths);

rec_paths.raw_data = fullfile(data_dir, 'rec_data');
rec_paths.depth = fullfile(rec_results, 'img_feats', 'depth');
rec_paths.normals = fullfile(rec_results, 'img_feats', 'normals');
rec_paths.curvature = fullfile(rec_results, 'img_feats', 'curvature');
make_directory_structure(rec_paths);

n_folds = 5;
det_fold_info = ceil(n_folds * rand(file_info.num_files, 1));
rec_fold_info = cell(file_info.num_files, 1);

% Normal and Curvature paramters
normal_params.patch_radius = 5; % Normal filter window radius
normal_params.sigma_scale = 9;  % Normal filter sigma
curv_params.patch_radius = 3;   % Curvature filter window radius
curv_params.n_pairs = 250;      % Curvature filter number of pairs

parfor file_i = 1:file_info.num_files
    % Load the rectangles, image, and point cloud.
    [parent_dirs, name_root] = get_file_info_entry(file_info, file_i);
    [img, pcd, rect_coord, rect_label, crop] = ...
        load_cornell_data(parent_dirs, name_root);

    % Get the depth image, normals, and curvature.
    depth = pcd(:,:,3);
    normals = depth_img_to_normals_cross(pcd, normal_params);
    curvature = pcd_normals_to_curvature(pcd, normals, curv_params);

    save_rect_data(det_paths, name_root, img, depth, normals, curvature, ...
                   rect_coord, rect_label);

    save_each_rect(rec_paths, name_root, img, depth, normals, curvature, ...
                   rect_coord, rect_label);

    rec_fold_info{file_i} = ones(size(rect_coord))*det_fold_info(file_i);
end

rec_fold_info = cell2mat(rec_fold_info);
save(fullfile(det_results, 'fold_info.mat'), 'det_fold_info');
save(fullfile(rec_results, 'fold_info.mat'), 'rec_fold_info');
end % cornell_preprocess

function [rect_coord, rect_label] = ...
    load_cornell_rectangles(parent_dirs, name_root)
% Load the positive and negative rectangle coordinates.
rects_pos = load(fullfile(parent_dirs{:}, ['pcd' name_root 'cpos.txt']));
rects_neg = load(fullfile(parent_dirs{:}, ['pcd' name_root 'cneg.txt']));

if (mod(size(rects_pos, 1), 4) ~= 0) || (mod(size(rects_neg, 1), 4) ~= 0)
    error('Number of rows should be a multiple of 4');
end
if (size(rects_pos, 2) ~= 2) && (size(rects_neg, 2) ~= 2)
    error('Number of columns should be 2 (x and y coordinates)');
end

rect_label = [ones(size(rects_pos, 1)/4, 1); zeros(size(rects_neg, 1)/4, 1)];
rect_coord = mat2cell([rects_pos; rects_neg], 4*ones(1, size(rect_label, 1)), 2);
end

function [img, pcd, rect_coord, rect_label, crop] = ...
    load_cornell_data(parent_dirs, name_root)
medfil_sz = 5;
crop_border = 100;

% Load the image and unordered pointcloud.
img_path = fullfile(parent_dirs{:}, ['pcd' name_root 'r.png']);
pcd_path = fullfile(parent_dirs{:}, ['pcd' name_root  '.txt']);
img = imread(img_path);
[height width ~] = size(img);

[pcd_raw, img_points] = readGraspingPcd(pcd_path);
pcd_raw = pcd_raw / 1000; % change cornell data to meters.
pcd_raw = pcd_raw(:, [2 1 3]);
pcd = zeros(width, height, 3);
% Convert unordered point cloud to a point cloud image.
for i = 1:3
    pcd(img_points + width*height*(i-1)) = pcd_raw(:,i);
end

% Get a bounding box for the foreground object.
% We take advantage of the fact that the table plane has
% z coordinate close to zero in the Cornell dataset pointclouds.
% mask = all(bsxfun(@gt, pcd, permute([-0.5 0 0.02], [1 3 2])) & ...
%            bsxfun(@lt, pcd, permute([0.5 1.4 10], [1 3 2])), 3);
% regions = bwconncomp(mask);
% [max_sz, max_id] = max(cellfun(@length, regions.PixelIdxList));
% [fg_row, fg_col] = ind2sub(size(mask), regions.PixelIdxList{max_id});
% fg_bounds = [min(fg_row) min(fg_col) max(fg_row) max(fg_col)];
% fg_bounds = [min(fg_bounds(1:2), rect_bounds(1:2)) ...
%              max(fg_bounds(3:4), rect_bounds(3:4))];
% crop = ceil(fg_bounds + crop_border*[-1 -1 1 1]);

% Load the labeled rectangles.
[rect_coord, rect_label] = load_cornell_rectangles(parent_dirs, name_root);
rect_ctrs = cell2mat(cellfun(@mean, rect_coord, 'UniformOutput', false));
rect_bounds = [min(rect_ctrs) max(rect_ctrs)];
crop = ceil(rect_bounds + crop_border*[-1 -1 1 1]);
crop = max(crop, 1);
crop = min(crop, [width height width height]);

% Crop around the foreground object.
img = img(crop(2):crop(4), crop(1):crop(3), :);
pcd = pcd(crop(1):crop(3), crop(2):crop(4), :);

% Median filter to clean the data.
for i = 1:3
    pcd(:,:,i) = medfilt2(pcd(:,:,i), [medfil_sz medfil_sz]);
end

% Adjust rectangle coordinates based on the crop.
rect_coord = cellfun(@(x) bsxfun(@minus, x, crop([1 2]) + 1), ...
                     rect_coord, 'UniformOutput', false);

crop = crop([2 4 1 3]);
pcd = permute(pcd, [2 1 3]);
end

% Extract rectangle sub-images and save individually.
function save_each_rect(paths, name_root, img, depth, normals, curvature, ...
                        rect_coord, rect_label)
% subplot(1,2,1);
% imshow(img);
% hold on

lims = [-64 64];
for i = 1:length(rect_label)
    rect = rect_coord{i};

    % Determine rectangle coordinate frame.
    [~, top_i] = min(rect(:, 2));
    adjacent = rect((mod([top_i-2 top_i], 4) + 1), :);
    [dist, adj_i] = sort(pdist2(adjacent, rect(top_i, :), 'euclidean'));

    % Calculate rectangle transformation.
    source_pts = [rect(top_i, :); adjacent(adj_i, :)];
    target_pts = bsxfun(@minus, [0 0; 0 dist(1); dist(2) 0], dist([2 1])'/2);
    tform = maketform('affine', source_pts, target_pts);

    % Transform and crop the data.
    rect_img  = imtransform(img, tform, 'XData', lims, 'YData', lims);
    rect_dep  = imtransform(depth, tform, 'XData', lims, 'YData', lims);
    rect_nrml = imtransform(normals, tform, 'XData', lims, 'YData', lims);
    rect_curv = imtransform(curvature, tform, 'XData', lims, 'YData', lims);

    % Save data.
    new_rect = dist([2 1; 2 1; 2 1; 2 1]).*[-1 -1; 1 -1; 1 1; -1 1]/2;
    new_rect = new_rect - lims(1);

    file_name = sprintf('%s_%03d', name_root, i);
    save_rect_data(paths, file_name, rect_img, rect_dep, rect_nrml, rect_curv, ...
                   {new_rect}, rect_label(i));

    % subplot(1,2,1); hold on;
    % plot([rect(:,1); rect(1,1)], [rect(:,2); rect(1,2)]);
    % subplot(1,2,2);
    % hold off;
    % imshow(rect_img);
    % hold on;
    % plot([new_rect(:,1); new_rect(1,1)], [new_rect(:,2); new_rect(1,2)]);
end
end

function save_rect_data(paths, file_name, img, depth, normals, curvature, ...
                        rect_coord, rect_label);
image_path     = fullfile(paths.raw_data,  [file_name '.png']);
gt_path        = fullfile(paths.raw_data,  [file_name '_gt.mat']);
depth_path     = fullfile(paths.depth,     [file_name '.mat']);
normals_path   = fullfile(paths.normals,   [file_name '.mat']);
curvature_path = fullfile(paths.curvature, [file_name '.mat']);

imwrite(img, image_path);
save(gt_path, 'rect_coord', 'rect_label');
save(depth_path, 'depth');
save(normals_path, 'normals');
save(curvature_path, 'curvature');
end
