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
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)
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)