Ejemplo n.º 1
0
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])
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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))
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
 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
Ejemplo n.º 8
0
 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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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
Ejemplo n.º 13
0
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])