Example #1
0
def calc_phasevec(waves, basismat, method='scalar', apt_mask=None, mlagrid=None, scale=1, weigh=True, cache=None):
	"""
	Compute phase vector in a certain basis set from filtered complex waves 
	(from filter_sideband()).

	- Scalar method: measure phase as arctangent of complex components, fit basis modes to recovered shape
	- Gradient method: same as scalar, but subsequently take the gradient of the phase to fit basis modes onto. This is better because it works around singularities and other discontinuities
	- Virtual Shack Hartmann (vshwfs) method: compute virtual Shack Hartmann image from the complex input waves, then use this to measure the local phase gradients and reconstruct into a set of basis modes, also sidetepping potential singularity problems [rueckel2006].

	For gradient and vshwfs, we compute a derivative of the basis modes 
	matrix suitable for fitting these data to. These are stored in the 
	dictionary **cache** which can be reused.

	@param [in] waves List of complex waves
	@param [in] basis Matrix with set of basis modes of shape (∏(waves.shape), n)
	@param [in] method Phase computation method, one of (scalar, gradient, vshwfs)
	@param [in] apt_mask Aperture mask (only for scalar, gradient)
	@param [in] mlagrid SH microlens array grid (only for vshwfs)
	@param [in] scale SH FFT zoom scale (only for vshwfs)
	@param [in] cache Dict to cache static stuff in
	@return Tuple of (phasvec, wfs image)
	"""

	if (method in ['scalar', 'gradient']):
		if (apt_mask == None):
			# slice(None) doesn't work because we cannot ravel() it
			apt_mask = np.ones(waves[0].shape, dtype=bool)
	elif (method == 'vshwfs'):
		if (mlagrid == None):
			raise RuntimeError("Require mlagrid when using vshwfs method")
		else:
			mlagrid = np.array(mlagrid)

	if (method == 'scalar'):
		phasewr, amp = avg_phase(waves)
		phase = flood_quality(phasewr, amp)
		phase -= phase[apt_mask].mean()

		wfsimg = phase

		# Fit basis modes, weigh with amplitude
		if (weigh):
			weight = amp[apt_mask]
		else:
			weight = np.ones(amp[apt_mask].shape)
		phasew = (phase[apt_mask] * weight).ravel()
		basismatw = (basismat[apt_mask.ravel()] * weight.reshape(-1,1))
		modevec = np.linalg.lstsq(basismatw, phasew)[0]
		
	elif (method == 'gradient'):
		phasewr, amp = avg_phase(waves)
		phase = flood_quality(phasewr, amp)

		wfsimg = phase - phase.mean()

		# Compute horizontal and vertical gradient in phase
		phgradvec = phase_grad(phase, apt_mask=apt_mask, asvec=True)

		# Concatenate amp and apt_mask to get same size as gradient vector
		amp2vec = np.hstack([amp[apt_mask].ravel(), amp[apt_mask].ravel()])
		apt2mask = np.hstack([apt_mask.ravel(), apt_mask.ravel()])

		# Check if we have the gradient basis matrix
		try:
			grad_basismat = cache['grad_basismat']
		except (KeyError, TypeError) as e:
			# KeyError: cache empty, TypeError: cache=None
			grad_basismat = np.r_[ [phase_grad(mode.reshape(waves[0].shape), apt_mask=apt_mask, asvec=True) for mode in basismat.T] ].T
			try:
				cache['grad_basismat'] = grad_basismat
			except:
				pass

		# Apply weighting 
		if (weigh):
			weight = amp2vec
		else:
			weight = np.ones(amp2vec.shape)
		# Compute basis modes from gradients
		grad_basismatw = grad_basismat * weight.reshape(-1,1)
		phasew = (phgradvec*weight).ravel()
		modevec = np.linalg.lstsq(grad_basismatw, phasew)[0]

	elif (method == 'vshwfs'):
		# Compute median (reject outliers) of series of virtual shwfs images
		wfsimg = vshwfs_im = np.median([tim.shwfs.sim_shwfs(wv, mlagrid, scale=scale) for wv in waves], axis=0)
		# Measure shift vector for all subapertures
		sasz = (mlagrid[:, 1::2] - mlagrid[:, ::2])
		vshwfs_vec = np.array([tim.shwfs.calc_cog(vshwfs_im[m[0]:m[1],m[2]:m[3]], index=True) for m in mlagrid]) - sasz/2.
		# Compute intensity in each subaperture to use as fitting weight
		vshwfs_pow = np.array( [vshwfs_im[m[0]:m[1],m[2]:m[3]].mean() for m in mlagrid] )
		vshwfs_pow = np.repeat(vshwfs_pow, 2)

		# Compute matrix of vSHWFS response for all basis modes
		try:
			vshwfs_basismat = cache['vshwfs_basismat']
		except (KeyError, TypeError) as e:
			# KeyError: cache empty, TypeError: cache=None
			shwfsmat = np.r_[ [tim.shwfs.sim_shwfs(np.exp(1j*mode.reshape(waves[0].shape)), mlagrid, scale=scale) for mode in basismat.T] ]
			vshwfs_basismat = np.r_[ [([tim.shwfs.calc_cog(shwfsim[m[0]:m[1],m[2]:m[3]], index=True) for m in mlagrid] - sasz/2.).ravel() for shwfsim in shwfsmat] ].T
			try:
				cache['vshwfs_basismat'] = vshwfs_basismat
			except:
				pass

		if (weigh):
			weight = vshwfs_pow
		else:
			weight = np.ones(vshwfs_pow.shape)

		# Fit phase as mode vector using basis mode matrix
		vshwfs_basismatw = vshwfs_basismat * weight.reshape(-1,1)
		vshwfsw = vshwfs_vec.ravel() * weight
		modevec = np.linalg.lstsq(vshwfs_basismatw, vshwfsw)[0]

		# Example of above weighted least sq.
		#
		# vshwfs_basismat = np.r_[[np.arange(100)*0+1], [np.arange(100)], [np.arange(100)**2]].T
		# vshwfs_vec = np.dot(vshwfs_basismat, [0.5, 0.1, 0.001])
		# weightmask = np.random.random((100)) < 0.7
		# weight = np.ones(100)
		# vshwfs_vec[weightmask] = np.random.random(sum(weightmask))*np.mean(vshwfs_vec)

		# vshwfs_basismatw = vshwfs_basismat * weight.reshape(-1,1)
		# vshwfsw = vshwfs_vec.ravel() * weight
		# print np.linalg.lstsq(vshwfs_basismatw, vshwfsw)[0]
		
		# weight[weightmask] = 0.1
		# vshwfs_basismatw = vshwfs_basismat * weight.reshape(-1,1)
		# vshwfsw = vshwfs_vec.ravel() * weight
		# print np.linalg.lstsq(vshwfs_basismatw, vshwfsw)[0]

	else:
		raise RuntimeError("Method not in ['scalar', 'gradient', 'vshwfs']")

	return modevec, wfsimg
