Download
;+
; NAME:
;    	TR_GET_DISP.PRO
;
; PURPOSE:
;	Given a datacube of images, measure the rigid displacement
;	between each image and the first in the cube and optionally
;	shift each image to coalign the entire cube.  Uses a centered
;	2^n x 2^n window for coalignment.  Finds the whole-pixel
;	shift of maximum cross-correlation, then interpolates for
;	fractional pixel part either on cross-correlation function or
;	optionally on an array of squared mean absolute deviations (MAD).  
;	Shifts may be accurate to a few tenths of a pixel but don't
;	expect much better than that.
;
; CALLING SEQUENCE:
;	disp = tr_get_disp(data [,/shift][,mad=mad][,/debug])
;
; INPUTS:
;	data -- data cube of images, size (NX,NY,NT); 
;	        data(*,*,0)= reference image to which others are aligned
;
; KEYWORD PARAMETERS:
;	shift -- if set, data(*,*,1:*) are shifted to match the reference
;	mad -- if non-zero, use mad x mad array of MAD residuals to
;	        find final displacement; if 0 < mad < 5, uses 5x5 array
;	        USE ONLY IF IMAGES ARE SAME WAVELENGTHS AND EXPOSURE LEVELS
;	nowindow -- use the full frame in stead of centered window
;	debug  -- prints out parts of cross-correlation & MAD arrays
;
; OUTPUTS:
;	Returns array of displacements disp = fltarr(2,NT)
;	The sense is that data(i,j,0) <==> data(i-disp(0,k),j-disp(1,k),k)
;
;	You can shift the images afterwards using data = shift_img(data, disp)
;
; TO DO:
;	Needs better 2-D interpolation of sub-pixel displacement 
;	Needs better handling when mad x mad doesn't include the minimum
;
; MODIFICATION HISTORY:
;	29-Jan-98 (TDT) - adapted get_disp from H. Lin's flat fielding package ccdcal5.pro
;	 1-Jul-98 (TDT) - added shift keyword, shift_img using poly_2d
;	11-Sep-98 (TDT) - variable MAD search area, fixed bug in subarea size 
;	22-Sep-98 (TDT) - added cross-correlation only feature
;	 1-Oct-98 (TDT) - renamed tr_get_disp and put on-line
;	26-Oct-08 (AdW) - added nowindow option to align on the whole frame
;
;-

;  is_in_range		true where x is inside the interval [lo,hi]
;
function is_in_range, x, lo, hi
   return, (x ge lo) and (x le hi)
   end

;  hanning		alternative (flatter than one in ~idl/lib)
;			Hanning function.
;
;From H. Lin's file ccdcal5.pro, 29-Jan-98
;modified to not be square
function hanning, n, m

x = fltarr (n) + 1.0
y = fltarr (m) + 1.0
tenth =  long (n*.2)
cons = !pi/tenth
for i = 0,tenth do begin
   x(i) = (1.0 - cos (i*cons))/2.0
   x(n-i-1) = x(i)
endfor
tenth =  long (m*.2)
cons = !pi/tenth
for i = 0,tenth do begin
   y(i) = (1.0 - cos (i*cons))/2.0
   y(m-i-1) = y(i)
endfor

return, x # y
end

; shift_img		shift image by given offsets, using IDL 
;			routine poly_2d with cubic interpolation
;			Works on single image or datacube

function shift_img,img,offsets
sz = size(img)
if (sz(0) eq 2) then $
  return, poly_2d(img,[-offsets(0),0.,1.,0.],[-offsets(1),1.,0.,0.],cubic=-0.5)
if (sz(0) eq 3) then for i=0,sz(3)-1 do $
  img(*,*,i) = poly_2d(img(*,*,i),[-offsets(0,i),0.,1.,0.],[-offsets(1,i),1.,0.,0.],cubic=-0.5)
return,img
end
;
;  tr_get_disp	get the image displacements
;
;  Method: Correlation tracks the image sequence using a power-of-2
;  square area centered on the image(s).  First image of sequence
;  is the reference.  Returns array of pixel displacements of images
;  with respect to first image.
; The sense is that data(i,j,0) <==> data(i-disp(0,k),j-disp(1,k),k)
; Changed by TDT to return fractional pixel offsets,
;   added MAD algorithm  29-Jan-98
;   added shift keyword, 1-Jul-98:  if set, shifts all images to match img(*,*,0)
;   variable MAD search area (def = 5x5), fixed bug in subarea size, 11-Sep-98

; Sample calling sequences:
;  disp = tr_get_disp(data)
;  disp = tr_get_disp(data, /shift)
;  disp = tr_get_disp(data, /debug, mad=9)

function tr_get_disp, data, shift=shift, mad=mad, debug=debug, nowindow=nowindow

if not keyword_set(mad) then mad=0
nmad = mad > 5
nmad = 2*(nmad/2)+1
nmad2 = (nmad-1)/2
if not keyword_set(shift) then shift=0
if not keyword_set(debug) then debug=0
errorstring = 'Minimum MAD not in '+string(nmad,format='(I2)')+'^2 area--image #, xmin, ymin:'

