
function [shift, scale, status] = ...
    dll_lowbw_calib_processed(fn, seed_state, num_sec, orig_pixels, ...
    orig_horiz_profile, orig_vert_profile, no_scaling);
% DLL_LOWBW_CALIB_PROCESSED
%   Calculate processed features and low bandwidth calibration.
% SYNTAX
%   [shift, scale, status] = ...
%       dll_lowbw_calib_processed(fn, seed_state, num_sec, orig_pixels, ...
%       orig_horiz_profile, orig_vert_profile, no_scaling);
% DESCRIPTION
%  Calcualte processed features needed for low bandwidth calibration, then
%  estimate spatial registration and scaling registration for each processed 
%  clip.  Video clip must be temporally registered first.
%  'fn' is the file identifier from dll_video, which should always be fn=2.
%  'seed_state' is as returned by dll_lowbw_initialize, which should be
%  called anew each time this function is called.
%  'num_sec' is the number of seconds of video from file fn=1 and 2 that should
%  be used for calibration.  
%  'orig_pixels','orig_horiz_profile', and 'orig_vert_profile' are results
%  from dll_lowbw_calib_original, for the original video (i.e., fn=1).
%   'no_scaling' is 1 to pre sume no spatial scaling,
%   'no_scaling' is 0 to calculate spatial scaling
% 
%   status.error of 1 indicates error,
%   status.scale of 1 indicates scale returned is equal to search limit,
%   status.shift of 1 indicates shift returned is equal to search limit,
 
num_sec = floor(num_sec);
 
% uncompress input arguments.
seed_state = double(seed_state);
% % orig_pixels = single(orig_pixels);
% % orig_horiz_profile = double( orig_horiz_profile ) / 257;
% % orig_vert_profile = double( orig_vert_profile ) / 257;
 
%
status.error = 1;
status.scale = 0;
status.shift = 0;
status.luminance = 0;
shift.horizontal = 0;
shift.vertical = 0;
scale.horizontal = 1000;
scale.vertical = 1000;
 
 
% try
    % set up constants.
    max_scale = 100; % maximum scaling search, 10%
 
 
    [rows,cols, fps] = dll_video('size', fn); 
    durration = floor( dll_calib_video('total_sec',2) );
    if durration < num_sec,
        num_sec = durration;
    end
 
    if rows <= 216,
        max_shift_horiz = 4;  % maximum search in any direction, in # pixels
        max_shift_vert = 4;  
        max_scale_horiz = 60; % maximum scaling search, 
        max_scale_vert = 40;  
    elseif rows <= 384,
        max_shift_horiz = 8;
        max_shift_vert = 8;
        max_scale_horiz = 60;  
        max_scale_vert = 40;  
    else
        max_shift_horiz = 20;
        max_shift_vert = 20;
        max_scale_horiz = 100;  
        max_scale_vert = 60;  
    end
 
    % error checks & corrections
    if mod(max_shift_horiz,2),
        max_shift_horiz = max_shift_horiz + 1;
    end
    if mod(max_shift_vert,2),
        max_shift_vert = max_shift_vert + 1;
    end
 
    % compute PVR and OROI given the above.
    max_pixels_horiz = max_shift_horiz + (max_scale_horiz / 1000) * cols;
    max_pixels_horiz = ceil(max_pixels_horiz);
    max_pixels_horiz = max_pixels_horiz + mod(max_pixels_horiz,2);
    
    max_pixels_vert = max_shift_vert + (max_scale_vert / 1000) * rows; 
    max_pixels_vert = ceil(max_pixels_vert);
    max_pixels_vert = max_pixels_vert + mod(max_pixels_vert,2);
 
    [pvr, oroi] = find_pvr_oroi_guess(rows, cols, max_pixels_horiz, max_pixels_vert);
 
    % run the calibration algorithm
    [shift, scale, status] = sas_core_algorithm( ...
        num_sec, fps, max_shift_horiz, max_shift_vert, max_scale_horiz, ...
        max_scale_vert, max_pixels_horiz, max_pixels_vert, status, pvr, oroi, ...
        seed_state, orig_pixels, orig_horiz_profile, orig_vert_profile, no_scaling, fn);
       
    % check for results next to the search limit.
    if shift.horizontal == max_shift_horiz | shift.vertical == max_shift_vert,
        status.shift = status.shift + 1;
    end
    if abs(scale.horizontal - 1000) == max_scale_horiz | abs(scale.vertical - 1000) == max_scale_horiz,
        status.scale = status.scale + 1;
    end
    
    status.error = 0;
 
