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_positive_scalar_integer_good(self): """ Verify checker works correctly for valid input. Type: positive scalar integer """ try: check.positive_scalar_integer(1, 'psi', TestCheckException) except check.CheckException: self.fail('positive_scalar_integer failed on valid input') pass
def test_positive_scalar_integer_bad_var(self): """ Fail on invalid variable type. Type: positive scalar integer """ for v0 in [1.0, -1, 0, 1j, (1., ), [5, 5], 'psi']: with self.assertRaises(TestCheckException): check.positive_scalar_integer(v0, 'psi', TestCheckException) pass pass pass
def test_positive_scalar_integer_bad_vexc(self): """Fail on input vexc not an Exception.""" with self.assertRaises(check.CheckException): check.positive_scalar_integer(1, 'psi', 'TestCheckException') pass pass
def test_positive_scalar_integer_bad_vname(self): """Fail on invalid input name for user output.""" with self.assertRaises(check.CheckException): check.positive_scalar_integer(1, (1, ), TestCheckException) pass pass
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 gen_surf_from_act(dm, dx, N): """ Function to compute the surface shape of a deformable mirror. Uses PROPER. Parameters ---------- dm : ModelParameters Structure containing parameter values for the DM dx : float Pixel width [meters] at the DM plane N : int Number of points across the array to return at the DM plane Returns ------- DMsurf : array_like 2-D surface map of the DM """ check.real_positive_scalar(dx, 'dx', TypeError) check.positive_scalar_integer(N, 'N', TypeError) # if type(dm) is not falco.config.Object: # raise TypeError('Input "dm" must be of type falco.config.Object') # Set the order of operations flagXYZ = True if(hasattr(dm, 'flagZYX')): if(dm.flagZYX): flagXYZ = False # Adjust the centering of the output DM surface. The shift needs to be in # units of actuators, not meters, for prop_dm.m. Darray = dm.NdmPad*dm.dx Narray = dm.NdmPad if dm.centering == 'interpixel': cshift = -Darray/2./Narray/dm.dm_spacing elif dm.centering == 'pixel': cshift = 0 pupil_ratio = 1 # beam diameter fraction wl_dummy = 1e-6 # dummy value needed to initialize PROPER (meters) bm = proper.prop_begin(N*dx, wl_dummy, N, pupil_ratio) # Apply various constraints to DM commands dm = enforce_constraints(dm) # Quantization of DM actuation steps based on least significant bit of the # DAC (digital-analog converter). In height, so called HminStep # If HminStep (minimum step in H) is defined, then quantize the DM voltages if(hasattr(dm, 'HminStep')): if not(hasattr(dm, 'HminStepMethod')): dm.HminStepMethod = 'round' # Discretize/Quantize the DM voltages (creates dm.Vquantized) dm = discretize_surf(dm, dm.HminStepMethod) heightMap = dm.VtoH*dm.Vquantized else: # Quantization not desired; send raw, continuous voltages heightMap = dm.VtoH*dm.V if hasattr(dm, 'orientation'): if dm.orientation.lower() == 'rot0': pass # no change elif dm.orientation.lower() == 'rot90': heightMap = np.rot90(heightMap, 1) elif dm.orientation.lower() == 'rot180': heightMap = np.rot90(heightMap, 2) elif dm.orientation.lower() == 'rot270': heightMap = np.rot90(heightMap, 3) elif dm.orientation.lower() == 'flipxrot0': heightMap = np.flipx(heightMap) elif dm.orientation.lower() == 'flipxrot90': heightMap = np.rot90(np.flipx(heightMap), 1) elif dm.orientation.lower() == 'flipxrot180': heightMap = np.rot90(np.flipx(heightMap), 2) elif dm.orientation.lower() == 'flipxrot270': heightMap = np.rot90(np.flipx(heightMap), 3) else: raise ValueError('invalid value of dm.orientation') # Generate the DM surface DMsurf = falco.dm.propcustom_dm(bm, heightMap, 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) return DMsurf
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])