function model = gdetect_dp(pyra, model)
% Compute dynamic programming tables used for finding detections.
%   model = gdetect_dp(pyra, model)
%
%   This function implements the dynamic programming algorithm for
%   computing high-scoring derivations using an Object Detection Grammar.
%   It is assumed that the detection grammar is an Isolated Deformation
%   Grammar and therefore contains only structural schemas and deformation
%   schemas.
%
% Return value
%   model   Object model augmented to store the dynamic programming tables
%
% Arguments
%   pyra    Feature pyramid returned by featpyramid.m
%   model   Object model

% AUTORIGHTS
% -------------------------------------------------------
% Copyright (C) 2009-2012 Ross Girshick
%
% This file is part of the voc-releaseX code
% (http://people.cs.uchicago.edu/~rbg/latent/)
% and is available under the terms of an MIT-like license
% provided in COPYING. Please retain this notice and
% COPYING if you use this file (or a portion of it) in
% your project.
% -------------------------------------------------------

% cache filter response
model = filter_responses(model, pyra);

% compute detection scores
L = model_sort(model);
for s = L
  for r = model.rules{s}
    model = apply_rule(model, r, pyra.pady, pyra.padx);
  end
  model = symbol_score(model, s, pyra);
end

% done
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% compute score pyramid for symbol s
function model = symbol_score(model, s, pyra)
% model  object model
% s      grammar symbol

% take pointwise max over scores for each rule with s as the lhs
rules = model.rules{s};
score = rules(1).score;

for r = rules(2:end)
  for i = 1:length(r.score)
    score{i} = max(score{i}, r.score{i});
  end
end
model.symbols(s).score = score;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% compute score pyramid for rule r
function model = apply_rule(model, r, pady, padx)
% model  object model
% r      structural|deformation rule
% pady   number of rows of feature map padding
% padx   number of cols of feature map padding

if r.type == 'S'
  model = apply_structural_rule(model, r, pady, padx);
else
  model = apply_deformation_rule(model, r);
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% compute score pyramid for structural rule r
function model = apply_structural_rule(model, r, pady, padx)
% model  object model
% r      structural rule
% pady   number of rows of feature map padding
% padx   number of cols of feature map padding

% structural rule -> shift and sum scores from rhs symbols
% prepare score for this rule
score      = model.scoretpt;
bias       = model_get_block(model, r.offset) * model.features.bias;
loc_w      = model_get_block(model, r.loc);
loc_f      = loc_feat(model, length(score));
loc_scores = loc_w * loc_f;
for i = 1:length(score)
  score{i}(:) = bias + loc_scores(i);
end

% sum scores from rhs (with appropriate shift and down sample)
for j = 1:length(r.rhs)
  ax = r.anchor{j}(1);
  ay = r.anchor{j}(2);
  ds = r.anchor{j}(3);
  % step size for down sampling
  step = 2^ds;
  % amount of (virtual) padding to halucinate
  virtpady = (step-1)*pady;
  virtpadx = (step-1)*padx;
  % starting points (simulates additional padding at finer scales)
  starty = 1+ay-virtpady;
  startx = 1+ax-virtpadx;
  % score table to shift and down sample
  s = model.symbols(r.rhs(j)).score;
  for i = 1:length(s)
    level = i - model.interval*ds;
    if level >= 1
      % ending points
      endy = min(size(s{level},1), starty+step*(size(score{i},1)-1));
      endx = min(size(s{level},2), startx+step*(size(score{i},2)-1));
      % y sample points
      iy = starty:step:endy;
      oy = sum(iy < 1);
      iy = iy(iy >= 1);
      % x sample points
      ix = startx:step:endx;
      ox = sum(ix < 1);
      ix = ix(ix >= 1);
      % sample scores
      sp = s{level}(iy, ix);
      sz = size(sp);
      % sum with correct offset
      stmp = -inf(size(score{i}));
      stmp(oy+1:oy+sz(1), ox+1:ox+sz(2)) = sp;
      score{i} = score{i} + stmp;
    else
      score{i}(:) = -inf;
    end
  end
end
model.rules{r.lhs}(r.i).score = score;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% compute score pyramid for deformation rule r
function model = apply_deformation_rule(model, r)
% model  object model
% r      deformation rule

% deformation rule -> apply distance transform
def_w      = model_get_block(model, r.def);
score      = model.symbols(r.rhs(1)).score;
bias       = model_get_block(model, r.offset) * model.features.bias;
loc_w      = model_get_block(model, r.loc);
loc_f      = loc_feat(model, length(score));
loc_scores = loc_w * loc_f;
for i = 1:length(score)
  score{i} = score{i} + bias + loc_scores(i);
  % Bounded distance transform with +/- 4 HOG cells (9x9 window)
  [score{i}, Ix{i}, Iy{i}] = fast_bounded_dt(score{i}, def_w(1), def_w(2), ...
                                             def_w(3), def_w(4), 4);
  % Unbounded distance transform
  %[score{i}, Ix{i}, Iy{i}] = dt(score{i}, def_w(1), def_w(2), ...
  %                              def_w(3), def_w(4));
end
model.rules{r.lhs}(r.i).score = score;
model.rules{r.lhs}(r.i).Ix    = Ix;
model.rules{r.lhs}(r.i).Iy    = Iy;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% compute all filter responses (filter score pyramids)
function model = filter_responses(model, pyra)
% model    object model
% pyra     feature pyramid

% gather filters for computing match quality responses
filters = cell(model.numfilters, 1);
for i = 1:model.numfilters
  filters{i} = single(model_get_block(model, model.filters(i)));
end

for level = 1:length(pyra.feat)
  if ~pyra.valid_levels(level)
    % not processing this level, so set default values
    model.scoretpt{level} = 0;
    for i = 1:model.numfilters
      model.symbols(model.filters(i).symbol).score{level} = -inf;
    end
    continue;
  end

  % Determine the convolution routine to use
  % Try to use an unrolled SSE routine if available
  % 4 floats are processed at a time, so round the feature dim
  % up to the next largest multiple of 4
  fconv_num = 4*ceil(size(pyra.feat{level},3)/4);
  fconv_name = ['fconv_' num2str(fconv_num)];
  if exist(fconv_name) == 3  % 3 ==> MEX function
    fconv_fun = str2func(fconv_name);
  else
    % Fall back to the slower version that works with any dimension
    fconv_fun = @fconv_var_dim;
  end
  % compute filter response for all filters at this level
  r = fconv_fun(pyra.feat{level}, filters, 1, length(filters));

  % find max response array size for this level
  s = cellfun(@size, r, 'UniformOutput', false);
  s = max(cat(1, s{:}), [], 1);

  % set filter response as the score for each filter terminal
  for i = 1:length(r)
    % normalize response array size so all responses at this
    % level have the same dimension
    spady = s(1) - size(r{i},1);
    spadx = s(2) - size(r{i},2);
    if spady > 0 || spadx > 0
      r{i} = post_pad(r{i}, spady, spadx, -inf);
      %r{i} = padarray(r{i}, [spady spady], -inf, 'both');
    end
    fsym = model.filters(i).symbol;
    model.symbols(fsym).score{level} = r{i};
  end
  model.scoretpt{level} = zeros(s);
end


function [ all_bboxes_gt ] = get_grount_truth_bboxes_from_voc( voc_path, image_set, voc_year, with_hard_samples, cache_dir )

if ~exist('cache_dir','var')
    cache_dir = '.';
end

VOCopts            = initVOCOpts( voc_path, voc_year );
fullimage_set_name = ['voc_', voc_year, '_' image_set];
addpath([voc_path, filesep, 'VOCcode']);
cache_file = [cache_dir, filesep, 'gt_bbox_', fullimage_set_name];
if with_hard_samples, cache_file = [cache_file, '_with_hard_samples']; end
cache_file = [cache_file, '.mat'];

if exist(cache_file,'file')
    all_bboxes_gt = loadGroundTruthBBoxes(cache_file);
else
    class_to_id     = containers.Map(VOCopts.classes, 1:length(VOCopts.classes));
    image_ids       = textread(sprintf(VOCopts.imgsetpath, image_set), '%s');
    num_imgs        = length(image_ids);
    all_bboxes_gt   = cell(num_imgs, 1);
    for img_idx = 1:num_imgs
        if mod(img_idx,500) == 0, fprintf('Load gt bboxes::%s %d/%d\n', fullimage_set_name, img_idx, num_imgs); end
        all_bboxes_gt{img_idx} = getGroundTruthBBoxes(sprintf(VOCopts.annopath, image_ids{img_idx}), class_to_id, with_hard_samples);
    end
    saveGroundTruthBBoxes(cache_file, all_bboxes_gt);
end

end

function gt_bboxes = getGroundTruthBBoxes(filename, class_to_id, with_hard_samples)
try
    voc_rec       = PASreadrecord(filename);
    valid_objects = 1:length(voc_rec.objects(:));
    is_difficult  = cat(1, voc_rec.objects(:).difficult);
   if ~with_hard_samples
       valid_objects = valid_objects(~is_difficult);
       is_difficult  = is_difficult(~is_difficult);
   end

   gt_bboxes = single(cat(1, voc_rec.objects(valid_objects).bbox));
   gt_class_idx = class_to_id.values({voc_rec.objects(valid_objects).class});
   gt_class_idx = single(cat(1, gt_class_idx{:}));
   gt_bboxes    = single([gt_bboxes, gt_class_idx, is_difficult(:)]);
catch
    gt_bboxes = zeros(0, 6, 'single');
end

end

function all_bboxes_gt = loadGroundTruthBBoxes(filename)
load(filename, 'all_bboxes_gt');
end

function saveGroundTruthBBoxes(filename, all_bboxes_gt)
save(filename, 'all_bboxes_gt', '-v7.3');
end


function makeGif( M, fileName, prm )
% Writes a matlab movie to an animated GIF.
%
% USAGE
%  makeGif( M, fileName, prm )
%
% INPUTS
%  M          - Matlab movie
%  fileName   - file name of the output gif file
%  prm
%   .fps        - number of frames per second
%   .nColor     - number of indexes colors
%   .scale      - scale to resize the frames at
%   .loop       - number of times to repeat the movie (Inf possible)
%
% OUTPUTS
%
% EXAMPLE
%  load( 'images.mat' );
%  M = playMovie( video, [], 1 );
%  makeGif( M, 'mouse.gif', struct('scale',0.5) );
%
% See also PLAYMOVIE, MONTAGE2
%
% Piotr's Computer Vision Matlab Toolbox      Version 2.0
% Copyright 2014 Piotr Dollar.  [pdollar-at-gmail.com]
% Licensed under the Simplified BSD License [see external/bsd.txt]

dfs = {'fps',30, 'nColor',256, 'scale',1, 'loop', 0};
prm = getPrmDflt( prm, dfs );
fps=prm.fps;  nColor=prm.nColor; scale=prm.scale; loop=prm.loop;

if scale~=1
  for i=1:length(M); M(i).cdata=imresize(M(i).cdata,scale, 'bicubic'); end
end

if ndims(M(1).cdata)==3
  for i=1:length(M)
    [M(i).cdata,M(i).colormap] = rgb2ind(M(i).cdata, nColor);
  end
else
  for i=1:length(M)
    M(i).colormap = repmat(0:1/256:1,[3 1])';
  end
end

imwrite(M(1).cdata,M(1).colormap,fileName,'gif','LoopCount',loop);
for i=2:length(M)
  imwrite(M(i).cdata,M(i).colormap,fileName,'gif','DelayTime',1/fps,...
    'WriteMode','append');
end


% WAITBAR Display wait bar.
%    H = WAITBAR(X,'message', property, value, property, value, ...)
%    creates and displays a waitbar of fractional length X.  The
%    handle to the waitbar figure is returned in H.
%    X should be between 0 and 1.  Optional arguments property and
%    value allow to set corresponding waitbar figure properties.
%    Property can also be an action keyword 'CreateCancelBtn', in
%    which case a cancel button will be added to the figure, and
%    the passed value string will be executed upon clicking on the
%    cancel button or the close figure button.
%
%    WAITBAR(X) will set the length of the current bar to the fractional
%    length X.
%
%    WAITBAR(X,H) will set the length of the bar in waitbar H
%    to the fractional length X.
%
%    WAITBAR(X,H,'message') will update the message text in
%    the waitbar figure, in addition to setting the fractional
%    length to X.
%
%    WAITBAR is typically used inside a FOR loop that performs a
%    lengthy computation.
%
%    Example:
%        h = waitbar(0,'Please wait...');
%        for i=1:1000,
%            % computation here %
%            waitbar(i/1000,h)
%        end
%
%    See also DIALOG, MSGBOX.
%
%    Reference page in Doc Center
%       doc waitbar
%
%