% catch
%     status.error = 1;
% end


 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Algorithm:
%
%   Use one frame every second (approx).  
%       horizontal and vertical profiles AND
%       80% as many randomly subsampled pixels as there are profile pixels
%           - randomly distributed over all frames
%   Search over ALL frames simultaneously.
%   Search original +- 0 second (yes! ZERO); 
%   shift processed +- 20 pixels/lines for NTSC (as specified else)
%   scale processed by +/- 10% for NTSC (as specified else)
%       rescale using nearest neighbor
%
%   When have final scale & shift, compute luminance gain & offset with
%   those pixels & profiles, too.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [new_spatial, new_scale, status] = sas_core_algorithm( ...
    num_sec, fps, max_shift_horiz, max_shift_vert, max_scale_horiz, max_scale_vert, ...
    max_pixels_horiz, max_pixels_vert, status, pvr, oroi, ...
    seed_state, orig_pixels, orig_horiz_profile, orig_vert_profile, no_scaling, fn);
% Compute spatial registration for one clip of fields..
 

new_spatial.horizontal = 0;
new_spatial.vertical = 0;
new_scale.horizontal = 1000;
new_scale.vertical = 1000;
 
 
% Choose the % of pixels to be used.
rows = oroi.bottom - oroi.top + 1;
cols = oroi.right - oroi.left + 1;
[list_row, list_col, list_time, list_o] = ...
    sas_choose_pixels (seed_state, rows, cols, num_sec, max_pixels_horiz, max_pixels_vert);
 
 
% load frames
dll_video('set_rewind', fn);
for loop = 1:num_sec,
    y(:,:,loop) = dll_calib_video('sec', fn, 1.0/fps); 
    if loop ~= num_sec,
        dll_video('discard', fn, (fps-1)/fps); 
    end
end
dll_video('rewind', fn);
 
y = double(y);
 
% cut out PVR
proc = y(pvr.top:pvr.bottom, pvr.left:pvr.right, :);
 
% Compute processed profiles.
[proc_horiz_profile, proc_vert_profile] = sas_profile_images(proc);
 
% reshape rows & columns into one dimension.
[rowp,colp,timep] = size(proc);
proc_y = reshape(proc, rowp*colp*timep,1);
clear proc;
 
%list of coordinates for profiles
list_horiz_profile = (1:cols) + max_pixels_horiz;
list_vert_profile = (1:rows) + max_pixels_vert;
 
 
if no_scaling,
    max_scale_vert = 0;
    max_scale_horiz = 0;
end
 
length_of_list = length(list_o);
    
% random search.
loop = 1;
best_scale_horiz = 1000;
best_scale_vert = 1000;
best_shift_horiz = 0;
best_shift_vert = 0;
best_value = inf;
best_loop = -1;
 
values = zeros(2*max_scale_horiz+1,2*max_scale_vert+1,2*max_shift_horiz+1,2*max_shift_vert+1);
values(:,:,:,:) = NaN;
 