Example #2
0
def filter_sideband(img, cfreq, sbsize, method='spectral', apt_mask=None, unwrap=True, wsize=-0.5, wfunc='cosine', do_embed=True, cache={}, ret_pow=False, get_complex=False, verb=0):
	"""
	Filter out sideband from a real image, return phase and amplitude. Phase
	is returned in radians, complex components are given 'as-is'.

	For extracting the sideband we have three options:
	1. Shift carrier frequency to origin using spectral shifting
	2. Select a passband around the carrier frequency in Fourier space
	3. Select a circular frequency region around the carrier frequency in Fourier space (work in progress)

	# Spectral method (recommended):
	1. Shift frequencies in image space such that carrier frequency is at the origin
	2. Apodise image and Fourier transform
	3. Lowpass filter around frequency zero. Due to spectral shifting, this selects our carrier frequency.
	4. Inverse transform, get complex components
	5. Calculate phase and unwrap

	# Passband method (not recommended):
	1. Apodise image and Fourier transform
	2. Bandpass filter around carrier frequency
	3. Half-plane cut and inverse transform
	4. Calculate phase and unwrap
	5. Remove carrier frequency tilt in phase

	# Circular method: (WIP)
	1. Apodise image and Fourier transform
	2. Apply circular filter around carrier frequency
	3. Inverse transform single sideband
	4. Calculate phase and unwrap
	5. Remove carrier frequency tilt in phase

	@param [in] img CCD image to process
	@param [in] cfreq Carrier frequency in cycles/frame. If 'None', remove all tip-tilt from output phase.
	@param [in] sbsize The size of the sideband extraction region, as fraction of the magnitude of cfreq
	@param [in] method Extraction method to use, one of ('spectral', 'bandpass')
	@param [in] apt_mask Boolean mask to select ROI
	@param [in] unwrap Unwrap phase
	@param [in] wsize Window size for apodisation mask, see tim.fft.mk_apod_mask
	@param [in] wfunc Window function for apodisation mask, see tim.fft.mk_apod_mask
	@param [in] cache Will be filled with cached items. Re-supply next call to speed up process
	@param [in] ret_pow Return Fourier power around **cfreq** as well.
	@param [in] verb Verbosity
	@returns Tuple of (phase [rad], amplitude) as numpy.ndarrays
	"""

	cfreq = np.asanyarray(cfreq)
	
	# Try to user faster fftw routines here, fallback to numpy versions
	try:
		fft2func = pyfftw.interfaces.numpy_fft.fft2
		ifft2func = pyfftw.interfaces.numpy_fft.ifft2
		# Enable cache, set timeout to one day because cache culling has a race condition (https://github.com/hgomersall/pyFFTW/issues/21)
		pyfftw.interfaces.cache.enable()
		pyfftw.interfaces.cache.set_keepalive_time(3600*24)
	except:
		fft2func = np.fft.fft2
		ifft2func = np.fft.ifft2

	if (method == 'spectral'):
		# 1a. Calculate shift matrix
		if cache.has_key('spec_sshift'):
			sshift = cache['spec_sshift']
		else:
			slope = np.indices(img.shape, dtype=np.float) / np.r_[img.shape].reshape(-1,1,1)
			slope = np.sum(slope * cfreq.reshape(-1,1,1), 0)
			sshift = np.exp(-1j * 2 * np.pi * slope)
			cache['spec_sshift'] = sshift

		# 1b. Shift image after removing the mean (0.6ms)
		img_sh = (img - img.mean()) * sshift

		# 2. Apodise image (0.280ms)
		if cache.has_key('spec_apodmask'):
			apod_mask = cache['spec_apodmask']
		else:
			apod_mask = tim.fft.mk_apod_mask(img.shape, wsize=wsize, shape='circ', apod_f=wfunc)
			cache['spec_apodmask'] = apod_mask

		img_apod = img_sh * apod_mask

		# 2b. FFT image (7.8ms)
		if (do_embed):
			img_sh_ft = fft2func(img_apod, s=tuple(apod_mask.shape*np.r_[2]))
		else:
			img_sh_ft = fft2func(img_apod)

		# 3. Lowpass filter (1.3ms)
		lowpass = (1+do_embed) * sbsize * (np.r_[cfreq]**2.0).sum()**0.5
		if cache.has_key('spec_lowpassmask'):
			lowpass_mask = cache['spec_lowpassmask']
		else:
			lowpass_mask = tim.fft.mk_apod_mask(img_sh_ft.shape, apodsz=lowpass*2, shape='circle', wsize=-.5, apod_f=wfunc)
			lowpass_mask = np.fft.ifftshift(lowpass_mask)
			cache['spec_lowpassmask'] = lowpass_mask
		img_sh_filt = img_sh_ft * lowpass_mask

		if (ret_pow):
			sz = img_sh_filt.shape
			img_sh_filts = np.fft.fftshift(img_sh_filt)
			fftpow = np.abs(img_sh_filts[sz[0]/2-lowpass:sz[0]/2+lowpass,
				sz[1]/2-lowpass:sz[1]/2+lowpass].copy())**2.0

		# 4. IFFT (8.3ms), get complex components
		img_ifft = ifft2func(img_sh_filt)
		if (do_embed):
			img_ifft = img_ifft[:img.shape[0],:img.shape[1]].copy()

		# 4b. Sometimes we only need the complex components
		if (get_complex):
			return img_ifft

		# 5. Calculate phase and unwrap
		phase_wr = np.arctan2(img_ifft.imag, img_ifft.real)
		amp = np.abs(img_ifft**2.0)

		if (np.any(apt_mask)):
			phase_wr *= apt_mask
			amp *= apt_mask

		if (unwrap): phase = flood_quality(phase_wr, amp)
		else: phase = phase_wr

		if (np.any(apt_mask)):
			phase -= phase[apt_mask].mean()
			phase *= apt_mask
		else:
			phase -= phase.mean()

	elif (method == 'passband'):
		# 1. Apodise data, Fourier transform
		if cache.has_key('pass_apodmask'):
			apod_mask = cache['pass_apodmask']
		else:
			apod_mask = tim.fft.mk_apod_mask(img.shape, wsize=wsize, apod_f=wfunc)
			cache['pass_apodmask'] = apod_mask

		img_apod = (img - img.mean()) * apod_mask
		img_ft = tim.fft.descramble(np.fft.fft2(img_apod))

		# 2. Bandpass filter around carrier frequency
		if cache.has_key('pass_filtmask'):
			filt_mask = cache['pass_filtmask']
		else:
	 		fa_inner = sbsize * (np.r_[cfreq]**2.0).sum()**0.5
	 		fa_outer = 1.0/sbsize * (np.r_[cfreq]**2.0).sum()**0.5
			filt_mask_in = 1.0-tim.fft.mk_apod_mask(img_ft.shape, apodsz=fa_inner*2, shape='circle', wsize=-.5, apod_f=wfunc)
			filt_mask_out = tim.fft.mk_apod_mask(img_ft.shape, apodsz=fa_outer*2, shape='circle', wsize=-.5, apod_f=wfunc)
	 		filt_mask = filt_mask_in * filt_mask_out
	 		cache['pass_filtmask'] = filt_mask

		img_filt = img_ft * filt_mask

		if (ret_pow):
			sz = img_filt.shape
			fa_outer = 1.0/sbsize * (np.r_[cfreq]**2.0).sum()**0.5
			fftpow = np.abs(img_filt[sz[0]/2:sz[0]/2+fa_outer,
				sz[1]/2:sz[1]/2+fa_outer].copy())**2.0

		# 3. Half-plane cut and inverse transform
		## @todo How to handle odd image sizes?
		imgsh = img_filt.shape
		if (imgsh[0] % 2 == 0):
			if (cfreq[0] > 0):
				img_filt[:imgsh[0]/2] = 0
			else:
				img_filt[imgsh[0]/2:] = 0
		elif (imgsh[1] % 2 == 0):
			if (cfreq[1] > 0):
				img_filt[:, :imgsh[1]/2] = 0
			else:
				img_filt[:, imgsh[1]/2:] = 0
		else:
			img_filt[:, :imgsh[1]/2] = 0
		
		# 4. IFFT, get complex components
		img_ifft = np.fft.ifft2(tim.fft.descramble(img_filt, -1))

		# 4b. Sometimes we only need the complex components
		if (get_complex):
			return img_ifft

		# 5. Calculate phase and unwrap
		phase_wr = np.arctan2(img_ifft.imag, img_ifft.real)
		amp = np.abs(img_ifft**2.0)

		if (np.any(apt_mask)):
			phase_wr *= apt_mask
			amp *= apt_mask

		if (unwrap): phase = flood_quality(phase_wr, amp)
		else: phase = phase_wr

		# 5. Calculate slope to subtract
		## @todo This can be improved by using orthogonal vectors
		if cache.has_key('pass_slope'):
			slope = cache['pass_slope']
		else:
			slope = np.indices(phase.shape, dtype=np.float) / np.r_[phase.shape].reshape(-1,1,1)
			slope = np.sum(slope * np.round(cfreq).reshape(-1,1,1), 0)
			slope -= slope.mean()
			cache['pass_slope'] = slope
		
		phase -= slope

		if (np.any(apt_mask)):
			phase -= phase[apt_mask].mean()
			phase *= apt_mask
		else:
			phase -= phase.mean()
	elif (method == 'circular'):
		# circular method:
		# - Apodise, Fourier transform
		# - Apply circular filter around carrier frequency
		# - Inverse transform single sideband
		# - Remove carrier frequency tilt in phase
		raise RuntimeWarning("Work in progress")

		apod_mask = tim.fft.mk_apod_mask(img.shape, wsize=wsize, apod_f=wfunc)
		img_apod = (img - img.mean()) * apod_mask

		tim.im.inter_imshow(img_apod, doshow=verb%100>=30, desc="circular::apod img")

		img_ft = tim.fft.descramble(np.fft.fft2(img_apod))

		# Circular-band filter, radius of half the carrier frequency
 		band_rad = 0.5 * (np.r_[cfreq]**2.0).sum()**0.5
 		band_pos = np.r_[cfreq]

		filt_mask = tim.fft.mk_apod_mask(img_ft.shape, apodpos=band_pos, apodsz=band_rad*2, shape='circle', wsize=-.5, apod_f=wfunc)

		img_filt = img_ft * filt_mask
		if (ret_pow):
			fftpow = None
		phase = None
		amp = None
	else:
		raise ValueError("Unknown method '%s'" % (method))

	if (ret_pow):
		return (phase, amp, fftpow)
	else:
		return (phase, amp)
