function [detections] = getDetectionsForModel(models, pyramid, ...
  patchCanonicalSize, data, imgHome, detectionParams)
% The Nth neg to select the decision boundary.
NTH_NEG = 10;

data = data.annotation;
prSize = round(patchCanonicalSize(1) / pyramid.sbins) - 2;
pcSize = round(patchCanonicalSize(2) / pyramid.sbins) - 2;
selFeatures = cell(length(pyramid.features), 1);
selFeaturesInds = cell(length(pyramid.features), 1);
selMetadata = cell(length(pyramid.features), 1);
selDecision = cell(length(pyramid.features), length(models));
selThresh = cell(length(pyramid.features), length(models));
totalProcessed = 0;
for i = 1 : length(pyramid.features)
  [feats, indexes] = getFeaturesForLevel(pyramid.features{i}, prSize, ...
    pcSize);
  selFeatures{i} = feats;
  selFeaturesInds{i} = indexes;
  selMetadata{i} = getMetadataForPositives(...
      1 : size(feats, 1), i, indexes, prSize, pcSize, data, ...
      pyramid, imgHome);
  labels = ones(size(feats, 1), 1);
  totalProcessed = totalProcessed + length(labels);
  for j = 1 : length(models)
    
    [unused_labels, unused_acc, decision] = mySvmPredict(labels, ...
      feats, models{j});
    % Make decision a row vector.
    decision = reshape(decision, 1, length(decision));
    selDecision{i, j} = decision;
    
    % Select a dummy metadata item for allocating array.
    selThresh{i, j} = getThreshForNthNeg(decision, NTH_NEG);
  end
  
end

[features, metadata, decision] = appendAllTogether(totalProcessed, ...
  selFeatures, selMetadata, selDecision);

detections = cell(1, length(models));
for i = 1 : length(models)
  % Do NMS for cluster
  picks = doNmsForImg(metadata, decision{i}, detectionParams.overlap);
  clustFeatures = features(picks, :);
  clustMetadata = metadata(picks);
  clustDecision = decision{i};
  clustDecision = clustDecision(picks);
  
  detections{i} = constructResultStruct( ...
    totalProcessed, clustFeatures, ...
    clustMetadata, clustDecision, selThresh(:, i), NTH_NEG, ...
    detectionParams, models{i});
end
detections = [detections{:}];
end

function [newFeat, newMeta, newDec] = appendAllTogether(totalProcessed, ...
  features, metadata, decision)
newFeat = zeros(totalProcessed, size(features{1}, 2));
newDec = cell(1, size(decision, 2));
dummyMetadata = metadata{1};
newMeta(totalProcessed) = dummyMetadata(1);
featInd = 1;
for i = 1 : length(features)
  if isempty(metadata{i})
    continue;
  end
  startInd = featInd;
  endInd = startInd + size(features{i}, 1) - 1;
  newFeat(startInd:endInd, :) = features{i};
  features{i} = [];
  newMeta(startInd:endInd) = metadata{i};
  metadata{i} = [];
  for j = 1 : size(decision, 2)
    newDec{j}(startInd:endInd) = decision{i, j};
    decision{i, j} = [];
  end
  featInd = endInd + 1;
end
end

function selected = doSelectionForParams(params, decision, model)
% Perform selection based on detection parameters.
[desc, inds] = sort(decision, 'descend');
if params.selectTopN
  numToSelect = min(length(desc), params.numToSelect);
  selected = inds(1 : numToSelect);
elseif params.useDecisionThresh
  if isfield(params, 'fixedDecisionThresh')
    thresh = params.fixedDecisionThresh;
  else
    thresh = model.threshold;
  end
  selected = find(decision >= thresh);
else
  thresh = 0;
  selected = find(decision >= thresh);
end
end

function detections = constructResultStruct( ...
  totalProcessed, features, ...
  metadata, decision, selectedThresh, nthNeg, ...
  detectionParams, model)
detections = struct('features', [], 'metadata', [], 'decision', [], ...
  'totalProcessed', totalProcessed, 'thresh', []);
selectedInds = doSelectionForParams(detectionParams, decision, ...
  model);
if isempty(selectedInds)
  return;
end

detections(1).features = features(selectedInds, :);
detections(1).metadata = metadata(selectedInds);
detections(1).decision = decision(selectedInds);
detections(1).totalProcessed = totalProcessed;

selectedThresh = sort([selectedThresh{:}], 'descend');
numThreshVal = min(nthNeg, length(selectedThresh));
detections(1).thresh = selectedThresh(1 : numThreshVal);
end

function [features, indexes] = getFeaturesForLevel(level, prSize, pcSize)
[rows, cols, dims] = size(level);
rLim = rows - prSize + 1;
cLim = cols - pcSize + 1;
featDim = prSize * pcSize * dims;
features = zeros(rLim * cLim, featDim);
indexes = zeros(rLim * cLim, 2);
featInd = 0;
for i = 1 : rLim
  for j = 1 : cLim
    feat = level(i:i+prSize-1, j:j+pcSize-1, :);
    featInd = featInd + 1;
    features(featInd, :) = reshape(feat, 1, featDim);
    indexes(featInd, :) = [i, j];
  end
end
end

function [metadata] = getMetadataForPositives(selected, level,...
  indexes, prSize, pcSize, data, pyramid, imgHome)
metadata = struct('im', {}, 'x1', {}, 'x2', {}, 'y1', {}, 'y2', {}, ...
    'flip', {}, 'trunc', {}, 'size', {});
imPath = [imgHome data.folder '/' data.filename];
levSc = pyramid.scales(level);
canoSc = 1 / pyramid.canonicalScale;
for i = 1 : length(selected)
  selInd = selected(i);
  x1 = indexes(selInd, 2);
  metadata(i).x1 = round((x1 * pyramid.sbins + 1) * levSc * canoSc);
  x2 = x1 + pcSize - 1;
  metadata(i).x2 = round(((x2 + 1) * pyramid.sbins - 1) * levSc * canoSc);
  y1 = indexes(selInd, 1);
  metadata(i).y1 = round((y1 * pyramid.sbins + 1) * levSc * canoSc);
  y2 = y1 + prSize - 1;
  metadata(i).y2 = round(((y2 + 1) * pyramid.sbins - 1) * levSc * canoSc);
  metadata(i).im = imPath;
  metadata(i).size = data.imagesize;
  metadata(i).flip = false;
  metadata(i).trunc = false;
end
end

function thresh = getThreshForNthNeg(decision, N)
thresh = [];
selected = intersect(find(decision < 0), find(decision > -1));
if isempty(selected)
  return;
end
selDec = decision(selected);
selDec = selDec(:)';
thresh = sort(selDec, 'descend');
if length(thresh) > N
  thresh = thresh(1:N);
end
end