random_tries = 15000; % hope it needn't be that large!
loop = 0;
cnt_used = 0;
while loop <= random_tries,
 
    % randomly choose stretch, shift, and time.
 
    % for the first 10% of tries, do a flat random search over all possibilities.
    if loop < random_tries / 10,
        scale_horiz = round( -max_scale_horiz + (2 * max_scale_horiz + 1) * rand );
        scale_vert =  round( -max_scale_vert + (2 * max_scale_vert + 1) * rand );
        shift_horiz = round( -max_shift_horiz + (2 * max_shift_horiz + 1) * rand );
        shift_vert =  round( -max_shift_vert + (2 * max_shift_vert + 1) * rand );
    % weight more near best stretch/shift/time found so far.  
    else
        scale_horiz = best_scale_horiz + round( 2 * randn );
        scale_vert = best_scale_vert + round( 2 * randn );
        shift_horiz = best_shift_horiz + round( 2 * randn );
        shift_vert = best_shift_vert + round( 2 * randn );
    end
    
    if max_scale_horiz == 0,
        scale_horiz = 0;
    end
    if max_scale_vert == 0,
        scale_vert = 0;
    end
 
    % If this point is out of the legal range, choose again.
    if abs(scale_horiz) > max_scale_horiz | abs(scale_vert) > max_scale_vert | ...
            abs(shift_horiz) > max_shift_horiz | abs(shift_vert) > max_shift_vert,
        continue;
    end
    % check whether this stretch/shift/time has been computed already. 
    if ~isnan( values(scale_horiz + max_scale_horiz + 1, scale_vert + max_scale_vert + 1, ...
        shift_horiz + max_shift_horiz + 1, shift_vert + max_shift_vert + 1) ),
        loop = loop + 1;
        continue;
    end
    cnt_used = cnt_used + 1;
 
    % convert from chosen original coordinates (in smaller image) into
    % processed coordinates.  
    % First, scale.  The "-0.4" is because the resampling routine we are
    % training for his this factor.
    curr_list_row = list_row * 1000 / (scale_vert+1000) + (rowp/2 - (1000/(scale_vert+1000)) * rowp/2);
    curr_list_col = list_col * 1000 / (scale_horiz+1000) + (colp/2 - (1000/(scale_horiz+1000)) * colp/2);
    
    % Second, shift.  Round to nearest pixel (use floor of +0.5 for speed)
    curr_list_row = floor(curr_list_row + shift_vert + 0.5);
    curr_list_col = floor(curr_list_col + shift_horiz + 0.5);
    list_p = (list_time-1)*rowp*colp + (curr_list_col-1)*rowp + curr_list_row;
 
    % compute the difference value of the random pixels.
    pixel_list = orig_pixels - proc_y(list_p);
    
    % scale the profiles.
    curr_list_row = list_vert_profile * 1000 / ...
        (scale_vert+1000) + (rowp/2 - (1000/(scale_vert+1000)) * rowp/2);
    curr_list_col = list_horiz_profile * 1000 / ...
        (scale_horiz+1000) + (colp/2-(1000/(scale_horiz+1000)) * colp/2);
 
    % Second, shift the profiles.  Round to nearest pixel (use floor of +0.5 for speed)
    curr_list_row = floor(curr_list_row + shift_vert + 0.5);
    curr_list_col = floor(curr_list_col + shift_horiz + 0.5);
 
    % compute the difference value of the profiles.  Append that to random
    % pixels' differneces.
    vert_diff = orig_vert_profile - proc_vert_profile(curr_list_row,:);
    horiz_diff = orig_horiz_profile - proc_horiz_profile(curr_list_col,:);
    pixel_list = [ pixel_list; reshape(vert_diff,rows*num_sec,1); reshape(horiz_diff,cols*num_sec,1)];
 
    % Compute and record standard deviation of difference.
    curr_value = std(pixel_list);
    values(scale_horiz + max_scale_horiz + 1, scale_vert + max_scale_vert + 1, ...
        shift_horiz + max_shift_horiz + 1, shift_vert + max_shift_vert + 1) = curr_value;
 
    % keep track of the best one!
    % if find a tie, change to it if the scaling factor is closer to no
    % scaling & no shifting
    want = 0;
    if curr_value < best_value,
        want = 1;
    elseif curr_value == best_value,
        if abs(scale_horiz) <= abs(best_scale_horiz) & abs(scale_vert) <= abs(best_scale_vert) & ...
                abs(shift_horiz) <= abs(best_shift_horiz) & abs(shift_vert) <= abs(best_shift_vert),
            want = 1;
        end
    end
    if want,
        best_scale_horiz = scale_horiz;
        best_scale_vert = scale_vert;
        best_shift_horiz = shift_horiz;
        best_shift_vert = shift_vert;
        best_value = curr_value;
        best_loop = loop;
    end
 
    loop = loop + 1;