sz = size(data)
nx = sz(1) & ny = sz(2) & nz = sz(3)
disp = fltarr (2,nz)

; ADW  20081025  option to use full frame
if keyword_set(nowindow) then begin
	nnx = nx
	nny = ny
endif else begin
	; TDT  11-Sep-98  added + 1.e-5 to make this work right!
	nnx = 2^long (alog10 (min ([nx, ny]))/.30103 + 1.e-5)
	nny = nnx
endelse

; TDT 29-Jan-98  added float to this next statement
nnsqd = float(nnx)*float(nny)
appodize = hanning (nnx, nny)

ref = data ((nx-nnx)/2:(nx+nnx)/2-1, (ny-nny)/2:(ny+nny)/2-1, 0)

tref = conj (fft ((ref-total(ref)/nnsqd)*appodize, -1))

for i = 1, nz-1 do begin
   scene = data ((nx-nnx)/2:(nx+nnx)/2-1,(ny-nny)/2:(ny+nny)/2-1, i)
   tscene = fft ((scene-total(scene)/nnsqd)*appodize, -1)
   cc = shift (abs (fft (tref*tscene, 1)), nnx/2, nny/2)
   printerror = 1

   mx = max (cc, loc)		; locate peak of Cross Correlation
   xmax0 = loc mod nnx
   ymax0 = loc/nnx
   xmax = ( (xmax0 > nmad2) < (nnx-nmad2-1) )
   ymax = ( (ymax0 > nmad2) < (nny-nmad2-1) )
   if debug then begin 
   	print,'Fourier Cross-correlation Peak: ',xmax0,ymax0
	print,cc(xmax-2:xmax+2,ymax-2:ymax+2), format='(5F8.1)'
   endif
   cc = -cc(xmax-nmad2:xmax+nmad2,ymax-nmad2:ymax+nmad2)
   
;   if (is_in_range (xmax,5,nnx-6) and is_in_range(ymax,5,nny-6) and (mad ne 0)) then begin
   if (mad) then begin

; Mean Absolute Difference algorithm centered on xmax & ymax

	cc = fltarr(nmad,nmad)
	dx = nnx/2-xmax
	dy = nny/2-ymax
	nnx2 = (nnx/2-abs(dx)-nmad2-1)/2
	nxl = nnx/2-nnx2
	nxh = nnx/2+nnx2
	nny2 = (nny/2-abs(dy)-nmad2-1)/2
	nyl = nny/2-nny2
	nyh = nny/2+nny2
	area = float(nxh-nxl+1)*float(nyh-nyl+1)

	for idx=-nmad2,nmad2 do begin
	for idy=-nmad2,nmad2 do begin
	cc(idx+nmad2,idy+nmad2)=total(appodize(nxl:nxh,nyl:nyh)*abs(ref(nxl:nxh,nyl:nyh) - $
	  scene(nxl-dx+idx:nxh-dx+idx,nyl-dy+idy:nyh-dy+idy)))/area
	endfor
	endfor
	cc = cc^2
	if debug then begin
	  print,'Squared MAD array:'
	  print,cc, format='('+string(nmad,format='(i2)')+'F8.1)'
	endif

     endif
; Locate minimum of MAD^2 or -Cross-correlation function
;   hope nmad x nmad is big enough to include minimum
	mx = min (cc, loc)		
	xmax7 = loc mod nmad
	ymax7 = loc/nmad
; 3 point parabolic fit, following Niblack, W.: Digital Image Processing,
; Prentice/Hall, 1986, p 139. 
; Need better 2-D peak interpolation routine here!
	if (xmax7 gt 0 and xmax7 lt (nmad-1) ) then begin
	  denom = mx*2 - cc(loc-1) - cc(loc+1)
	  xfra = (mx-cc(loc-1))/denom
	endif else begin 
	  xfra = 0
	  if (printerror) then print,errorstring,i,xmax7-nmad2,ymax7-nmad2
	  printerror=0
	endelse
	if (ymax7 gt 0 and ymax7 lt (nmad-1) ) then begin
	  denom = mx*2 - cc(loc-nmad) - cc(loc+nmad)
	  yfra = (mx-cc(loc-nmad))/denom
	endif else begin 
	  yfra = 0
	  if (printerror) then print,errorstring,i,xmax7-nmad2,ymax7-nmad2
	  printerror=0
	endelse

	xfra = xfra + xmax7 - nmad2-0.5
	yfra = yfra + ymax7 - nmad2-0.5
	if debug then print,xfra,yfra,format='("Fractional dx, dy: ",2F10.3)'
	xmax = xfra + xmax 
	ymax = yfra + ymax 

;      endif

   disp(0,i) = (nnx/2-xmax)
   disp(1,i) = (nny/2-ymax)
   
   if debug then print, i, disp(0,i), disp(1,i), $
     format='("Image ",I4, "    Final offsets ",2F10.2,/)'
   if (shift) then data(*,*,i) = shift_img(data(*,*,i),disp(*,i))
   endfor
   
return, disp
end