Example #3
0
def filter_sideband(img, cfreq, sbsize, method='spectral', apt_mask=None, unwrap=True, wsize=-0.5, wfunc='cosine', do_embed=True, cache={}, ret_pow=False, pow_list=None, get_complex=False, verb=0):
	"""
	Filter out sideband from a real image, return phase and amplitude. Phase
	is returned in radians, complex components are given 'as-is'.

	For extracting the sideband we have three options:
	1. Shift carrier frequency to origin using spectral shifting
	2. Select a passband around the carrier frequency in Fourier space
	3. Select a circular frequency region around the carrier frequency in Fourier space (work in progress)

	# Spectral method (recommended):
	1. Shift frequencies in image space such that carrier frequency is at the origin
	2. Apodise image and Fourier transform
	3. Lowpass filter around frequency zero. Due to spectral shifting, this selects our carrier frequency.
	4. Inverse transform, get complex components
	5. Calculate phase and unwrap

	# Passband method (not recommended):
	1. Apodise image and Fourier transform
	2. Bandpass filter around carrier frequency
	3. Half-plane cut and inverse transform
	4. Calculate phase and unwrap
	5. Remove carrier frequency tilt in phase

	# Circular method: (WIP)
	1. Apodise image and Fourier transform
	2. Apply circular filter around carrier frequency
	3. Inverse transform single sideband
	4. Calculate phase and unwrap
	5. Remove carrier frequency tilt in phase

	@param [in] img CCD image to process
	@param [in] cfreq Carrier frequency in cycles/frame. If 'None', remove all tip-tilt from output phase.
	@param [in] sbsize The size of the sideband extraction region, as fraction of the magnitude of cfreq
	@param [in] method Extraction method to use, one of ('spectral', 'bandpass')
	@param [in] apt_mask Boolean mask to select ROI
	@param [in] unwrap Unwrap phase
	@param [in] wsize Window size for apodisation mask, see tim.fft.mk_apod_mask
	@param [in] wfunc Window function for apodisation mask, see tim.fft.mk_apod_mask
	@param [in] cache Will be filled with cached items. Re-supply next call to speed up process
	@param [in] ret_pow Return Fourier power around **cfreq** as well.
	@param [in] pow_list If a list, store FT power here
	@param [in] verb Verbosity
	@returns Tuple of (phase [rad], amplitude) as numpy.ndarrays
	"""

	try:
		assert len(cfreq) == 2
	except Exception as e:
		raise e.__class__("cfreq should be a 2-element vector (%s)"%e.message)

	assert type(sbsize) in [float, int], "sbsize should be a real scalar"
	assert type(wsize) in [float, int], "wsize should be a real scalar"

	cfreq = np.asanyarray(cfreq)
	
	# Try to user faster fftw routines here, fallback to numpy versions
	try:
		fft2func = pyfftw.interfaces.numpy_fft.fft2
		ifft2func = pyfftw.interfaces.numpy_fft.ifft2
		# Enable cache, set timeout to one day because cache culling has a race condition (https://github.com/hgomersall/pyFFTW/issues/21)
		pyfftw.interfaces.cache.enable()
		pyfftw.interfaces.cache.set_keepalive_time(3600*24)
	except:
		fft2func = np.fft.fft2
		ifft2func = np.fft.ifft2

	if (method == 'spectral'):
		# 1a. Calculate shift matrix
		if cache.has_key('spec_sshift'):
			sshift = cache['spec_sshift']
		else:
			slope = np.indices(img.shape, dtype=np.float) / np.r_[img.shape].reshape(-1,1,1)
			slope = np.sum(slope * cfreq.reshape(-1,1,1), 0)
			sshift = np.exp(-1j * 2 * np.pi * slope)
			cache['spec_sshift'] = sshift

		# 1b. Shift image after removing the mean (0.6ms)
		img_sh = (img - img.mean()) * sshift

		# 2. Apodise image (0.280ms)
		if cache.has_key('spec_apodmask'):
			apod_mask = cache['spec_apodmask']
		else:
			apod_mask = tim.fft.mk_apod_mask(img.shape, wsize=wsize, shape='circ', apod_f=wfunc)
			cache['spec_apodmask'] = apod_mask

		img_apod = img_sh * apod_mask

		# 2b. FFT image (7.8ms)
		if (do_embed):
			img_sh_ft = fft2func(img_apod, s=tuple(apod_mask.shape*np.r_[2]))
		else:
			img_sh_ft = fft2func(img_apod)

		# 3. Lowpass filter (1.3ms)
		lowpass = (1+do_embed) * sbsize * (np.r_[cfreq]**2.0).sum()**0.5
		if cache.has_key('spec_lowpassmask'):
			lowpass_mask = cache['spec_lowpassmask']
		else:
			lowpass_mask = tim.fft.mk_apod_mask(img_sh_ft.shape, apodsz=lowpass*2, shape='circle', wsize=-.5, apod_f=wfunc)
			lowpass_mask = np.fft.ifftshift(lowpass_mask)
			cache['spec_lowpassmask'] = lowpass_mask
		img_sh_filt = img_sh_ft * lowpass_mask

		if (ret_pow or pow_list != None):
			sz = img_sh_ft.shape
			img_sh_fts = np.fft.fftshift(img_sh_ft)
			fftpow = np.abs(img_sh_fts[sz[0]/2-lowpass:sz[0]/2+lowpass,
				sz[1]/2-lowpass:sz[1]/2+lowpass].copy())**2.0
			try:
				pow_list.append(np.abs(img_sh_fts)**2.0)
			except:
				pass

		# 4. IFFT (8.3ms), get complex components
		img_ifft = ifft2func(img_sh_filt)
		if (do_embed):
			img_ifft = img_ifft[:img.shape[0],:img.shape[1]].copy()

		# 4b. Sometimes we only need the complex components
		if (get_complex):
			return img_ifft

		# 5. Calculate phase and unwrap
		phase_wr = np.arctan2(img_ifft.imag, img_ifft.real)
		amp = np.abs(img_ifft**2.0)

		if (np.any(apt_mask)):
			phase_wr *= apt_mask
			amp *= apt_mask

		if (unwrap): phase = flood_quality(phase_wr, amp)
		else: phase = phase_wr

		if (np.any(apt_mask)):
			phase -= phase[apt_mask].mean()
			phase *= apt_mask
		else:
			phase -= phase.mean()

	elif (method == 'passband'):
		# 1. Apodise data, Fourier transform
		if cache.has_key('pass_apodmask'):
			apod_mask = cache['pass_apodmask']
		else:
			apod_mask = tim.fft.mk_apod_mask(img.shape, wsize=wsize, apod_f=wfunc)
			cache['pass_apodmask'] = apod_mask

		img_apod = (img - img.mean()) * apod_mask
		img_ft = tim.fft.descramble(np.fft.fft2(img_apod))

		# 2. Bandpass filter around carrier frequency
		if cache.has_key('pass_filtmask'):
			filt_mask = cache['pass_filtmask']
		else:
	 		fa_inner = sbsize * (np.r_[cfreq]**2.0).sum()**0.5
	 		fa_outer = 1.0/sbsize * (np.r_[cfreq]**2.0).sum()**0.5
			filt_mask_in = 1.0-tim.fft.mk_apod_mask(img_ft.shape, apodsz=fa_inner*2, shape='circle', wsize=-.5, apod_f=wfunc)
			filt_mask_out = tim.fft.mk_apod_mask(img_ft.shape, apodsz=fa_outer*2, shape='circle', wsize=-.5, apod_f=wfunc)
	 		filt_mask = filt_mask_in * filt_mask_out
	 		cache['pass_filtmask'] = filt_mask

		img_filt = img_ft * filt_mask

		if (ret_pow):
			sz = img_filt.shape
			fa_outer = 1.0/sbsize * (np.r_[cfreq]**2.0).sum()**0.5
			fftpow = np.abs(img_filt[sz[0]/2:sz[0]/2+fa_outer,
				sz[1]/2:sz[1]/2+fa_outer].copy())**2.0

		# 3. Half-plane cut and inverse transform
		## @todo How to handle odd image sizes?
		imgsh = img_filt.shape
		if (imgsh[0] % 2 == 0):
			if (cfreq[0] > 0):
				img_filt[:imgsh[0]/2] = 0
			else:
				img_filt[imgsh[0]/2:] = 0
		elif (imgsh[1] % 2 == 0):
			if (cfreq[1] > 0):
				img_filt[:, :imgsh[1]/2] = 0
			else:
				img_filt[:, imgsh[1]/2:] = 0
		else:
			img_filt[:, :imgsh[1]/2] = 0
		
		# 4. IFFT, get complex components
		img_ifft = np.fft.ifft2(tim.fft.descramble(img_filt, -1))

		# 4b. Sometimes we only need the complex components
		if (get_complex):
			return img_ifft

		# 5. Calculate phase and unwrap
		phase_wr = np.arctan2(img_ifft.imag, img_ifft.real)
		amp = np.abs(img_ifft**2.0)

		if (np.any(apt_mask)):
			phase_wr *= apt_mask
			amp *= apt_mask

		if (unwrap): phase = flood_quality(phase_wr, amp)
		else: phase = phase_wr

		# 5. Calculate slope to subtract
		## @todo This can be improved by using orthogonal vectors
		if cache.has_key('pass_slope'):
			slope = cache['pass_slope']
		else:
			slope = np.indices(phase.shape, dtype=np.float) / np.r_[phase.shape].reshape(-1,1,1)
			slope = np.sum(slope * np.round(cfreq).reshape(-1,1,1), 0)
			slope -= slope.mean()
			cache['pass_slope'] = slope
		
		phase -= slope

		if (np.any(apt_mask)):
			phase -= phase[apt_mask].mean()
			phase *= apt_mask
		else:
			phase -= phase.mean()
	elif (method == 'circular'):
		# circular method:
		# - Apodise, Fourier transform
		# - Apply circular filter around carrier frequency
		# - Inverse transform single sideband
		# - Remove carrier frequency tilt in phase
		raise RuntimeWarning("Work in progress")

		apod_mask = tim.fft.mk_apod_mask(img.shape, wsize=wsize, apod_f=wfunc)
		img_apod = (img - img.mean()) * apod_mask

		tim.im.inter_imshow(img_apod, doshow=verb%100>=30, desc="circular::apod img")

		img_ft = tim.fft.descramble(np.fft.fft2(img_apod))

		# Circular-band filter, radius of half the carrier frequency
 		band_rad = 0.5 * (np.r_[cfreq]**2.0).sum()**0.5
 		band_pos = np.r_[cfreq]

		filt_mask = tim.fft.mk_apod_mask(img_ft.shape, apodpos=band_pos, apodsz=band_rad*2, shape='circle', wsize=-.5, apod_f=wfunc)

		img_filt = img_ft * filt_mask
		if (ret_pow):
			fftpow = None
		phase = None
		amp = None
	else:
		raise ValueError("Unknown method '%s'" % (method))

	if (ret_pow):
		return (phase, amp, fftpow)
	else:
		return (phase, amp)