end
 
scale_horiz = best_scale_horiz;
scale_vert = best_scale_vert;
shift_horiz = best_shift_horiz;
shift_vert = best_shift_vert;
 
 
% send back all results.
new_spatial.horizontal = best_shift_horiz;
new_spatial.vertical = best_shift_vert;
new_scale.horizontal = best_scale_horiz+1000;
new_scale.vertical = best_scale_vert+1000;


 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [pvr] = find_pvr_guess(rows, cols);
% return the best guess for pvr based only on image size.
% i.e., discard overscan.
% 'fchoice' is 1 for 'field' or 0 for 'frame' depending on which is desired.
 
if (rows == 486 | rows == 480 ) ...
        & cols == 720,
    pvr.top = 19;
    pvr.bottom = 486 - 18;
    pvr.left = 23;
    pvr.right = 720 - 22;
elseif rows == 576 & cols == 720,
    pvr.top = 15;
    pvr.bottom = 576 - 14;
    pvr.left = 23;
    pvr.right = 720 - 22;
else
    pvr.top = 1;
    pvr.bottom = rows;
    pvr.left = 1;
    pvr.right = cols;
end


 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [pvr, oroi] = find_pvr_oroi_guess(rows, cols, max_pixels_horiz, max_pixels_vert);
% Find best guess at PVR and OROI, given image size and
% size of maximum search ('max_pixels_horiz'), in pixels.
 
pvr = find_pvr_guess(rows, cols);
oroi.top = pvr.top + max_pixels_vert;
oroi.bottom = pvr.bottom - max_pixels_vert;
oroi.left = pvr.left + max_pixels_horiz;
oroi.right = pvr.right - max_pixels_horiz;

 
 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [list_row, list_col, list_time, list_o] = ...
    sas_choose_pixels (seed_state, rows, cols, time, max_pixels_horiz, max_pixels_vert);
 
% Choose 80% as many random points as profile points.
need = ceil(0.80 * (rows+cols)*time );
 
rand('state', seed_state);
list_row = round(rand(1,need) * (rows) + 0.5);
list_col = round(rand(1,need) * (cols) + 0.5);
list_time = round(rand(1,need) * (time) + 0.5);
 
% limit to range available.  'rand' is unlikely to exceed that range, but
% it is possible.
list_row = max( min(list_row,rows), 1);
list_col = max( min(list_col,cols), 1);
list_time = max( min(list_time,time), 1);
 
list_o = (list_time-1)*rows*cols + (list_col-1)*rows + list_row;
 
% change coordinates to be for processed, where there are more rows &
% columns.
list_row = list_row + max_pixels_vert;
list_col = list_col + max_pixels_horiz;

 
 
%%%%%%%%%%%
function [horiz_profile, vert_profile] = sas_profile_images(y);
% Compute the horizontal profile for each frame (averaging each column).
% Put all such together into one giant image.
% Return that image in profile_image.
 
[rows,cols,time] = size(y);
 
horiz_profile = zeros(cols, time);
vert_profile = zeros(rows, time);
for cnt = 1:time,
    horiz_profile(:,cnt) = mean(y(:,:,cnt))';
    vert_profile(:,cnt) = mean(y(:,:,cnt), 2);
end
