function [seg_x, seg_gt, seg_sz, seg_id] = ...
        aggregate_box_features(parent_dirs, name_root, config, feat_types)

% Load the labeled boxes.
gt_path = fullfile(parent_dirs{:}, ...
                   sprintf(config.data_info.gt_format, name_root));
load(gt_path, 'rect_coord', 'rect_label');

if strcmp(config.model_params.setting, 'recognition')
    seg_gt = rect_label;    
    seg_id = 1:length(rect_label);
    img_sz = [129 129];
elseif strcmp(config.model_params.setting, 'detection')
    [rect_coord, seg_gt, seg_id, img_sz] = ...
        get_detection_boxes(parent_dirs, name_root, config, rect_coord, rect_label);
end

% Load each of the features specified
all_seg_feats = cell(size(feat_types, 1), 1);
for feat_i = 1:size(feat_types, 1)
    % Get path to the file where the features are stored
    feat_path = fullfile(config.paths.seg_feats, feat_types{feat_i}, ...
                         [name_root '.mat']);

    % Get the segment features.
    seg_feats = load_seg_feats(feat_path, seg_id);
    if isempty(seg_feats)
        % Load the feature map for the current feature type
        map_path = fullfile(config.paths.img_feats, feat_types{feat_i}, ...
                            [name_root '.mat']);
        load(map_path, 'feature_map', 'map_info');

        % Aggregate region features for each segment
        seg_feats = zeros(size(feature_map, 1), length(seg_id));
        for i = 1:length(seg_id)
            mask = poly2mask(rect_coord{i}(:,1), rect_coord{i}(:,2), ....
                             img_sz(1), img_sz(2));

            seg_feats(:,i) = pool_region_feats(feature_map, map_info, mask, ...
                                               config.pool_type);
        end

        save(feat_path, 'seg_feats', 'seg_id');
    end

    % Select the appropriate segments
    all_seg_feats{feat_i} = seg_feats;

    % Normalize each feature (before they are concatenated)
    if isfield(config.model_params, 'norm_each_feat')
        switch config.model_params.norm_each_feat
          case 'L2'
            all_seg_feats{feat_i} = ...
                bsxfun(@rdivide, all_seg_feats{feat_i}, ...
                       sqrt(sum(all_seg_feats{feat_i}.^2, 1))+eps);
          case 'L1'
            all_seg_feats{feat_i} = ...
                bsxfun(@rdivide, all_seg_feats{feat_i}, ...
                       sum(all_seg_feats{feat_i}, 1)+eps);
          case 'Linf'
            all_seg_feats{feat_i} = ...
                bsxfun(@rdivide, all_seg_feats{feat_i}, ...
                       max(all_seg_feats{feat_i}, 1)+eps);
          otherwise
            error('Unknown per feature normalization.');
        end
    end
end

seg_x = sparse(cell2mat(all_seg_feats));
seg_sz = [];
end

function [rect_coord, seg_gt, seg_id, img_sz] = ...
        get_detection_boxes(parent_dirs, name_root, config, rect_coord, rect_label)
step_sz = 4;
% Rectangle dimensions were set based on the average size of cornell bounding boxes.
rect_width = 48;
rect_height = 24;

% Load the image and get the image size.
img_path = fullfile(parent_dirs{:}, ...
                    sprintf(config.data_info.rgb_format, name_root));
img = im2double(imread(img_path));
img_sz = [size(img, 1) size(img, 2)];

% Compute the rectangle centroids and bounding rectangle. 
pos_ctrs = cell2mat(cellfun(@mean, rect_coord(rect_label == 1), ...
                            'UniformOutput', false));
box_min = max(ceil(min(pos_ctrs)) - config.model_params.box_pad, 1);
box_max = min(ceil(max(pos_ctrs)) + config.model_params.box_pad, img_sz);

% Sample the image grid and compute distances to each positive rectangle.
[ctr_x, ctr_y] = meshgrid(box_min(1):step_sz:box_max(1), ...
                          box_min(2):step_sz:box_max(2));
% To ignore the bounding box, use the following.
% [ctr_x, ctr_y] = meshgrid(1:step_sz:img_sz(2), 1:step_sz:img_sz(1));
rect_ctrs = [ctr_x(:) ctr_y(:)];
dist = min(pdist2(rect_ctrs, pos_ctrs, 'euclidean'), [], 2);

% Sample from positive and negative boxes.
pos_inds = dist < config.model_params.pos_dist_thresh;
neg_inds = dist >= config.model_params.pos_dist_thresh;
% If you're not using the bounding box, but you want to ignore 
% parts of the image too far from the positive centroids (background)
% neg_inds = ((dist >= config.model_params.pos_dist_thresh) & ...
%             (dist < config.model_params.max_neg_dist));
sel = Inf(size(dist));
sel(pos_inds) = randperm(sum(pos_inds));
sel(neg_inds) = randperm(sum(neg_inds));
sel = sel < config.model_params.max_img_samples;
dist = dist(sel);
rect_ctrs = rect_ctrs(sel, :);

% Create rect_coord cell structure. 
box_offset = [[-1 1 1 -1]*rect_width/2; [-1 -1 1 1]*rect_height/2]';
rect_coord = mat2cell(rect_ctrs, ones(1, size(rect_ctrs, 1)), 2);
rect_coord = cellfun(@(x) bsxfun(@plus, box_offset, x), rect_coord, ...
                     'UniformOutput', false);

% Create ground truth labels.
if config.model_params.regression_labels
    seg_gt = 1 - dist/config.model_params.pos_dist_thresh
else
    seg_gt = dist < config.model_params.pos_dist_thresh;
end

seg_id = sub2ind(img_sz, rect_ctrs(:, 2), rect_ctrs(:, 1));

end

function seg_feats = load_seg_feats(feat_path, seg_id)
seg_feats = [];

% Load segment feats if they were saved.
if exist(feat_path, 'file')
    seg_data = load(feat_path, 'seg_feats', 'seg_id');

    if (all(size(seg_id) == size(seg_data.seg_id)) && ...
        all(seg_id == seg_data.seg_id))
        seg_feats = seg_data.seg_feats;
    else
        id_perm = arrayfun(@(x) find(x == seg_data.seg_id, 1), ...
                           seg_id, 'UniformOutput', false);

        if ~any(cellfun(@isempty, id_perm))
            id_perm = cell2mat(id_perm);
            seg_feats = seg_data.seg_feats(:, id_perm);
        end
    end
end

end
