def mft_f2p(E_foc, fl, wavelength, dxi, deta, dx, N, centering='pixel'): """ Propagate a field from a focal plane to a pupil plane, using a matrix-multiply DFT. Parameters ---------- E_foc : array_like Electric field array in focal plane fl : float Focal length of Fourier transforming lens wavelength : float Propagation wavelength dxi : float Step size along horizontal axis of focal plane deta : float Step size along vertical axis of focal plane dx : float Step size along either axis of focal plane. The vertical and horizontal step sizes are assumed to be equal. N : int Number of datapoints along each side of the pupil-plane (output) array centering : string Whether the input and output arrays are pixel-centered or inter-pixel-centered. Possible values: 'pixel', 'interpixel' Returns ------- array_like Field in pupil plane, after propagating through Fourier transforming lens """ if centering not in _VALID_CENTERING: raise ValueError(_CENTERING_ERR) check.twoD_array(E_foc, 'E_foc', TypeError) check.real_scalar(fl, 'fl', TypeError) check.real_positive_scalar(wavelength, 'wavelength', TypeError) check.real_positive_scalar(dxi, 'dxi', TypeError) check.real_positive_scalar(deta, 'deta', TypeError) check.real_positive_scalar(dx, 'dx', TypeError) check.positive_scalar_integer(N, 'N', TypeError) Neta, Nxi = E_foc.shape dy = dx # Assume equal sample spacing along both directions # Focal-plane coordinates xi = util.create_axis(Nxi, dxi, centering=centering)[:, None] # Broadcast to column vector eta = util.create_axis(Neta, dxi, centering=centering)[None, :] # Broadcast to row vector # Pupil-plane coordinates x = util.create_axis(N, dx, centering=centering)[None, :] # Broadcast to row vector y = x.T # Column vector # Fourier transform matrices pre = np.exp(-2 * np.pi * 1j * (y * eta) / (wavelength * fl)) post = np.exp(-2 * np.pi * 1j * (xi * x) / (wavelength * fl)) # Constant scaling factor in front of Fourier transform scaling = np.sqrt(dx * dy * dxi * deta) / (1 * wavelength * fl) return scaling * np.linalg.multi_dot([pre, E_foc, post])
def test_twoD_square_array_good(self): """ Verify checker works correctly for valid input. Type: 2D array """ try: check.twoD_array(np.ones((5, 5)), '2d', TestCheckException) except check.CheckException: self.fail('twoD_square_array failed on valid input') pass
def test_twoD_array_bad_var(self): """ Fail on invalid variable type. Type: 2D array """ for v0 in [np.ones((5, )), np.ones((5, 5, 5)), [], 'foo']: with self.assertRaises(TestCheckException): check.twoD_array(v0, '2d', TestCheckException) pass pass pass
def add_noise_to_subband_image(mp, imageIn, iSubband): """ Add noise (photon shot, dark current, & read) to a simulated image. Parameters ---------- mp : ModelParameters structure of model parameters. imageIn : array_like 2-Dnoiseless starting image for a given subband [normalized intensity] iSubband : int index of subband in which the image was taken Returns ------- imageOut : array_like 2-D noisy image [normalized intensity] """ check.twoD_array(imageIn, 'imageIn', ValueError) check.nonnegative_scalar_integer(iSubband, 'iSubband', ValueError) peakCounts = (mp.detector.peakFluxVec[iSubband] * mp.detector.tExpVec[iSubband]) peakElectrons = mp.detector.gain * peakCounts imageInElectrons = peakElectrons * imageIn imageInCounts = 0 for iExp in range(mp.detector.Nexp): # Add photon shot noise noisyImageInElectrons = np.random.poisson(imageInElectrons) # Compute dark current darkCurrent = (mp.detector.darkCurrentRate * mp.detector.tExpVec[iSubband] * np.ones_like(imageIn)) darkCurrent = np.random.poisson(darkCurrent) # Compute Gaussian read noise readNoise = (mp.detector.readNoiseStd * np.random.randn(imageIn.shape[0], imageIn.shape[1])) # Convert back from e- to counts and then discretize imageInCounts = (imageInCounts + np.round( (noisyImageInElectrons + darkCurrent + readNoise) / mp.detector.gain) / mp.detector.Nexp) # Convert back from counts to normalized intensity imageOut = imageInCounts / peakCounts return imageOut
def ptp(E_in, full_width, wavelength, dz): """ Propagate an electric field array using the angular spectrum technique. Parameters ---------- E_in : array_like Square (i.e. NxN) input array. full_width : float The width along each side of the array [meters] wavelength : float Propagation wavelength [meters] dz : float Axial propagation distance [meters] Returns ------- array_like Field after propagating over distance dz. """ check.twoD_array(E_in, 'E_in', TypeError) check.real_positive_scalar(full_width, 'full_width', TypeError) check.real_positive_scalar(wavelength, 'wavelength', TypeError) check.real_scalar(dz, 'dz', TypeError) M, N = E_in.shape dx = full_width / N N_critical = int(np.floor(wavelength * np.abs(dz) / (dx ** 2))) # Critical sampling if M != N: # Input array is not square raise ValueError('Input array is not square') elif N < N_critical: log.warning( ''' Input array is undersampled. Minimum required samples: {} Actual: {} '''.format(N_critical, N)) fx = np.arange(-N // 2, N // 2) / full_width rho = util.radial_grid(fx) # Spatial frequency coordinate grid kernel = np.fft.fftshift(np.exp(-1j * np.pi * wavelength * dz * (rho ** 2))) intermediate = np.fft.fftn(np.fft.fftshift(E_in)) return np.fft.ifftshift(np.fft.ifftn(kernel * intermediate))
def relay(E_in, Nrelay, centering='pixel'): """ Perform re-imaging of the input E-field through optical relays. Propagate a field through Nrelay optical relays, without any intermediate mask multiplications. This results in a 180-degree rotation of the array for each optical relay. Correct centering of the array must be maintained. Parameters ---------- E_in : array_like Input electric field Nrelay: int Number of times to relay (and rotate by 180 degrees) centering : string Whether the input field is pixel-centered or inter-pixel-centered. If the array is pixel-centered, the output is shifted by 1 pixel in both axes after an odd number of relays. Returns ------- E_out : array_like The output E-field. Same as the input E-field but rotated by 180 degrees times the number of optical relays. """ if centering not in _VALID_CENTERING: raise ValueError(_CENTERING_ERR) check.twoD_array(E_in, 'E_in', TypeError) check.scalar_integer(Nrelay, 'Nrelay', TypeError) #--Only rotate if odd number of 180-degree rotations. If even, no change. if(np.mod(Nrelay,2)==1): # Reverse and scale input to account for propagation E_out = E_in[::-1, ::-1] if centering == 'pixel': # Move the DC pixel back to the right place E_out = np.roll(E_out, (1, 1), axis=(0, 1)) else: E_out = E_in return E_out
def test_twoD_array_bad_vexc(self): """Fail on input vexc not an Exception.""" with self.assertRaises(check.CheckException): check.twoD_array(np.ones((5, 5)), 'rps', 'TestCheckException') pass pass
def test_twoD_array_bad_vname(self): """Fail on invalid input name for user output.""" with self.assertRaises(check.CheckException): check.twoD_array(np.ones((5, 5)), (1, ), TestCheckException) pass pass
def derotate_resize_surface(surfaceToFit, dx, Nact, dm_xc, dm_yc, spacing, **kwargs): """ Derotate and resize a DM surface to size and alignment of actuator grid. Does the order of operations in the reverse order of PROPER's prop_dm. Parameters ---------- surfaceToFit : numpy ndarray 2-D DM surface map to be fitted dx : float width of a pixel in meters Nact : int number of actuators across the DM array dm_xc, dm_yc : list or numpy ndarray The location of the optical axis (center of the wavefront) on the DM in actuator units (0 ro num_actuator-1). The center of the first actuator is (0.0, 0.0) spacing : float Spacing in meters between actuator centers (aka the pitch). Returns ------- gridDerotAtActRes : numpy ndarray Returns DM surface at same alignment and resolution as the DM actuator array. Other Parameters ---------------- XTILT, YTILT, ZTILT : float Specify the rotation of the DM surface with respect to the wavefront plane in degrees about the X, Y, Z axes, respectively, with the origin at the center of the wavefront. The DM surface is interpolated and orthographically projected onto the wavefront grid. The coordinate system assumes that the wavefront and initial DM surface are in the X,Y plane with a lower left origin with Z towards the observer. The rotations are left handed. The default rotation order is X, Y, then Z unless the /ZYX switch is set. XYZ or ZYX : bool Specifies the rotation order if two or more of XTILT, YTILT, or ZTILT are specified. The default is /XYZ for X, Y, then Z rotations. inf_fn : string specify a new influence function as a FITS file with the same header keywords as PROPER's default influence function. Needs these values in info.PrimaryData.Keywords: 'P2PDX_M' % pixel width x (m) 'P2PDY_M' % pixel width y (m) 'C2CDX_M' % actuator pitch x (m) 'C2CDY_M' % actuator pitch y (m) inf_sign : {+,-} specifies the sign (+/-) of the influence function. Given as an option because the default influence function file is positive, but positive DM actuator commands make a negative deformation for Xinetics and BMC DMs. Raises ------ ValueError: User cannot specify both ZYX and XYZ rotations. """ check.twoD_array(surfaceToFit, 'surfaceToFit', TypeError) check.real_positive_scalar(dx, 'dx', TypeError) check.real_scalar(dm_xc, 'dm_xc', TypeError) check.real_scalar(dm_yc, 'dm_yc', TypeError) check.real_positive_scalar(spacing, 'spacing', TypeError) if "ZYX" in kwargs and "XYZ" in kwargs: raise ValueError('Error: Cannot specify both XYZ and ZYX rotation' + ' orders. Stopping') elif "ZYX" not in kwargs and 'XYZ' not in kwargs: XYZ = 1 # default is rotation around X, then Y, then Z # ZYX = 0 elif "ZYX" in kwargs: # ZYX = 1 XYZ = 0 elif "XYZ" in kwargs: XYZ = 1 # ZYX = 0 if "XTILT" in kwargs: xtilt = kwargs["XTILT"] else: xtilt = 0. if "YTILT" in kwargs: ytilt = kwargs["YTILT"] else: ytilt = 0. if "ZTILT" in kwargs: ztilt = kwargs["ZTILT"] else: ztilt = 0. dm_z = np.eye(Nact) if "inf_fn" in kwargs: inf_fn = kwargs["inf_fn"] else: inf_fn = "influence_dm5v2.fits" if "inf_sign" in kwargs: if(kwargs["inf_sign"] == '+'): sign_factor = 1. elif(kwargs["inf_sign"] == '-'): sign_factor = -1. else: sign_factor = 1. n = surfaceToFit.shape[0] dx_surf = dx # sampling of current surface in meters # Default influence function sampling is 0.1 mm, peak at (x,y)=(45,45) # Default influence function has shape = 1x91x91. Saving it as a 2D array # before continuing with processing dir_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data") inf = proper.prop_fits_read(os.path.join(dir_path, inf_fn)) inf = sign_factor*np.squeeze(inf) s = inf.shape nx_inf = s[1] ny_inf = s[0] xc_inf = nx_inf // 2 yc_inf = ny_inf // 2 dx_inf = 0.1e-3 # influence function spacing in meters dx_dm_inf = 1.e-3 # spacing between DM actuators in meters assumed by influence function inf_mag = 10 dx_dm = spacing dx_inf = dx_inf * dx_dm / dx_dm_inf # Influence function sampling scaled # to specified DM actuator spacing dm_z_commanded = dm_z s = dm_z.shape nx_dm = s[1] ny_dm = s[0] # Create subsampled DM grid margin = 9 * inf_mag nx_grid = nx_dm * inf_mag + 2 * margin ny_grid = ny_dm * inf_mag + 2 * margin xoff_grid = margin + inf_mag/2 # pixel location of 1st actuator center in subsampled grid yoff_grid = xoff_grid dm_grid = np.zeros([ny_grid, nx_grid], dtype = np.float64) x = np.arange(nx_dm, dtype=int) * int(inf_mag) + int(xoff_grid) y = np.arange(ny_dm, dtype=int) * int(inf_mag) + int(yoff_grid) dm_grid[np.tile(np.vstack(y), (nx_dm,)), np.tile(x, (ny_dm, 1))] = dm_z_commanded dm_grid = ss.fftconvolve(dm_grid, inf, mode='same') # 3D rotate DM grid and project orthogonally onto wavefront xdim = int(np.round(np.sqrt(2) * nx_grid * dx_inf / dx_surf)) # grid dimensions (pix) projected onto wavefront ydim = int(np.round(np.sqrt(2) * ny_grid * dx_inf / dx_surf)) if xdim > n: xdim = n if ydim > n: ydim = n x = np.ones((ydim,1), dtype=int) * ((np.arange(xdim) - xdim // 2) * dx_surf) y = (np.ones((xdim,1), dtype=int) * ((np.arange(ydim) - ydim // 2) * dx_surf)).T a = xtilt * np.pi / 180 b = ytilt * np.pi / 180 g = ztilt * np.pi / 180 if XYZ: m = np.array([ [cos(b)*cos(g), -cos(b)*sin(g), sin(b), 0], [cos(a)*sin(g) + sin(a)*sin(b)*cos(g), cos(a)*cos(g)-sin(a)*sin(b)*sin(g), -sin(a)*cos(b), 0], [sin(a)*sin(g)-cos(a)*sin(b)*cos(g), sin(a)*cos(g)+cos(a)*sin(b)*sin(g), cos(a)*cos(b), 0], [0, 0, 0, 1] ]) else: m = np.array([ [cos(b)*cos(g), cos(g)*sin(a)*sin(b)-cos(a)*sin(g), cos(a)*cos(g)*sin(b)+sin(a)*sin(g), 0], [cos(b)*sin(g), cos(a)*cos(g)+sin(a)*sin(b)*sin(g), -cos(g)*sin(a)+cos(a)*sin(b)*sin(g), 0], [-sin(b), cos(b)*sin(a), cos(a)*cos(b), 0], [0, 0, 0, 1] ]) # Compute xdm0 and ydm0 for use in de-rotating the DM surface edge = np.array([[-1.0,-1.0,0.0,0.0], [1.0,-1.0,0.0,0.0], [1.0,1.0,0.0,0.0], [-1.0,1.0,0.0,0.0]]) new_xyz = edge #np.dot(edge, m) # determine backward projection for screen-raster-to-DM-surce computation dx_dxs = (new_xyz[0, 0] - new_xyz[1, 0]) / (edge[0, 0] - edge[1, 0]) dx_dys = (new_xyz[1, 0] - new_xyz[2, 0]) / (edge[1, 1] - edge[2, 1]) dy_dxs = (new_xyz[0, 1] - new_xyz[1, 1]) / (edge[0, 0] - edge[1, 0]) dy_dys = (new_xyz[1, 1] - new_xyz[2, 1]) / (edge[1, 1] - edge[2, 1]) xs = (x/dx_dxs - y*dx_dys/(dx_dxs*dy_dys) ) / ( 1 - dy_dxs*dx_dys/(dx_dxs*dy_dys)) ys = (y/dy_dys - x*dy_dxs/(dx_dxs*dy_dys) ) / ( 1 - dx_dys*dy_dxs/(dx_dxs*dy_dys)) xdm0 = (xs + dm_xc * dx_dm) / dx_inf + xoff_grid ydm0 = (ys + dm_yc * dx_dm) / dx_inf + yoff_grid ###### # Forward project a square # edge = np.array([[-1.0,-1.0,0.0,0.0], [1.0,-1.0,0.0,0.0], # [1.0,1.0,0.0,0.0], [-1.0,1.0,0.0,0.0]]) new_xyz = np.dot(edge, m) # determine backward projection for screen-raster-to-DM-surce computation dx_dxs = (new_xyz[0, 0] - new_xyz[1, 0]) / (edge[0, 0] - edge[1, 0]) dx_dys = (new_xyz[1, 0] - new_xyz[2, 0]) / (edge[1, 1] - edge[2, 1]) dy_dxs = (new_xyz[0, 1] - new_xyz[1, 1]) / (edge[0, 0] - edge[1, 0]) dy_dys = (new_xyz[1, 1] - new_xyz[2, 1]) / (edge[1, 1] - edge[2, 1]) xs = (x/dx_dxs - y*dx_dys/(dx_dxs*dy_dys)) / \ (1 - dy_dxs*dx_dys/(dx_dxs*dy_dys)) ys = (y/dy_dys - x*dy_dxs/(dx_dxs*dy_dys)) / \ (1 - dx_dys*dy_dxs/(dx_dxs*dy_dys)) xdm = (xs + dm_xc * dx_dm) / dx_inf + xoff_grid ydm = (ys + dm_yc * dx_dm) / dx_inf + yoff_grid # if proper.use_cubic_conv: # grid = proper.prop_cubic_conv(dm_grid.T, xdm, ydm, GRID = False) # grid = grid.reshape([xdm.shape[1], xdm.shape[0]]) # else: # grid = map_coordinates(dm_grid.T, [xdm, ydm], order=3, # mode="nearest", prefilter = True) # dm_grid = falco.util.pad_crop(surfaceToFit, xdm.shape[0]) # Derotate the DM surface dm_grid = falco.util.pad_crop(surfaceToFit, xdm.shape[0]) gridDerot = griddata((xdm.flatten(), ydm.flatten()), dm_grid.flatten(), (xdm0, ydm0), method='cubic', fill_value=0.) # gridDerot(isnan(gridDerot)) = 0 # Resize and decimate the DM surface to get it at the same size as the DM # actuator command array. # The result will be fed to fit_surf_to_act() for deconvolution with the # influence function. xOffsetInAct = ((Nact/2 - 1/2) - dm_xc) yOffsetInAct = ((Nact/2 - 1/2) - dm_yc) multipleOfCommandGrid = int(falco.util.ceil_odd(spacing/dx)) N1 = Nact*multipleOfCommandGrid N2 = dm_grid.shape[0] xs1 = np.linspace(-(N1-1)/2, (N1-1)/2, N1)/N1 # interpixel centered if(N2 % 2 == 0): xs2 = np.linspace(-N2/2, (N2/2)-1, N2)/N2*(N2*dx/(Nact*spacing)) else: xs2 = np.linspace(-(N2-1)/2, (N2-1)/2, N2)/N2*(N2*dx/(Nact*spacing)) interp_spline = RectBivariateSpline(xs2, xs2, gridDerot) gridDerotResize = interp_spline(xs1-xOffsetInAct/Nact, xs1-yOffsetInAct/Nact) xyOffset = int(np.floor(multipleOfCommandGrid/2.)) gridDerotAtActRes = gridDerotResize[xyOffset::multipleOfCommandGrid, xyOffset::multipleOfCommandGrid] # # plt.figure(11); plt.imshow(dm_grid); plt.colorbar(); plt.pause(0.1) # plt.figure(12); plt.imshow(gridDerot); plt.colorbar(); plt.pause(0.1) # plt.figure(13); plt.imshow(gridDerotResize); plt.colorbar(); plt.pause(0.1) # plt.figure(14); plt.imshow(gridDerotAtActRes); plt.colorbar(); plt.pause(0.1) # plt.figure(15); plt.imshow(gridDerot-gridDerot[::-1,::-1]); plt.colorbar(); plt.pause(0.1) # plt.figure(16); plt.imshow(gridDerotResize-gridDerotResize[::-1,::-1]); plt.colorbar(); plt.pause(0.1) return gridDerotAtActRes
def fit_surf_to_act(dm, surfaceToFit): """ Compute the deformable mirror (DM) commands to best fit a given surface. Parameters ---------- surfaceToFit : numpy ndarray 2-D array of the surface heights for the DM to fit dm : ModelParameters Structure containing parameter values for the DM Returns ------- Vout : numpy ndarray 2-D array of DM voltage commands """ check.twoD_array(surfaceToFit, 'surfaceToFit', TypeError) [mSurface, nSurface] = surfaceToFit.shape # Starting influence function (must be square) inf1 = dm.inf0 N1 = inf1.shape[0] actres1 = dm.dm_spacing/dm.dx_inf0 x = np.linspace(-(N1-1.)/2.,(N1-1.)/2., N1)/actres1 [X, Y] = np.meshgrid(x, x) # Influence function resampled to actuator map resolution actres2 = 1. # pixels per actuator width N2 = falco.util.ceil_even(N1*actres2/actres1)+1 # Make odd to have peak of 1 xq = np.linspace(-(N2-1)/2, (N2-1)/2, N2)/actres2 # pixel-centered # [Xq,Yq] = np.meshgrid(xq) # inf2 = interp2(X,Y,inf1,Xq,Yq,'cubic',0); # MATLAB way interp_spline = RectBivariateSpline(x, x, inf1) # RectBivariateSpline is faster in 2-D than interp2d infFuncAtActRes = interp_spline(xq, xq) # Set the order of operations flagXYZ = True if(hasattr(dm, 'flagZYX')): if(dm.flagZYX): flagXYZ = False # Perform the fit if(nSurface == dm.Nact): gridDerotAtActRes = surfaceToFit elif(nSurface > dm.Nact): # Adjust the centering of the output DM surface. The shift needs to be # in units of actuators, not meters wArray = nSurface*dm.dx cshift = -wArray/2./nSurface/dm.dm_spacing if(dm.centering == 'interpixel') else 0. gridDerotAtActRes = derotate_resize_surface(surfaceToFit, dm.dx, dm.Nact, dm.xc-cshift, dm.yc-cshift, dm.dm_spacing, XTILT=dm.xtilt, YTILT=dm.ytilt, ZTILT=dm.zrot, XYZ=flagXYZ, inf_sign=dm.inf_sign, inf_fn=dm.inf_fn) elif(nSurface < dm.Nact): raise ValueError('surfaceToFit cannot be smaller than [Nact x Nact].') [Vout, surfaceOut] = proper.prop_fit_dm(gridDerotAtActRes, infFuncAtActRes) return Vout
def apply_neighbor_rule(Vin, Vlim, Nact): """ Apply the neighbor rule to DM commands. Find neighboring actuators that exceed a specified difference in voltage and to scale down those voltages until the rule is met. Parameters ---------- Vin : numpy ndarray 2-D array of DM voltage commands Vlim : float maximum difference in command values between neighboring actuators Nact : int Number of actuators across the DM Returns ------- Vout : numpy ndarray 2-D array of DM voltage commands indPair : numpy ndarray [nPairs x 2] array of tied actuator linear indices """ check.twoD_array(Vin, 'Vin', TypeError) check.real_scalar(Vlim, 'Vlim', TypeError) check.positive_scalar_integer(Nact, 'Nact', TypeError) Vout = Vin # Initialize output voltage map indPair = np.zeros((0,2)) # Initialize the paired indices list. [nPairs x 2] kx1 = np.array([[0, 1], [1, 1], [1, 0]]) # R1-C1 kx2 = np.array([[0,1], [1,1], [1,0], [1,-1]]) # R1, C2 - C47 kx3 = np.array([[1,0], [1,-1]]) # R1, C48 kx4 = np.array([[-1,1], [0,1], [1,1], [1,0]]) # R2-R47, C1 kx5 = np.array([[-1,1], [0,1], [1,1], [1,0], [1,-1]]) # R2-47, C2-47 kx6 = np.array([[1,0], [1,-1]]) # R2-47, C8 kx7 = np.array([[-1,1], [0,1]]) # R48, C1 - C47 kx8 = np.array([[-1,-1]]) # R48, C48 for jj in range(Nact): # Row for ii in range(Nact): # Col if jj == 0: if ii == 0: kx = kx1 elif ii < Nact-1: kx = kx2 else: kx = kx3 elif jj < Nact-1: if ii == 0: kx = kx4 elif ii < Nact-1: kx = kx5 else: kx = kx6 else: if ii < Nact-1: kx = kx7 else: kx = kx8 kr = jj + kx[:,0] kc = ii + kx[:,1] nNbr = kr.size # length(kr); # Number of neighbors if nNbr >= 1: for iNbr in range(nNbr): a1 = Vout[jj, ii] - Vout[kr[iNbr],kc[iNbr]] # Compute the delta voltage if (np.abs(a1) > Vlim): # If neighbor rule is violated indLinCtr = (ii-1)*Nact + jj # linear index of center actuator indLinNbr = (kc[iNbr]-1)*Nact + kr[iNbr] # linear index of neigboring actuator indPair = np.array([indPair, np.array([indLinCtr, indLinNbr]).reshape(1, 2)]) indPair = np.vstack([indPair, np.array([indLinCtr, indLinNbr]).reshape(1, 2)]) fx = (np.abs(a1) - Vlim) / 2. Vout[jj, ii] = Vout[jj, ii] - np.sign(a1)*fx Vout[kr[iNbr], kc[iNbr]] = Vout[kr[iNbr], kc[iNbr]] +\ np.sign(a1)*fx return Vout, indPair
def mft_p2v2p(pupilPre, charge, beamRadius, inVal, outVal): """ Propagate from the pupil plane before a vortex FPM to pupil plane after it. Compute a radial Tukey window for propagating through a vortex coroangraph. Parameters ---------- pupilPre : array_like 2-D E-field at pupil plane before the vortex focal plane mask charge : int, float Charge of the vortex mask beamRadius : float Beam radius at pupil plane. Units of pixels. inVal : float Ask Gary outVal : float Ask Gary Returns ------- pupilPost : array_like 2-D E-field at pupil plane after the vortex focal plane mask """ check.twoD_array(pupilPre, 'pupilPre', TypeError) check.scalar_integer(charge, 'charge', TypeError) check.real_positive_scalar(beamRadius, 'beamRadius', TypeError) check.real_positive_scalar(inVal, 'inVal', TypeError) check.real_positive_scalar(outVal, 'outVal', TypeError) # showPlots2debug = False D = 2.0*beamRadius lambdaOverD = 4. # samples per lambda/D NA = pupilPre.shape[1] NB = util.ceil_even(lambdaOverD*D) # [X,Y] = np.meshgrid(np.arange(-NB/2., NB/2., dtype=float),np.arange(-NB/2., NB/2., dtype=float)) # [RHO,THETA] = util.cart2pol(Y,X) RHO = util.radial_grid(np.arange(-NB/2., NB/2., dtype=float)) windowKnee = 1.-inVal/outVal windowMask1 = gen_tukey_for_vortex(2*outVal*lambdaOverD, RHO, windowKnee) windowMask2 = gen_tukey_for_vortex(NB, RHO, windowKnee) # DFT vectors x = np.arange(-NA/2,NA/2,dtype=float)/D #(-NA/2:NA/2-1)/D u1 = np.arange(-NB/2,NB/2,dtype=float)/lambdaOverD #(-NB/2:NB/2-1)/lambdaOverD u2 = np.arange(-NB/2,NB/2,dtype=float)*2*outVal/NB # (-NB/2:NB/2-1)*2*outVal/N FPM = falco_gen_vortex_mask(charge, NB) #if showPlots2debug; figure;imagesc(abs(pupilPre));axis image;colorbar; title('pupil'); end; ## Low-sampled DFT of entire region FP1 = 1/(1*D*lambdaOverD)*np.exp(-1j*2*np.pi*np.outer(u1,x)) @ pupilPre @ np.exp(-1j*2*np.pi*np.outer(x,u1)) #if showPlots2debug; figure;imagesc(log10(abs(FP1).^2));axis image;colorbar; title('Large scale DFT'); end; LP1 = 1/(1*D*lambdaOverD)*np.exp(-1j*2*np.pi*np.outer(x,u1)) @ (FP1*FPM*(1-windowMask1)) @ np.exp(-1j*2*np.pi*np.outer(u1,x)) #if showPlots2debug; figure;imagesc(abs(FP1.*(1-windowMask1)));axis image;colorbar; title('Large scale DFT (windowed)'); end; ## Fine sampled DFT of innter region FP2 = 2*outVal/(1*D*NB)*np.exp(-1j*2*np.pi*np.outer(u2,x)) @ pupilPre @ np.exp(-1j*2*np.pi*np.outer(x,u2)) #if showPlots2debug; figure;imagesc(log10(abs(FP2).^2));axis image;colorbar; title('Fine sampled DFT'); end; FPM = falco_gen_vortex_mask(charge, NB) LP2 = 2.0*outVal/(1*D*NB)*np.exp(-1j*2*np.pi*np.outer(x,u2)) @ (FP2*FPM*windowMask2) @ np.exp(-1j*2*np.pi*np.outer(u2,x)) #if showPlots2debug; figure;imagesc(abs(FP2.*windowMask2));axis image;colorbar; title('Fine sampled DFT (windowed)'); end; pupilPost = LP1 + LP2; #if showPlots2debug; figure;imagesc(abs(pupilPost));axis image;colorbar; title('Lyot plane'); end; return pupilPost
def mft_p2f(E_pup, fl, wavelength, dx, dxi, Nxi, deta, Neta, centering='pixel'): """ Propagate a pupil to a focus using a matrix-multiply DFT. Parameters ---------- E_pup : array_like Electric field array in pupil plane fl : float Focal length of Fourier transforming lens wavelength : float Propagation wavelength dx : float Step size along either axis of focal plane. The vertical and horizontal step sizes are assumed to be equal. dxi : float Step size along horizontal axis of focal plane Nxi : int Number of samples along horizontal axis of focal plane. deta : float Step size along vertical axis of focal plane Neta : int Number of samples along vertical axis of focal plane. centering : string Whether the input and output arrays are pixel-centered or inter-pixel-centered. Possible values: 'pixel', 'interpixel' Returns ------- array_like Field in pupil plane, after propagating through Fourier transforming lens """ if centering not in _VALID_CENTERING: raise ValueError(_CENTERING_ERR) check.twoD_array(E_pup, 'E_pup', TypeError) check.real_scalar(fl, 'fl', TypeError) check.real_positive_scalar(wavelength, 'wavelength', TypeError) check.real_positive_scalar(dx, 'dx', TypeError) check.real_positive_scalar(dxi, 'dxi', TypeError) check.positive_scalar_integer(Nxi, 'Nxi', TypeError) check.real_positive_scalar(deta, 'deta', TypeError) check.positive_scalar_integer(Neta, 'Neta', TypeError) dy = dx M, N = E_pup.shape if M != N: raise ValueError('Input array is not square') # Pupil-plane coordinates x = util.create_axis(N, dx, centering=centering)[:, None] # Broadcast to column vector y = x.T # Row vector # Focal-plane coordinates xi = util.create_axis(Nxi, dxi, centering=centering)[None, :] # Broadcast to row vector eta = util.create_axis(Neta, deta, centering=centering)[:, None] # Broadcast to column vector # Fourier transform matrices pre = np.exp(-2 * np.pi * 1j * (eta * y) / (wavelength * fl)) post = np.exp(-2 * np.pi * 1j * (x * xi) / (wavelength * fl)) # Constant scaling factor in front of Fourier transform scaling = np.sqrt(dx * dy * dxi * deta) / (1 * wavelength * fl) return scaling * np.linalg.multi_dot([pre, E_pup, post])