Esempio n. 1
0
def quicklook_IQ(wfo, logAmp=False, show=True):
    I = np.real(wfo.wfarr)
    Q = np.imag(wfo.wfarr)

    print(np.sum(I), np.sum(Q))

    I = proper.prop_shift_center(I)
    Q = proper.prop_shift_center(Q)

    fig = plt.figure(figsize=(14, 10))
    ax1 = plt.subplot2grid((3, 2), (0, 0), rowspan=2)
    ax2 = plt.subplot2grid((3, 2), (0, 1), rowspan=2)
    ax3 = plt.subplot2grid((3, 2), (2, 0))
    ax4 = plt.subplot2grid((3, 2), (2, 1))
    if logAmp:
        ax1.imshow(I, origin='lower', cmap="YlGnBu_r", norm=LogNorm())
    else:
        ax1.imshow(I, origin='lower', cmap="YlGnBu_r")
    ax2.imshow(Q, origin='lower', cmap="YlGnBu_r")  # , vmin=-0.5, vmax=0.5)

    ax3.plot(I[int(tp.grid_size / 2)])
    ax3.plot(np.sum(np.eye(tp.grid_size) * I, axis=1))

    # plt.plot(np.sum(after_dm,axis=1)/after_dm[128,128])

    ax4.plot(Q[int(tp.grid_size / 2)])
    # ax4.plot(np.sum(np.eye(tp.grid_size)*phase_afterdm,axis=1))
    plt.xlim([0, proper.prop_get_gridsize(wfo)])
    fig.set_tight_layout(True)
    if show == True:
        plt.show()
Esempio n. 2
0
def rotate_atmos(wf, atmos_map):
    time = float(atmos_map[-19:-11])
    rotate_angle = tp.rot_rate * time
    # print  rotate_angle, 'rotate'
    # quicklook_wf(wf)
    wf.wfarr = proper.prop_shift_center(wf.wfarr)
    wf.wfarr = proper.prop_rotate(wf.wfarr, rotate_angle)
    wf.wfarr = proper.prop_shift_center(wf.wfarr)
def circular_apodization(wf, radius, t_in, t_out, xc = 0.0, yc = 0.0, **kwargs):

    if ("NORM" in kwargs and kwargs["NORM"]):
        norm = True
    else:
        norm = False

    if (t_in > t_out):
        apodizer = proper.prop_shift_center(proper.prop_ellipse(wf, radius, radius, xc, yc, NORM = norm))*(t_in-t_out)+t_out
    else:
         apodizer = proper.prop_shift_center(proper.prop_ellipse(wf, radius, radius, xc, yc, NORM = norm))*(t_out-t_in)+t_in

    return apodizer
Esempio n. 4
0
def build_phase_map(wf, phase_error):
    """Add a phase error map or value to the current wavefront array. 
    
    The phase error array is assumed to be at the same sampling as the current
    wavefront. Note that this is wavefront, not surface error.
    
    Parameters
    ----------
    wf : object
        WaveFront class object
        
    phase_error : numpy ndarray
        A scalar or 2D image containing the phase error in meters
        
    Returns
    -------
        None
    """
    i = complex(0., 1.)

    if type(phase_error) != np.ndarray and type(phase_error) != list:
        phase_error = float(phase_error)
        phase_map = np.exp(2 * np.pi * i / wf.lamda * phase_error)
    else:
        phase_error = np.asarray(phase_error)
        phase_map = np.exp(2 * np.pi * i / wf.lamda *
                           proper.prop_shift_center(phase_error))

    return phase_map
Esempio n. 5
0
    def save_plane(self, location=None):
        """
        Saves the complex field at a specified location in the optical system. If the function is called by
        wfo.loop_collection, the plane is saved AFTER the function is applied

        Note that the complex planes saved are not summed by object, interpolated over wavelength, nor masked down
        to the sp.maskd_size.

        :param location: name of plane where field is being saved
        :return: self.save_E_fields
        """
        if sp.verbose:
            dprint(f"saving plane at {location}")

        if location is not None and location in sp.save_list:
            E_field = np.zeros(
                (1, np.shape(self.wf_collection)[0],
                 np.shape(self.wf_collection)[1], sp.grid_size, sp.grid_size),
                dtype=np.complex64)
            samp_lambda = np.zeros(ap.n_wvl_init)

            for iw, sources in enumerate(self.wf_collection):
                samp_lambda[iw] = proper.prop_get_sampling(
                    self.wf_collection[iw, 0])
                for io, wavefront in enumerate(sources):
                    wf = proper.prop_shift_center(wavefront.wfarr)
                    E_field[0, iw, io] = copy.copy(wf)

            self.Efield_planes = np.vstack((self.Efield_planes, E_field))
            self.saved_planes.append(location)
            self.plane_sampling.append(samp_lambda)
Esempio n. 6
0
def piston_refbeam(wf, iter, w):
    beam_ratio = 0.7 * tp.band[0] / w * 1e-9
    wf_ref = proper.prop_begin(tp.diam, w, tp.grid_size, beam_ratio)
    proper.prop_define_entrance(wf_ref)
    phase_mod = iter % 4 * w / 4  #np.pi/2
    obj_map = np.ones((tp.grid_size, tp.grid_size)) * phase_mod
    proper.prop_add_phase(wf_ref, obj_map)
    wf_ref.wfarr = wf.wfarr + wf_ref.wfarr
    Imap = proper.prop_shift_center(np.abs(wf_ref.wfarr)**2)
    return Imap
Esempio n. 7
0
def apodize_pupil(wf):
    phase_map = proper.prop_get_phase(wf)
    amp_map = proper.prop_get_amplitude(wf)
    h, w = wf.wfarr.shape[:2]
    wavelength = wf.lamda
    scale = ap.wvl_range[0] / wavelength
    inds = circular_mask(h, w, radius=scale * 0.95 * h * sp.beam_ratio / 2)  #TO DO remove hardcoded sizing, especially if use is default
    mask = np.zeros_like(phase_map)
    mask[inds] = 1
    smooth_mask = gaussian_filter(mask, 1.5, mode='nearest')  #TO DO remove hardcoded sizing, especially if use is default
    smoothed = phase_map * smooth_mask
    wf.wfarr = proper.prop_shift_center(amp_map * np.cos(smoothed) + 1j * amp_map * np.sin(smoothed))
Esempio n. 8
0
def save_pix_IQ(wf, plot=False):
    with open(iop.IQpixel, 'a') as the_file:
        complex_map = proper.prop_shift_center(wf.wfarr)
        pix0 = complex_map[64, 64]
        pix1 = complex_map[100, 100]

        the_file.write('%f, %f, %f, %f\n' % (np.real(pix0), np.imag(pix0), np.real(pix1), np.imag(pix1)))

    if plot:
        quicklook_im(np.real(complex_map))
        quicklook_wf(wf, show=True)
        quicklook_IQ(wf, show=True)
Esempio n. 9
0
def circularise(prim_map):
    x = np.linspace(-1, 1, 128) * np.ones((128, 128))
    y = np.transpose(np.linspace(-1, 1, 128) * np.ones((128, 128)))
    circ_map = np.zeros((2, 128, 128))
    circ_map[0] = x * np.sqrt(1 - (y**2 / 2.))
    circ_map[1] = y * np.sqrt(1 - (x**2 / 2.))
    circ_map *= 64
    new_prim = np.zeros((128, 128))
    for x in range(128):
        for y in range(128):
            ix = circ_map[0][x, y]
            iy = circ_map[1][x, y]
            new_prim[ix, iy] = prim_map[x, y]
    new_prim = proper.prop_shift_center(new_prim)
    new_prim = np.transpose(new_prim)
    # quicklook_im(new_prim, logAmp=False, colormap="jet")
    return new_prim
def build_prop_rectangular_obscuration(wf, width, height, xc = 0.0, yc = 0.0, **kwargs):
    """Multiply the current wavefront by a rectangular obscuration.
    
    Parameters
    ----------
    wf : obj
        WaveFront class object
        
    width, height : float
        X and Y widths of the obscuration in meters (unless the norm switch is  
        specified, in which case these are normalized to the beam diameter) 
    
    xc, yc : float
        Center of obscuration relative to the center of the beam in meters 
        (unless norm is specified, in which case they are normalized relative 
        to the beam radius; the default is (0,0)
        
        
    Returns
    -------
        None
        Multiplies the wavefront array in "wf" object by a dark rectangular mask.

    
    Other Parameters
    ----------------
    NORM : bool
        Indicates that obscuration halfwidths and center are specified relative 
        to the beam radius.
        
    ROTATION : float
        Degrees counter-clockwise to rotate obscuration about its center.
    """
    if ("NORM" in kwargs and kwargs["NORM"]):
        norm = True
    else:
        norm = False
        
    if "ROTATION" in kwargs:
        rotation = kwargs["ROTATION"]
    else:
        rotation = 0.0
        
    return proper.prop_shift_center(proper.prop_rectangle(wf, width, height, xc, yc, NORM = norm, ROTATION = rotation, DARK = True))
Esempio n. 11
0
def build_prop_circular_aperture(wf, radius, xc=0.0, yc=0.0, **kwargs):
    """Multiply the wavefront by a circular clear aperture.
    
    Parameters
    ----------
    wf : obj
        WaveFront class object
    
    radius : float
       Radius of aperture in meters, unless norm is specified
      
    xc : float
        X-center of aperture relative to center of wavefront. Default is 0.0
        
    yc : float
        Y-center of aperture relative to center of wavefront. Default is 0.0
        
        
    Returns
    -------
    numpy ndarray:
        Multiplies current wavefront in wf object by a circular aperture.
    
    
    Other Parameters
    -----------------
    NORM : bool
        If set to True, the specified radius and xc, yc aperure centers are 
        assumed to be normalized to the current beam radius (e.g. radius is 1.0
        means the aperture is the same size as  the current beam). xc, yc = 0,0 
        is the center of the wavefront. Default is False.
    """

    if ("NORM" in kwargs and kwargs["NORM"]):
        norm = True
    else:
        norm = False

    return proper.prop_shift_center(
        proper.prop_ellipse(wf, radius, radius, xc, yc, NORM=norm))
Esempio n. 12
0
def hardmask_pupil(wf):
    """
    hard-edged circular mask of the pupil plane.

    Masks out the WFS map outside of the beam since the DM modeled by proper can only be a square nxn grid of actuators,
    and thus the influence function surrounding each DM actuator could  be affecting on-beam pixels, even if the
    actuator is acting on off-beam signal. In other words, even if a cornerDM actuator doesn't actuate on the beam,
    if there was non-zero signal in the WFS map, it will try to act on it, and it could 'influence' nearby DM actuators
    that are acting on the beam.

    This hard-edged mask is different from prop_circular_aperture in that it does not anti-alias the edges of the mask
    based on the 'fill factor' of the edge pixels. Instead, it has a boolean mask to zero everything > a fixed radius,
    in this case determined by the grid size and beam ratio of each wavefront passed into it.

    :param wf: a single wavefront
    :return: nothing is returned but the wf passed into it has been masked

    """
    phase_map = proper.prop_get_phase(wf)
    amp_map = proper.prop_get_amplitude(wf)

    # Sizing the Mask
    h, w = wf.wfarr.shape[:2]
    center = (int(w / 2), int(h / 2))
    radius = np.floor(
        sp.grid_size * wf.beam_ratio /
        2)  # Should scale with wavelength if sp.focused_system=False,
    # np.ceil used to oversize map so don't clip the beam
    # Making the Circular Boolean Mask
    Y, X = np.mgrid[:h, :w]
    dist_from_center = np.sqrt((X - center[0])**2 + (Y - center[1])**2)
    inds = dist_from_center <= radius

    # Applying the Mask to the Complex Array
    mask = np.zeros_like(phase_map)
    mask[inds] = 1
    masked = phase_map * mask
    wf.wfarr = proper.prop_shift_center(amp_map * np.cos(masked) +
                                        1j * amp_map * np.sin(masked))
Esempio n. 13
0
def readmap(wf, dmap, xshift=0, yshift=0, **kwargs):
    """Read in a surface, wavefront, or amplitude error map, scaling if necessary.
    
    Parameters
    ----------
    wf : obj
        WaveFront class object
        
    dmap : 2D numpy array
        the DM map in units of surface deformation
        
    xshift, yshift : float
        Amount to shift map in meters in X and Y directions
        
        
    Returns
    -------
    dmap : numpy ndarray
        Returns the error map in a 2D numpy array
    
    
    Other Parameters
    ----------------
    XC_MAP, YC_MAP : float
        Specifies center pixel of map (default is n/2)
        
    SAMPLING : float
        Specifies sampling of map in meters (will override any sampling 
        specified in the file header; must be specified if header does not 
        specify sampling using the PIXSIZE value)
        
    
    Raises
    ------
    ValueError:
        if pixsize keyword does not exist in FITS image header
    
    
    Notes
    -----
    (a) The sampling is not returned in this variable 
    (b) if the header value RADPIX is specified (the radius of the beam in the 
        map in pixels), then this will override any other sampling specifiers, 
        either in the header or using SAMPLING.
    (c) Intended for internal use by PROP routines. Users should call either
        prop_errormap or prop_psd_errormap.
    """
    # if the radius of the beam (in pixels) in the map is specified in the
    # header, this will override any other sampling specifications, either from
    # the header (PIXSIZE value) or procedure call (SAMPLING keyword)
    pixsize = kwargs["SAMPLING"]

    if not "XC_MAP" in kwargs:
        s = dmap.shape
        xc = s[0] // 2
        yc = s[1] // 2
    else:
        xc = kwargs["XC_MAP"]
        yc = kwargs["YC_MAP"]

    # resample map to current wavefront grid spacing
    dmap = proper.prop_resamplemap(wf, dmap, pixsize, xc, yc, xshift, yshift)

    dmap = proper.prop_shift_center(dmap)

    return dmap
Esempio n. 14
0
def wfirst_phaseb(lambda_m, output_dim0, PASSVALUE={'dummy': 0}):

    # "output_dim" is used to specify the output dimension in pixels at the final image plane.
    # Computational grid sizes are hardcoded for each coronagraph.
    # Based on Zemax prescription "WFIRST_CGI_DI_LOWFS_Sep24_2018.zmx" by Hong Tang.

    data_dir = wfirst_phaseb_proper.data_dir
    if 'PASSVALUE' in locals():
        if 'data_dir' in PASSVALUE: data_dir = PASSVALUE['data_dir']

    map_dir = data_dir + wfirst_phaseb_proper.map_dir
    polfile = data_dir + wfirst_phaseb_proper.polfile

    cor_type = 'hlc'  # coronagraph type ('hlc', 'spc', 'none')
    source_x_offset_mas = 0  # source offset in mas (tilt applied at primary)
    source_y_offset_mas = 0
    source_x_offset = 0  # source offset in lambda0_m/D radians (tilt applied at primary)
    source_y_offset = 0
    polaxis = 0  # polarization axis aberrations:
    #    -2 = -45d in, Y out
    #    -1 = -45d in, X out
    #     1 = +45d in, X out
    #     2 = +45d in, Y out
    #     5 = mean of modes -1 & +1 (X channel polarizer)
    #     6 = mean of modes -2 & +2 (Y channel polarizer)
    #    10 = mean of all modes (no polarization filtering)
    use_errors = 1  # use optical surface phase errors? 1 or 0
    zindex = np.array([0, 0])  # array of Zernike polynomial indices
    zval_m = np.array([0, 0])  # array of Zernike coefficients (meters RMS WFE)
    use_aperture = 0  # use apertures on all optics? 1 or 0
    cgi_x_shift_pupdiam = 0  # X,Y shear of wavefront at FSM (bulk displacement of CGI); normalized relative to pupil diameter
    cgi_y_shift_pupdiam = 0
    cgi_x_shift_m = 0  # X,Y shear of wavefront at FSM (bulk displacement of CGI) in meters
    cgi_y_shift_m = 0
    fsm_x_offset_mas = 0  # offset in focal plane caused by tilt of FSM in mas
    fsm_y_offset_mas = 0
    fsm_x_offset = 0  # offset in focal plane caused by tilt of FSM in lambda0/D
    fsm_y_offset = 0
    end_at_fsm = 0  # end propagation after propagating to FSM (no FSM errors)
    focm_z_shift_m = 0  # offset (meters) of focus correction mirror (+ increases path length)
    use_hlc_dm_patterns = 0  # use Dwight's HLC default DM wavefront patterns? 1 or 0
    use_dm1 = 0  # use DM1? 1 or 0
    use_dm2 = 0  # use DM2? 1 or 0
    dm_sampling_m = 0.9906e-3  # actuator spacing in meters
    dm1_xc_act = 23.5  # for 48x48 DM, wavefront centered at actuator intersections: (0,0) = 1st actuator center
    dm1_yc_act = 23.5
    dm1_xtilt_deg = 0  # tilt around X axis (deg)
    dm1_ytilt_deg = 5.7  # effective DM tilt in deg including 9.65 deg actual tilt and pupil ellipticity
    dm1_ztilt_deg = 0  # rotation of DM about optical axis (deg)
    dm2_xc_act = 23.5  # for 48x48 DM, wavefront centered at actuator intersections: (0,0) = 1st actuator center
    dm2_yc_act = 23.5
    dm2_xtilt_deg = 0  # tilt around X axis (deg)
    dm2_ytilt_deg = 5.7  # effective DM tilt in deg including 9.65 deg actual tilt and pupil ellipticity
    dm2_ztilt_deg = 0  # rotation of DM about optical axis (deg)
    use_pupil_mask = 1  # SPC only: use SPC pupil mask (0 or 1)
    mask_x_shift_pupdiam = 0  # X,Y shear of shaped pupil mask; normalized relative to pupil diameter
    mask_y_shift_pupdiam = 0
    mask_x_shift_m = 0  # X,Y shear of shaped pupil mask in meters
    mask_y_shift_m = 0
    use_fpm = 1  # use occulter? 1 or 0
    fpm_x_offset = 0  # FPM x,y offset in lambda0/D
    fpm_y_offset = 0
    fpm_x_offset_m = 0  # FPM x,y offset in meters
    fpm_y_offset_m = 0
    fpm_z_shift_m = 0  # occulter offset in meters along optical axis (+ = away from prior optics)
    pinhole_diam_m = 0  # FPM pinhole diameter in meters
    end_at_fpm_exit_pupil = 0  # return field at FPM exit pupil?
    output_field_rootname = ''  # rootname of FPM exit pupil field file (must set end_at_fpm_exit_pupil=1)
    use_lyot_stop = 1  # use Lyot stop? 1 or 0
    lyot_x_shift_pupdiam = 0  # X,Y shear of Lyot stop mask; normalized relative to pupil diameter
    lyot_y_shift_pupdiam = 0
    lyot_x_shift_m = 0  # X,Y shear of Lyot stop mask in meters
    lyot_y_shift_m = 0
    use_field_stop = 1  # use field stop (HLC)? 1 or 0
    field_stop_radius_lam0 = 0  # field stop radius in lambda0/D (HLC or SPC-wide mask only)
    field_stop_x_offset = 0  # field stop offset in lambda0/D
    field_stop_y_offset = 0
    field_stop_x_offset_m = 0  # field stop offset in meters
    field_stop_y_offset_m = 0
    use_pupil_lens = 0  # use pupil imaging lens? 0 or 1
    use_defocus_lens = 0  # use defocusing lens? Options are 1, 2, 3, 4, corresponding to +18.0, +9.0, -4.0, -8.0 waves P-V @ 550 nm
    defocus = 0  # instead of specific lens, defocus in waves P-V @ 550 nm (-8.7 to 42.0 waves)
    final_sampling_m = 0  # final sampling in meters (overrides final_sampling_lam0)
    final_sampling_lam0 = 0  # final sampling in lambda0/D
    output_dim = output_dim0  # dimension of output in pixels (overrides output_dim0)

    if 'PASSVALUE' in locals():
        if 'use_fpm' in PASSVALUE: use_fpm = PASSVALUE['use_fpm']
        if 'cor_type' in PASSVALUE: cor_type = PASSVALUE['cor_type']

    is_spc = False
    is_hlc = False

    if cor_type == 'hlc':
        is_hlc = True
        file_directory = data_dir + '/hlc_20190210/'  # must have trailing "/"
        prefix = file_directory + 'run461_'
        pupil_diam_pix = 309.0
        pupil_file = prefix + 'pupil_rotated.fits'
        lyot_stop_file = prefix + 'lyot.fits'
        lambda0_m = 0.575e-6
        lam_occ = [
            5.4625e-07, 5.49444444444e-07, 5.52638888889e-07, 5.534375e-07,
            5.55833333333e-07, 5.59027777778e-07, 5.60625e-07,
            5.62222222222e-07, 5.65416666667e-07, 5.678125e-07,
            5.68611111111e-07, 5.71805555556e-07, 5.75e-07, 5.78194444444e-07,
            5.81388888889e-07, 5.821875e-07, 5.84583333333e-07,
            5.87777777778e-07, 5.89375e-07, 5.90972222222e-07,
            5.94166666667e-07, 5.965625e-07, 5.97361111111e-07,
            6.00555555556e-07, 6.0375e-07
        ]
        lam_occs = [
            '5.4625e-07', '5.49444444444e-07', '5.52638888889e-07',
            '5.534375e-07', '5.55833333333e-07', '5.59027777778e-07',
            '5.60625e-07', '5.62222222222e-07', '5.65416666667e-07',
            '5.678125e-07', '5.68611111111e-07', '5.71805555556e-07',
            '5.75e-07', '5.78194444444e-07', '5.81388888889e-07',
            '5.821875e-07', '5.84583333333e-07', '5.87777777778e-07',
            '5.89375e-07', '5.90972222222e-07', '5.94166666667e-07',
            '5.965625e-07', '5.97361111111e-07', '6.00555555556e-07',
            '6.0375e-07'
        ]
        lam_occs = [
            prefix + 'occ_lam' + s + 'theta6.69polp_' for s in lam_occs
        ]
        # find nearest matching FPM wavelength
        wlam = (np.abs(lambda_m - np.array(lam_occ))).argmin()
        occulter_file_r = lam_occs[wlam] + 'real.fits'
        occulter_file_i = lam_occs[wlam] + 'imag.fits'
        n_default = 1024  # gridsize in non-critical areas
        if use_fpm == 1:
            n_to_fpm = 2048
        else:
            n_to_fpm = 1024
        n_from_lyotstop = 1024
        field_stop_radius_lam0 = 9.0
    elif cor_type == 'hlc_erkin':
        is_hlc = True
        file_directory = data_dir + '/hlc_20190206_v3/'  # must have trailing "/"
        prefix = file_directory + 'dsn17d_run2_pup310_fpm2048_'
        pupil_diam_pix = 310.0
        pupil_file = prefix + 'pupil.fits'
        lyot_stop_file = prefix + 'lyot.fits'
        lambda0_m = 0.575e-6
        lam_occ = [
            5.4625e-07, 5.4944e-07, 5.5264e-07, 5.5583e-07, 5.5903e-07,
            5.6222e-07, 5.6542e-07, 5.6861e-07, 5.7181e-07, 5.75e-07,
            5.7819e-07, 5.8139e-07, 5.8458e-07, 5.8778e-07, 5.9097e-07,
            5.9417e-07, 5.9736e-07, 6.0056e-07, 6.0375e-07
        ]
        lam_occs = [
            '5.4625e-07', '5.4944e-07', '5.5264e-07', '5.5583e-07',
            '5.5903e-07', '5.6222e-07', '5.6542e-07', '5.6861e-07',
            '5.7181e-07', '5.75e-07', '5.7819e-07', '5.8139e-07', '5.8458e-07',
            '5.8778e-07', '5.9097e-07', '5.9417e-07', '5.9736e-07',
            '6.0056e-07', '6.0375e-07'
        ]
        lam_occs = [
            prefix + 'occ_lam' + s + 'theta6.69pols_' for s in lam_occs
        ]
        # find nearest matching FPM wavelength
        wlam = (np.abs(lambda_m - np.array(lam_occ))).argmin()
        occulter_file_r = lam_occs[wlam] + 'real_rotated.fits'
        occulter_file_i = lam_occs[wlam] + 'imag_rotated.fits'
        n_default = 1024  # gridsize in non-critical areas
        if use_fpm == 1:
            n_to_fpm = 2048
        else:
            n_to_fpm = 1024
        n_from_lyotstop = 1024
        field_stop_radius_lam0 = 9.0
    elif cor_type == 'spc-ifs_short' or cor_type == 'spc-ifs_long' or cor_type == 'spc-spec_short' or cor_type == 'spc-spec_long':
        is_spc = True
        file_dir = data_dir + '/spc_20190130/'  # must have trailing "/"
        pupil_diam_pix = 1000.0
        pupil_file = file_dir + 'pupil_SPC-20190130_rotated.fits'
        pupil_mask_file = file_dir + 'SPM_SPC-20190130.fits'
        fpm_file = file_dir + 'fpm_0.05lamdivD.fits'
        fpm_sampling = 0.05  # sampling in fpm_sampling_lambda_m/D of FPM mask
        if cor_type == 'spc-ifs_short' or cor_type == 'spc-spec_short':
            fpm_sampling_lambda_m = 0.66e-6
            lambda0_m = 0.66e-6
        else:
            fpm_sampling_lambda_m = 0.73e-6
            lambda0_m = 0.73e-6  # FPM scaled for this central wavelength
        lyot_stop_file = file_dir + 'LS_SPC-20190130.fits'
        n_default = 2048  # gridsize in non-critical areas
        n_to_fpm = 2048  # gridsize to/from FPM
        n_mft = 1400  # gridsize to FPM (propagation to/from FPM handled by MFT)
        n_from_lyotstop = 4096
    elif cor_type == 'spc-wide':
        is_spc = True
        file_dir = data_dir + '/spc_20181220/'  # must have trailing "/"
        pupil_diam_pix = 1000.0
        pupil_file = file_dir + 'pupil_SPC-20181220_1k_rotated.fits'
        pupil_mask_file = file_dir + 'SPM_SPC-20181220_1000_rounded9_gray.fits'
        fpm_file = file_dir + 'fpm_0.05lamdivD.fits'
        fpm_sampling = 0.05  # sampling in lambda0/D of FPM mask
        fpm_sampling_lambda_m = 0.825e-6
        lambda0_m = 0.825e-6  # FPM scaled for this central wavelength
        lyot_stop_file = file_dir + 'LS_SPC-20181220_1k.fits'
        n_default = 2048  # gridsize in non-critical areas
        n_to_fpm = 2048  # gridsize to/from FPM
        n_mft = 1400
        n_from_lyotstop = 4096
    elif cor_type == 'none':
        file_directory = data_dir + '/hlc_20190210/'  # must have trailing "/"
        prefix = file_directory + 'run461_'
        pupil_diam_pix = 309.0
        pupil_file = prefix + 'pupil_rotated.fits'
        lambda0_m = 0.575e-6
        use_fpm = 0
        use_lyot_stop = 0
        use_field_stop = 0
        n_default = 1024
        n_to_fpm = 1024
        n_from_lyotstop = 1024
    else:
        raise Exception('ERROR: Unsupported cor_type: ' + cor_type)

    if 'PASSVALUE' in locals():
        if 'lam0' in PASSVALUE: lamba0_m = PASSVALUE['lam0'] * 1.0e-6
        if 'lambda0_m' in PASSVALUE: lambda0_m = PASSVALUE['lambda0_m']
        mas_per_lamD = lambda0_m * 360.0 * 3600.0 / (
            2 * np.pi * 2.363) * 1000  # mas per lambda0/D
        if 'source_x_offset' in PASSVALUE:
            source_x_offset = PASSVALUE['source_x_offset']
        if 'source_y_offset' in PASSVALUE:
            source_y_offset = PASSVALUE['source_y_offset']
        if 'source_x_offset_mas' in PASSVALUE:
            source_x_offset = PASSVALUE['source_x_offset_mas'] / mas_per_lamD
        if 'source_y_offset_mas' in PASSVALUE:
            source_y_offset = PASSVALUE['source_y_offset_mas'] / mas_per_lamD
        if 'use_errors' in PASSVALUE: use_errors = PASSVALUE['use_errors']
        if 'polaxis' in PASSVALUE: polaxis = PASSVALUE['polaxis']
        if 'zindex' in PASSVALUE: zindex = np.array(PASSVALUE['zindex'])
        if 'zval_m' in PASSVALUE: zval_m = np.array(PASSVALUE['zval_m'])
        if 'end_at_fsm' in PASSVALUE: end_at_fsm = PASSVALUE['end_at_fsm']
        if 'cgi_x_shift_pupdiam' in PASSVALUE:
            cgi_x_shift_pupdiam = PASSVALUE['cgi_x_shift_pupdiam']
        if 'cgi_y_shift_pupdiam' in PASSVALUE:
            cgi_y_shift_pupdiam = PASSVALUE['cgi_y_shift_pupdiam']
        if 'cgi_x_shift_m' in PASSVALUE:
            cgi_x_shift_m = PASSVALUE['cgi_x_shift_m']
        if 'cgi_y_shift_m' in PASSVALUE:
            cgi_y_shift_m = PASSVALUE['cgi_y_shift_m']
        if 'fsm_x_offset' in PASSVALUE:
            fsm_x_offset = PASSVALUE['fsm_x_offset']
        if 'fsm_y_offset' in PASSVALUE:
            fsm_y_offset = PASSVALUE['fsm_y_offset']
        if 'fsm_x_offset_mas' in PASSVALUE:
            fsm_x_offset = PASSVALUE['fsm_x_offset_mas'] / mas_per_lamD
        if 'fsm_y_offset_mas' in PASSVALUE:
            fsm_y_offset = PASSVALUE['fsm_y_offset_mas'] / mas_per_lamD
        if 'focm_z_shift_m' in PASSVALUE:
            focm_z_shift_m = PASSVALUE['focm_z_shift_m']
        if 'use_hlc_dm_patterns' in PASSVALUE:
            use_hlc_dm_patterns = PASSVALUE['use_hlc_dm_patterns']
        if 'use_dm1' in PASSVALUE: use_dm1 = PASSVALUE['use_dm1']
        if 'dm1_m' in PASSVALUE: dm1_m = PASSVALUE['dm1_m']
        if 'dm1_xc_act' in PASSVALUE: dm1_xc_act = PASSVALUE['dm1_xc_act']
        if 'dm1_yc_act' in PASSVALUE: dm1_yc_act = PASSVALUE['dm1_yc_act']
        if 'dm1_xtilt_deg' in PASSVALUE:
            dm1_xtilt_deg = PASSVALUE['dm1_xtilt_deg']
        if 'dm1_ytilt_deg' in PASSVALUE:
            dm1_ytilt_deg = PASSVALUE['dm1_ytilt_deg']
        if 'dm1_ztilt_deg' in PASSVALUE:
            dm1_ztilt_deg = PASSVALUE['dm1_ztilt_deg']
        if 'use_dm2' in PASSVALUE: use_dm2 = PASSVALUE['use_dm2']
        if 'dm2_m' in PASSVALUE: dm2_m = PASSVALUE['dm2_m']
        if 'dm2_xc_act' in PASSVALUE: dm2_xc_act = PASSVALUE['dm2_xc_act']
        if 'dm2_yc_act' in PASSVALUE: dm2_yc_act = PASSVALUE['dm2_yc_act']
        if 'dm2_xtilt_deg' in PASSVALUE:
            dm2_xtilt_deg = PASSVALUE['dm2_xtilt_deg']
        if 'dm2_ytilt_deg' in PASSVALUE:
            dm2_ytilt_deg = PASSVALUE['dm2_ytilt_deg']
        if 'dm2_ztilt_deg' in PASSVALUE:
            dm2_ztilt_deg = PASSVALUE['dm2_ztilt_deg']
        if 'use_pupil_mask' in PASSVALUE:
            use_pupil_mask = PASSVALUE['use_pupil_mask']
        if 'mask_x_shift_pupdiam' in PASSVALUE:
            mask_x_shift_pupdiam = PASSVALUE['mask_x_shift_pupdiam']
        if 'mask_y_shift_pupdiam' in PASSVALUE:
            mask_y_shift_pupdiam = PASSVALUE['mask_y_shift_pupdiam']
        if 'mask_x_shift_m' in PASSVALUE:
            mask_x_shift_m = PASSVALUE['mask_x_shift_m']
        if 'mask_y_shift_m' in PASSVALUE:
            mask_y_shift_m = PASSVALUE['mask_y_shift_m']
        if 'fpm_x_offset' in PASSVALUE:
            fpm_x_offset = PASSVALUE['fpm_x_offset']
        if 'fpm_y_offset' in PASSVALUE:
            fpm_y_offset = PASSVALUE['fpm_y_offset']
        if 'fpm_x_offset_m' in PASSVALUE:
            fpm_x_offset_m = PASSVALUE['fpm_x_offset_m']
        if 'fpm_y_offset_m' in PASSVALUE:
            fpm_y_offset_m = PASSVALUE['fpm_y_offset_m']
        if 'fpm_z_shift_m' in PASSVALUE:
            fpm_z_shift_m = PASSVALUE['fpm_z_shift_m']
        if 'pinhole_diam_m' in PASSVALUE:
            pinhole_diam_m = PASSVALUE['pinhole_diam_m']
        if 'end_at_fpm_exit_pupil' in PASSVALUE:
            end_at_fpm_exit_pupil = PASSVALUE['end_at_fpm_exit_pupil']
        if 'output_field_rootname' in PASSVALUE:
            output_field_rootname = PASSVALUE['output_field_rootname']
        if 'use_lyot_stop' in PASSVALUE:
            use_lyot_stop = PASSVALUE['use_lyot_stop']
        if 'lyot_x_shift_pupdiam' in PASSVALUE:
            lyot_x_shift_pupdiam = PASSVALUE['lyot_x_shift_pupdiam']
        if 'lyot_y_shift_pupdiam' in PASSVALUE:
            lyot_y_shift_pupdiam = PASSVALUE['lyot_y_shift_pupdiam']
        if 'lyot_x_shift_m' in PASSVALUE:
            lyot_x_shift_m = PASSVALUE['lyot_x_shift_m']
        if 'lyot_y_shift_m' in PASSVALUE:
            lyot_y_shift_m = PASSVALUE['lyot_y_shift_m']
        if 'use_field_stop' in PASSVALUE:
            use_field_stop = PASSVALUE['use_field_stop']
        if 'field_stop_x_offset' in PASSVALUE:
            field_stop_x_offset = PASSVALUE['field_stop_x_offset']
        if 'field_stop_y_offset' in PASSVALUE:
            field_stop_y_offset = PASSVALUE['field_stop_y_offset']
        if 'field_stop_x_offset_m' in PASSVALUE:
            field_stop_x_offset_m = PASSVALUE['field_stop_x_offset_m']
        if 'field_stop_y_offset_m' in PASSVALUE:
            field_stop_y_offset_m = PASSVALUE['field_stop_y_offset_m']
        if 'use_pupil_lens' in PASSVALUE:
            use_pupil_lens = PASSVALUE['use_pupil_lens']
        if 'use_defocus_lens' in PASSVALUE:
            use_defocus_lens = PASSVALUE['use_defocus_lens']
        if 'defocus' in PASSVALUE: defocus = PASSVALUE['defocus']
        if 'output_dim' in PASSVALUE: output_dim = PASSVALUE['output_dim']
        if 'final_sampling_m' in PASSVALUE:
            final_sampling_m = PASSVALUE['final_sampling_m']
        if 'final_sampling_lam0' in PASSVALUE:
            final_sampling_lam0 = PASSVALUE['final_sampling_lam0']

    diam = 2.3633372
    fl_pri = 2.83459423440 * 1.0013
    d_pri_sec = 2.285150515460035
    d_focus_sec = d_pri_sec - fl_pri
    fl_sec = -0.653933011 * 1.0004095
    d_sec_focus = 3.580188916677103
    diam_sec = 0.58166
    d_sec_fold1 = 2.993753476654728
    d_fold1_focus = 0.586435440022375
    diam_fold1 = 0.09
    d_fold1_m3 = 1.680935841598811
    fl_m3 = 0.430216463069001
    d_focus_m3 = 1.094500401576436
    d_m3_pupil = 0.469156807701977
    d_m3_focus = 0.708841602661368
    diam_m3 = 0.2
    d_m3_m4 = 0.943514749358944
    fl_m4 = 0.116239114833590
    d_focus_m4 = 0.234673014520402
    d_m4_pupil = 0.474357941656967
    d_m4_focus = 0.230324117970585
    diam_m4 = 0.07
    d_m4_m5 = 0.429145636743193
    d_m5_focus = 0.198821518772608
    fl_m5 = 0.198821518772608
    d_m5_pupil = 0.716529242882632
    diam_m5 = 0.07
    d_m5_fold2 = 0.351125431220770
    diam_fold2 = 0.06
    d_fold2_fsm = 0.365403811661862
    d_fsm_oap1 = 0.354826767220001
    fl_oap1 = 0.503331895563883
    diam_oap1 = 0.06
    d_oap1_focm = 0.768005607094041
    d_focm_oap2 = 0.314483210543378
    fl_oap2 = 0.579156922073536
    diam_oap2 = 0.06
    d_oap2_dm1 = 0.775775726154228
    d_dm1_dm2 = 1.0
    d_dm2_oap3 = 0.394833855161549
    fl_oap3 = 1.217276467668519
    diam_oap3 = 0.06
    d_oap3_fold3 = 0.505329955078121
    diam_fold3 = 0.06
    d_fold3_oap4 = 1.158897671642761
    fl_oap4 = 0.446951159052363
    diam_oap4 = 0.06
    d_oap4_pupilmask = 0.423013568764728
    d_pupilmask_oap5 = 0.408810648253099
    fl_oap5 = 0.548189351937178
    diam_oap5 = 0.06
    d_oap5_fpm = 0.548189083164429
    d_fpm_oap6 = 0.548189083164429
    fl_oap6 = 0.548189083164429
    diam_oap6 = 0.06
    d_oap6_lyotstop = 0.687567667550736
    d_lyotstop_oap7 = 0.401748843470518
    fl_oap7 = 0.708251083480054
    diam_oap7 = 0.06
    d_oap7_fieldstop = 0.708251083480054
    d_fieldstop_oap8 = 0.210985967281651
    fl_oap8 = 0.210985967281651
    diam_oap8 = 0.06
    d_oap8_pupil = 0.238185804200797
    d_oap8_filter = 0.368452268225530
    diam_filter = 0.01
    d_filter_lens = 0.170799548215162
    fl_lens = 0.246017378417573 + 0.050001306014153
    diam_lens = 0.01
    d_lens_fold4 = 0.246017378417573
    diam_fold4 = 0.02
    d_fold4_image = 0.050001578514650
    fl_pupillens = 0.149260576823040

    n = n_default  # start off with less padding

    wavefront = proper.prop_begin(diam, lambda_m, n, float(pupil_diam_pix) / n)
    pupil = proper.prop_fits_read(pupil_file)
    proper.prop_multiply(wavefront, trim(pupil, n))
    pupil = 0
    if polaxis != 0: polmap(wavefront, polfile, pupil_diam_pix, polaxis)
    proper.prop_define_entrance(wavefront)
    proper.prop_lens(wavefront, fl_pri)
    if source_x_offset != 0 or source_y_offset != 0:
        # compute tilted wavefront to offset source by xoffset,yoffset lambda0_m/D
        xtilt_lam = -source_x_offset * lambda0_m / lambda_m
        ytilt_lam = -source_y_offset * lambda0_m / lambda_m
        x = np.tile((np.arange(n) - n // 2) / (pupil_diam_pix / 2.0), (n, 1))
        y = np.transpose(x)
        proper.prop_multiply(
            wavefront,
            np.exp(complex(0, 1) * np.pi * (xtilt_lam * x + ytilt_lam * y)))
        x = 0
        y = 0
    if zindex[0] != 0: proper.prop_zernikes(wavefront, zindex, zval_m)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_PRIMARY_phase_error_V1.0.fits',
                             WAVEFRONT=True)
        proper.prop_errormap(
            wavefront,
            map_dir +
            'wfirst_phaseb_GROUND_TO_ORBIT_4.2X_phase_error_V1.0.fits',
            WAVEFRONT=True)

    proper.prop_propagate(wavefront, d_pri_sec, 'secondary')
    proper.prop_lens(wavefront, fl_sec)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_SECONDARY_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_sec / 2.0)

    proper.prop_propagate(wavefront, d_sec_fold1, 'FOLD_1')
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_FOLD1_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_fold1 / 2.0)

    proper.prop_propagate(wavefront, d_fold1_m3, 'M3')
    proper.prop_lens(wavefront, fl_m3)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_M3_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_m3 / 2.0)

    proper.prop_propagate(wavefront, d_m3_m4, 'M4')
    proper.prop_lens(wavefront, fl_m4)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_M4_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_m4 / 2.0)

    proper.prop_propagate(wavefront, d_m4_m5, 'M5')
    proper.prop_lens(wavefront, fl_m5)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_M5_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_m5 / 2.0)

    proper.prop_propagate(wavefront, d_m5_fold2, 'FOLD_2')
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_FOLD2_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_fold2 / 2.0)

    proper.prop_propagate(wavefront, d_fold2_fsm, 'FSM')
    if end_at_fsm == 1:
        (wavefront, sampling_m) = proper.prop_end(wavefront, NOABS=True)
        wavefront = trim(wavefront, n)
        return wavefront, sampling_m
    if cgi_x_shift_pupdiam != 0 or cgi_y_shift_pupdiam != 0 or cgi_x_shift_m != 0 or cgi_y_shift_m != 0:  # bulk coronagraph pupil shear
        # FFT the field, apply a tilt, FFT back
        if cgi_x_shift_pupdiam != 0 or cgi_y_shift_pupdiam != 0:
            # offsets are normalized to pupil diameter
            xt = -cgi_x_shift_pupdiam * pupil_diam_pix * float(
                pupil_diam_pix) / n
            yt = -cgi_y_shift_pupdiam * pupil_diam_pix * float(
                pupil_diam_pix) / n
        else:
            # offsets are meters
            d_m = proper.prop_get_sampling(wavefront)
            xt = -cgi_x_shift_m / d_m * float(pupil_diam_pix) / n
            yt = -cgi_y_shift_m / d_m * float(pupil_diam_pix) / n
        x = np.tile((np.arange(n) - n // 2) / (pupil_diam_pix / 2.0), (n, 1))
        y = np.transpose(x)
        tilt = complex(0, 1) * np.pi * (x * xt + y * yt)
        x = 0
        y = 0
        wavefront0 = proper.prop_get_wavefront(wavefront)
        wavefront0 = ffts(wavefront0, -1)
        wavefront0 *= np.exp(tilt)
        wavefront0 = ffts(wavefront0, 1)
        tilt = 0
        wavefront.wfarr[:, :] = proper.prop_shift_center(wavefront0)
        wavefront0 = 0
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_FSM_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_fsm / 2.0)
    if (fsm_x_offset != 0.0 or fsm_y_offset != 0.0):
        # compute tilted wavefront to offset source by fsm_x_offset,fsm_y_offset lambda0_m/D
        xtilt_lam = fsm_x_offset * lambda0_m / lambda_m
        ytilt_lam = fsm_y_offset * lambda0_m / lambda_m
        x = np.tile((np.arange(n) - n // 2) / (pupil_diam_pix / 2.0), (n, 1))
        y = np.transpose(x)
        proper.prop_multiply(
            wavefront,
            np.exp(complex(0, 1) * np.pi * (xtilt_lam * x + ytilt_lam * y)))
        x = 0
        y = 0

    proper.prop_propagate(wavefront, d_fsm_oap1, 'OAP1')
    proper.prop_lens(wavefront, fl_oap1)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_OAP1_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_oap1 / 2.0)

    proper.prop_propagate(wavefront, d_oap1_focm + focm_z_shift_m, 'FOCM')
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_FOCM_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_focm / 2.0)

    proper.prop_propagate(wavefront, d_focm_oap2 + focm_z_shift_m, 'OAP2')
    proper.prop_lens(wavefront, fl_oap2)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_OAP2_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_oap2 / 2.0)

    proper.prop_propagate(wavefront, d_oap2_dm1, 'DM1')
    if use_dm1 != 0:
        proper.prop_dm(wavefront,
                       dm1_m,
                       dm1_xc_act,
                       dm1_yc_act,
                       dm_sampling_m,
                       XTILT=dm1_xtilt_deg,
                       YTILT=dm1_ytilt_deg,
                       ZTILT=dm1_ztilt_deg)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_DM1_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if is_hlc == True and use_hlc_dm_patterns == 1:
        dm1wfe = proper.prop_fits_read(prefix + 'dm1wfe.fits')
        proper.prop_add_phase(wavefront, trim(dm1wfe, n))
        dm1wfe = 0

    proper.prop_propagate(wavefront, d_dm1_dm2, 'DM2')
    if use_dm2 == 1:
        proper.prop_dm(wavefront,
                       dm2_m,
                       dm2_xc_act,
                       dm2_yc_act,
                       dm_sampling_m,
                       XTILT=dm2_xtilt_deg,
                       YTILT=dm2_ytilt_deg,
                       ZTILT=dm2_ztilt_deg)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_DM2_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if is_hlc == True:
        if use_hlc_dm_patterns == 1:
            dm2wfe = proper.prop_fits_read(prefix + 'dm2wfe.fits')
            proper.prop_add_phase(wavefront, trim(dm2wfe, n))
            dm2wfe = 0
        dm2mask = proper.prop_fits_read(prefix + 'dm2mask.fits')
        proper.prop_multiply(wavefront, trim(dm2mask, n))
        dm2mask = 0

    proper.prop_propagate(wavefront, d_dm2_oap3, 'OAP3')
    proper.prop_lens(wavefront, fl_oap3)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_OAP3_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_oap3 / 2.0)

    proper.prop_propagate(wavefront, d_oap3_fold3, 'FOLD_3')
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_FOLD3_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_fold3 / 2.0)

    proper.prop_propagate(wavefront, d_fold3_oap4, 'OAP4')
    proper.prop_lens(wavefront, fl_oap4)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_OAP4_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_oap4 / 2.0)

    proper.prop_propagate(wavefront, d_oap4_pupilmask,
                          'PUPIL_MASK')  # flat/reflective shaped pupil
    if is_spc == True and use_pupil_mask != 0:
        pupil_mask = proper.prop_fits_read(pupil_mask_file)
        pupil_mask = trim(pupil_mask, n)
        if mask_x_shift_pupdiam != 0 or mask_y_shift_pupdiam != 0 or mask_x_shift_m != 0 or mask_y_shift_m != 0:
            # shift SP mask by FFTing it, applying tilt, and FFTing back
            if mask_x_shift_pupdiam != 0 or mask_y_shift_pupdiam != 0:
                # offsets are normalized to pupil diameter
                xt = -mask_x_shift_pupdiam * pupil_diam_pix * float(
                    pupil_diam_pix) / n
                yt = -mask_y_shift_pupdiam * pupil_diam_pix * float(
                    pupil_diam_pix) / n
            else:
                d_m = proper.prop_get_sampling(wavefront)
                xt = -mask_x_shift_m / d_m * float(pupil_diam_pix) / n
                yt = -mask_y_shift_m / d_m * float(pupil_diam_pix) / n
            x = np.tile((np.arange(n) - n // 2) / (pupil_diam_pix / 2.0),
                        (n, 1))
            y = np.transpose(x)
            tilt = complex(0, 1) * np.pi * (x * xt + y * yt)
            x = 0
            y = 0
            pupil_mask = ffts(pupil_mask, -1)
            pupil_mask *= np.exp(tilt)
            pupil_mask = ffts(pupil_mask, 1)
            pupil_mask = pupil_mask.real
            tilt = 0
        proper.prop_multiply(wavefront, pupil_mask)
        pupil_mask = 0
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_PUPILMASK_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    # while at a pupil, use more padding to provide 2x better sampling at FPM
    diam = 2 * proper.prop_get_beamradius(wavefront)
    (wavefront, dx) = proper.prop_end(wavefront, NOABS=True)
    n = n_to_fpm
    wavefront0 = trim(wavefront, n)
    wavefront = proper.prop_begin(diam, lambda_m, n, float(pupil_diam_pix) / n)
    wavefront.wfarr[:, :] = proper.prop_shift_center(wavefront0)
    wavefront0 = 0

    proper.prop_propagate(wavefront, d_pupilmask_oap5, 'OAP5')
    proper.prop_lens(wavefront, fl_oap5)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_OAP5_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_oap5 / 2.0)

    proper.prop_propagate(wavefront,
                          d_oap5_fpm + fpm_z_shift_m,
                          'FPM',
                          TO_PLANE=True)
    if use_fpm == 1:
        if fpm_x_offset != 0 or fpm_y_offset != 0 or fpm_x_offset_m != 0 or fpm_y_offset_m != 0:
            # To shift FPM, FFT field to pupil, apply tilt, FFT back to focus,
            # apply FPM, FFT to pupil, take out tilt, FFT back to focus
            if fpm_x_offset != 0 or fpm_y_offset != 0:
                # shifts are specified in lambda0/D
                x_offset_lamD = fpm_x_offset * lambda0_m / lambda_m
                y_offset_lamD = fpm_y_offset * lambda0_m / lambda_m
            else:
                d_m = proper.prop_get_sampling(wavefront)
                x_offset_lamD = fpm_x_offset_m / d_m * float(
                    pupil_diam_pix) / n
                y_offset_lamD = fpm_y_offset_m / d_m * float(
                    pupil_diam_pix) / n
            x = np.tile((np.arange(n) - n // 2) / (pupil_diam_pix / 2.0),
                        (n, 1))
            y = np.transpose(x)
            tilt = complex(0,
                           1) * np.pi * (x * x_offset_lamD + y * y_offset_lamD)
            x = 0
            y = 0
            wavefront0 = proper.prop_get_wavefront(wavefront)
            wavefront0 = ffts(wavefront0, -1)
            wavefront0 *= np.exp(tilt)
            wavefront0 = ffts(wavefront0, 1)
            wavefront.wfarr[:, :] = proper.prop_shift_center(wavefront0)
            wavefront0 = 0
        if is_hlc == True:
            occ_r = proper.prop_fits_read(occulter_file_r)
            occ_i = proper.prop_fits_read(occulter_file_i)
            occ = np.array(occ_r + 1j * occ_i, dtype=np.complex128)
            proper.prop_multiply(wavefront, trim(occ, n))
            occ_r = 0
            occ_i = 0
            occ = 0
        elif is_spc == True:
            # super-sample FPM
            wavefront0 = proper.prop_get_wavefront(wavefront)
            wavefront0 = ffts(wavefront0, 1)  # to virtual pupil
            wavefront0 = trim(wavefront0, n_mft)
            fpm = proper.prop_fits_read(fpm_file)
            nfpm = fpm.shape[1]
            fpm_sampling_lam = fpm_sampling * fpm_sampling_lambda_m / lambda_m
            wavefront0 = mft2(wavefront0, fpm_sampling_lam, pupil_diam_pix,
                              nfpm, -1)  # MFT to highly-sampled focal plane
            wavefront0 *= fpm
            fpm = 0
            wavefront0 = mft2(wavefront0, fpm_sampling_lam, pupil_diam_pix, n,
                              +1)  # MFT to virtual pupil
            wavefront0 = ffts(wavefront0,
                              -1)  # back to normally-sampled focal plane
            wavefront.wfarr[:, :] = proper.prop_shift_center(wavefront0)
            wavefront0 = 0
        if fpm_x_offset != 0 or fpm_y_offset != 0 or fpm_x_offset_m != 0 or fpm_y_offset_m != 0:
            wavefront0 = proper.prop_get_wavefront(wavefront)
            wavefront0 = ffts(wavefront0, -1)
            wavefront0 *= np.exp(-tilt)
            wavefront0 = ffts(wavefront0, 1)
            wavefront.wfarr[:, :] = proper.prop_shift_center(wavefront0)
            wavefront0 = 0
            tilt = 0
    if pinhole_diam_m != 0:
        # "pinhole_diam_m" is pinhole diameter in meters
        dx_m = proper.prop_get_sampling(wavefront)
        dx_pinhole_diam_m = pinhole_diam_m / 101.0  # 101 samples across pinhole
        n_out = 105
        m_per_lamD = dx_m * n / float(
            pupil_diam_pix)  # current focal plane sampling in lambda_m/D
        dx_pinhole_lamD = dx_pinhole_diam_m / m_per_lamD  # pinhole sampling in lambda_m/D
        n_in = int(round(pupil_diam_pix * 1.2))
        wavefront0 = proper.prop_get_wavefront(wavefront)
        wavefront0 = ffts(wavefront0, +1)  # to virtual pupil
        wavefront0 = trim(wavefront0, n_in)
        m = dx_pinhole_lamD * n_in * float(n_out) / pupil_diam_pix
        wavefront0 = mft2(wavefront0, dx_pinhole_lamD, pupil_diam_pix, n_out,
                          -1)  # MFT to highly-sampled focal plane
        p = (radius(n_out) * dx_pinhole_diam_m) <= (pinhole_diam_m / 2.0)
        p = p.astype(np.int)
        wavefront0 *= p
        p = 0
        wavefront0 = mft2(wavefront0, dx_pinhole_lamD, pupil_diam_pix, n,
                          +1)  # MFT back to virtual pupil
        wavefront0 = ffts(wavefront0,
                          -1)  # back to normally-sampled focal plane
        wavefront.wfarr[:, :] = proper.prop_shift_center(wavefront0)
        wavefront0 = 0

    proper.prop_propagate(wavefront, d_fpm_oap6 - fpm_z_shift_m, 'OAP6')
    proper.prop_lens(wavefront, fl_oap6)
    if use_errors != 0 and end_at_fpm_exit_pupil == 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_OAP6_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_oap6 / 2.0)

    proper.prop_propagate(wavefront, d_oap6_lyotstop, 'LYOT_STOP')
    # while at a pupil, switch back to less padding
    diam = 2 * proper.prop_get_beamradius(wavefront)
    (wavefront, dx) = proper.prop_end(wavefront, NOABS=True)
    n = n_from_lyotstop
    wavefront = trim(wavefront, n)
    if output_field_rootname != '':
        lams = format(lambda_m * 1e6, "6.4f")
        pols = format(int(round(polaxis)))
        hdu = pyfits.PrimaryHDU()
        hdu.data = np.real(wavefront)
        hdu.writeto(output_field_rootname + '_' + lams + 'um_' + pols +
                    '_real.fits',
                    overwrite=True)
        hdu = pyfits.PrimaryHDU()
        hdu.data = np.imag(wavefront)
        hdu.writeto(output_field_rootname + '_' + lams + 'um_' + pols +
                    '_imag.fits',
                    overwrite=True)
    if end_at_fpm_exit_pupil == 1:
        return wavefront, dx
    wavefront0 = wavefront.copy()
    wavefront = 0
    wavefront = proper.prop_begin(diam, lambda_m, n, float(pupil_diam_pix) / n)
    wavefront.wfarr[:, :] = proper.prop_shift_center(wavefront0)
    wavefront0 = 0

    if use_lyot_stop != 0:
        lyot = proper.prop_fits_read(lyot_stop_file)
        lyot = trim(lyot, n)
        if lyot_x_shift_pupdiam != 0 or lyot_y_shift_pupdiam != 0 or lyot_x_shift_m != 0 or lyot_y_shift_m != 0:
            # apply shift to lyot stop by FFTing the stop, applying a tilt, and FFTing back
            if lyot_x_shift_pupdiam != 0 or lyot_y_shift_pupdiam != 0:
                # offsets are normalized to pupil diameter
                xt = -lyot_x_shift_pupdiam * pupil_diam_pix * float(
                    pupil_diam_pix) / n
                yt = -lyot_y_shift_pupdiam * pupil_diam_pix * float(
                    pupil_diam_pix) / n
            else:
                d_m = proper.prop_get_sampling(wavefront)
                xt = -lyot_x_shift_m / d_m * float(pupil_diam_pix) / n
                yt = -lyot_y_shift_m / d_m * float(pupil_diam_pix) / n
            x = np.tile((np.arange(n) - n // 2) / (pupil_diam_pix / 2.0),
                        (n, 1))
            y = np.transpose(x)
            tilt = complex(0, 1) * np.pi * (x * xt + y * yt)
            x = 0
            y = 0
            lyot = ffts(lyot, -1)
            lyot *= np.exp(tilt)
            lyot = ffts(lyot, 1)
            lyot = lyot.real
            tilt = 0
        proper.prop_multiply(wavefront, lyot)
        lyot = 0
    if use_pupil_lens != 0 or pinhole_diam_m != 0:
        proper.prop_circular_aperture(wavefront, 1.1, NORM=True)

    proper.prop_propagate(wavefront, d_lyotstop_oap7, 'OAP7')
    proper.prop_lens(wavefront, fl_oap7)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_OAP7_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_oap7 / 2.0)

    proper.prop_propagate(wavefront, d_oap7_fieldstop, 'FIELD_STOP')
    if use_field_stop != 0 and (cor_type == 'hlc' or cor_type == 'hlc_erkin'):
        sampling_lamD = float(
            pupil_diam_pix) / n  # sampling at focus in lambda_m/D
        stop_radius = field_stop_radius_lam0 / sampling_lamD * (
            lambda0_m / lambda_m) * proper.prop_get_sampling(wavefront)
        if field_stop_x_offset != 0 or field_stop_y_offset != 0:
            # convert offsets in lambda0/D to meters
            x_offset_lamD = field_stop_x_offset * lambda0_m / lambda_m
            y_offset_lamD = field_stop_y_offset * lambda0_m / lambda_m
            pupil_ratio = float(pupil_diam_pix) / n
            field_stop_x_offset_m = x_offset_lamD / pupil_ratio * proper.prop_get_sampling(
                wavefront)
            field_stop_y_offset_m = y_offset_lamD / pupil_ratio * proper.prop_get_sampling(
                wavefront)
        proper.prop_circular_aperture(wavefront, stop_radius,
                                      -field_stop_x_offset_m,
                                      -field_stop_y_offset_m)

    proper.prop_propagate(wavefront, d_fieldstop_oap8, 'OAP8')
    proper.prop_lens(wavefront, fl_oap8)
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_OAP8_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_oap8 / 2.0)

    proper.prop_propagate(wavefront, d_oap8_filter, 'filter')
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_FILTER_phase_error_V1.0.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_filter / 2.0)

    proper.prop_propagate(wavefront, d_filter_lens, 'LENS')
    if use_pupil_lens == 0 and use_defocus_lens == 0 and defocus == 0:
        # use imaging lens to create normal focus
        proper.prop_lens(wavefront, fl_lens)
        if use_errors != 0:
            proper.prop_errormap(wavefront,
                                 map_dir +
                                 'wfirst_phaseb_LENS_phase_error_V1.0.fits',
                                 WAVEFRONT=True)
    elif use_pupil_lens != 0:
        # use pupil imaging lens
        proper.prop_lens(wavefront, fl_pupillens)
        if use_errors != 0:
            proper.prop_errormap(
                wavefront,
                map_dir + 'wfirst_phaseb_PUPILLENS_phase_error_V1.0.fits',
                WAVEFRONT=True)
    else:
        # table is waves P-V @ 575 nm
        z4_pv_waves = np.array([
            -9.0545, -8.5543, -8.3550, -8.0300, -7.54500, -7.03350, -6.03300,
            -5.03300, -4.02000, -2.51980, 0.00000000, 3.028000, 4.95000,
            6.353600, 8.030000, 10.10500, 12.06000, 14.06000, 20.26000,
            28.34000, 40.77500, 56.65700
        ])
        fl_defocus_lens = np.array([
            5.09118, 1.89323, 1.54206, 1.21198, 0.914799, 0.743569, 0.567599,
            0.470213, 0.406973, 0.350755, 0.29601868, 0.260092, 0.24516,
            0.236606, 0.228181, 0.219748, 0.213278, 0.207816, 0.195536,
            0.185600, 0.176629, 0.169984
        ])
        # subtract ad-hoc function to make z4 vs f_length more accurately spline interpolatible
        f = fl_defocus_lens / 0.005
        f0 = 59.203738
        z4t = z4_pv_waves - (0.005 * (f0 - f - 40)) / f**2 / 0.575e-6
        if use_defocus_lens != 0:
            # use one of 4 defocusing lenses
            defocus = np.array([18.0, 9.0, -4.0, -8.0])  # waves P-V @ 575 nm
            f = interp1d(z4_pv_waves, z4t, kind='cubic')
            z4x = f(defocus)
            f = interp1d(z4t, fl_defocus_lens, kind='cubic')
            lens_fl = f(z4x)
            proper.prop_lens(wavefront, lens_fl[use_defocus_lens - 1])
            if use_errors != 0:
                proper.prop_errormap(wavefront,
                                     map_dir + 'wfirst_phaseb_DEFOCUSLENS' +
                                     str(use_defocus_lens) +
                                     '_phase_error_V1.0.fits',
                                     WAVEFRONT=True)
            defocus = defocus[use_defocus_lens - 1]
        else:
            # specify amount of defocus (P-V waves @ 575 nm)
            f = interp1d(z4_pv_waves, z4t, kind='cubic')
            z4x = f(defocus)
            f = interp1d(z4t, fl_defocus_lens, kind='cubic')
            lens_fl = f(z4x)
            proper.prop_lens(wavefront, lens_fl)
            if use_errors != 0:
                proper.prop_errormap(
                    wavefront,
                    map_dir +
                    'wfirst_phaseb_DEFOCUSLENS1_phase_error_V1.0.fits',
                    WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_lens / 2.0)

    proper.prop_propagate(wavefront, d_lens_fold4, 'FOLD_4')
    if use_errors != 0:
        proper.prop_errormap(wavefront,
                             map_dir +
                             'wfirst_phaseb_FOLD4_phase_error_V1.1.fits',
                             WAVEFRONT=True)
    if use_aperture != 0:
        proper.prop_circular_aperture(wavefront, diam_fold4 / 2.0)

    if defocus != 0 or use_defocus_lens != 0:
        if np.abs(defocus) <= 4:
            proper.prop_propagate(wavefront,
                                  d_fold4_image,
                                  'IMAGE',
                                  TO_PLANE=True)
        else:
            proper.prop_propagate(wavefront, d_fold4_image, 'IMAGE')
    else:
        proper.prop_propagate(wavefront, d_fold4_image, 'IMAGE')

    (wavefront, sampling_m) = proper.prop_end(wavefront, NOABS=True)

    if final_sampling_lam0 != 0 or final_sampling_m != 0:
        if final_sampling_m != 0:
            mag = sampling_m / final_sampling_m
            sampling_m = final_sampling_m
        else:
            mag = (float(pupil_diam_pix) /
                   n) / final_sampling_lam0 * (lambda_m / lambda0_m)
            sampling_m = sampling_m / mag
        wavefront = proper.prop_magnify(wavefront,
                                        mag,
                                        output_dim,
                                        AMP_CONSERVE=True)
    else:
        wavefront = trim(wavefront, output_dim)

    return wavefront, sampling_m
Esempio n. 15
0
    def occulter(self, wf):

        n = int(proper.prop_get_gridsize(wf))
        ofst = 0  # no offset
        ramp_sign = 1  # sign of charge is positive
        ramp_oversamp = 11.  # vortex is oversampled for a better discretization

        # f_lens = tp.f_lens #conf['F_LENS']
        # diam = tp.diam#conf['DIAM']
        charge = 2  #conf['CHARGE']
        pixelsize = 5  #conf['PIXEL_SCALE']
        Debug_print = False  #conf['DEBUG_PRINT']

        coron_temp = os.path.join(iop.testdir, 'coron_maps/')
        if not os.path.exists(coron_temp):
            os.mkdir(coron_temp)

        if charge != 0:
            wavelength = proper.prop_get_wavelength(wf)
            gridsize = proper.prop_get_gridsize(wf)
            beam_ratio = pixelsize * 4.85e-9 / (wavelength / tp.entrance_d)
            # dprint((wavelength,gridsize,beam_ratio))
            calib = str(charge) + str('_') + str(int(
                beam_ratio * 100)) + str('_') + str(gridsize)
            my_file = str(coron_temp + 'zz_perf_' + calib + '_r.fits')

            if (os.path.isfile(my_file) == True):
                if (Debug_print == True):
                    print("Charge ", charge)
                vvc = self.readfield(
                    coron_temp,
                    'zz_vvc_' + calib)  # read the theoretical vortex field
                vvc = proper.prop_shift_center(vvc)
                scale_psf = wf._wfarr[0, 0]
                psf_num = self.readfield(coron_temp, 'zz_psf_' +
                                         calib)  # read the pre-vortex field
                psf0 = psf_num[0, 0]
                psf_num = psf_num / psf0 * scale_psf
                perf_num = self.readfield(
                    coron_temp,
                    'zz_perf_' + calib)  # read the perfect-result vortex field
                perf_num = perf_num / psf0 * scale_psf
                wf._wfarr = (
                    wf._wfarr - psf_num
                ) * vvc + perf_num  # the wavefront takes into account the real pupil with the perfect-result vortex field

            else:  # CAL==1: # create the vortex for a perfectly circular pupil
                if (Debug_print == True):
                    dprint(f"Vortex Charge= {charge}")

                f_lens = 200.0 * tp.entrance_d
                wf1 = proper.prop_begin(tp.entrance_d, wavelength, gridsize,
                                        beam_ratio)
                proper.prop_circular_aperture(wf1, tp.entrance_d / 2)
                proper.prop_define_entrance(wf1)
                proper.prop_propagate(wf1, f_lens,
                                      'inizio')  # propagate wavefront
                proper.prop_lens(
                    wf1, f_lens,
                    'focusing lens vortex')  # propagate through a lens
                proper.prop_propagate(wf1, f_lens, 'VC')  # propagate wavefront

                self.writefield(coron_temp, 'zz_psf_' + calib,
                                wf1.wfarr)  # write the pre-vortex field
                nramp = int(n * ramp_oversamp)  # oversamp
                # create the vortex by creating a matrix (theta) representing the ramp (created by atan 2 gradually varying matrix, x and y)
                y1 = np.ones((nramp, ), dtype=np.int)
                y2 = np.arange(0, nramp,
                               1.) - (nramp / 2) - int(ramp_oversamp) / 2
                y = np.outer(y2, y1)
                x = np.transpose(y)
                theta = np.arctan2(y, x)
                x = 0
                y = 0
                vvc_tmp = np.exp(1j * (ofst + ramp_sign * charge * theta))
                theta = 0
                vvc_real_resampled = cv2.resize(
                    vvc_tmp.real, (0, 0),
                    fx=1 / ramp_oversamp,
                    fy=1 / ramp_oversamp,
                    interpolation=cv2.INTER_LINEAR
                )  # scale the pupil to the pupil size of the simualtions
                vvc_imag_resampled = cv2.resize(
                    vvc_tmp.imag, (0, 0),
                    fx=1 / ramp_oversamp,
                    fy=1 / ramp_oversamp,
                    interpolation=cv2.INTER_LINEAR
                )  # scale the pupil to the pupil size of the simualtions
                vvc = np.array(vvc_real_resampled, dtype=complex)
                vvc.imag = vvc_imag_resampled
                vvcphase = np.arctan2(vvc.imag,
                                      vvc.real)  # create the vortex phase
                vvc_complex = np.array(np.zeros((n, n)), dtype=complex)
                vvc_complex.imag = vvcphase
                vvc = np.exp(vvc_complex)
                vvc_tmp = 0.
                self.writefield(coron_temp, 'zz_vvc_' + calib,
                                vvc)  # write the theoretical vortex field

                proper.prop_multiply(wf1, vvc)
                proper.prop_propagate(wf1, f_lens, 'OAP2')
                proper.prop_lens(wf1, f_lens)
                proper.prop_propagate(wf1, f_lens, 'forward to Lyot Stop')
                proper.prop_circular_obscuration(
                    wf1, 1.,
                    NORM=True)  # null the amplitude iside the Lyot Stop
                proper.prop_propagate(wf1, -f_lens)  # back-propagation
                proper.prop_lens(wf1, -f_lens)
                proper.prop_propagate(wf1, -f_lens)
                self.writefield(
                    coron_temp, 'zz_perf_' + calib,
                    wf1.wfarr)  # write the perfect-result vortex field

                vvc = self.readfield(coron_temp, 'zz_vvc_' + calib)
                vvc = proper.prop_shift_center(vvc)
                scale_psf = wf._wfarr[0, 0]
                psf_num = self.readfield(coron_temp, 'zz_psf_' +
                                         calib)  # read the pre-vortex field
                psf0 = psf_num[0, 0]
                psf_num = psf_num / psf0 * scale_psf
                perf_num = self.readfield(
                    coron_temp,
                    'zz_perf_' + calib)  # read the perfect-result vortex field
                perf_num = perf_num / psf0 * scale_psf
                wf._wfarr = (
                    wf._wfarr - psf_num
                ) * vvc + perf_num  # the wavefront takes into account the real pupil with the perfect-result vortex field

        return wf
Esempio n. 16
0
def fp_mask(wf, mode='RAVC', focal=660, verbose=False, **conf):

    # case 1: vortex coronagraphs
    if mode in ['CVC', 'RAVC']:
        if verbose is True:
            print('Apply Vortex phase mask')                        
        # update conf
        conf.update(focal=focal)
        # load vortex calibration files: 
        #   conf['psf_num'], conf['vvc'], conf['perf_num']
        conf = vortex_init(verbose=verbose, **conf)
        # propagate to vortex
        lens(wf, focal)
        # apply vortex
        scale_psf = wf._wfarr[0,0]/conf['psf_num'][0,0]
        wf_corr = (conf['psf_num']*conf['vvc'] - conf['perf_num'])*scale_psf
        wf._wfarr = wf._wfarr*conf['vvc'] - wf_corr
        # propagate to lyot stop
        lens(wf, focal)

    # TODO: cleanup CLC
    
    # case 2: classical Lyot
    elif mode in ['CLC']:
        if verbose is True:
            print('Apply Classical Lyot mask\n')        
        f_lens = conf['focal']
        beam_ratio = conf['beam_ratio']
        CLC_diam = conf['CLC_diam'] # classical lyot diam in lam/D (default to 4)
        gridsize = conf['gridsize']
        tmp_dir = conf['temp_dir']
        
        # propagate to lyot mask
        lens(wf, conf['focal'])
        
        # create or load the classical Lyot mask
        calib = str(CLC_diam)+str('_')+str(int(beam_ratio*100))+str('_')+str(gridsize)
        my_file = os.path.join(tmp_dir, 'clc_'+calib+'.fits')
        if not os.path.isfile(my_file):
            # calculate exact size of Lyot mask diameter, in pixels
            Dmask = CLC_diam/beam_ratio
            # oversample the Lyot mask (round up)
            samp = 100
            ndisk = int(samp*np.ceil(Dmask))
            ndisk = ndisk + 1 if not ndisk % 2 else ndisk # must be odd
            # find center
            cdisk = int((ndisk - 1)/2)
            # calculate the distances to center
            xy = range(-cdisk, cdisk + 1)
            x,y = np.meshgrid(xy, xy)
            dist = np.sqrt(x**2 + y**2)
            # create the Lyot mask
            mask = np.zeros((ndisk, ndisk))
            mask[np.where(dist > samp*Dmask/2)] = 1
            # resize to Lyot mask real size, and pad with ones
            mask = resize_img(mask, int(ndisk/samp))
            mask = pad_img(mask, gridsize, 1)
            # write mask
            fits.writeto(my_file, mask)
        else:
            mask = fits.getdata(my_file)
        
        # apply lyot mask
        mask = proper.prop_shift_center(mask)
        wf._wfarr.real *= mask
        
        # propagate to lyot stop
        lens(wf, conf['focal'])

    return wf
Esempio n. 17
0
def convert_to_wfo(image, wfo):
    wf_temp = copy(wfo)
    wf_temp.wfarr = proper.prop_shift_center(image + 0j)

    return wf_temp
Esempio n. 18
0
def apodizer(wf,
             mode='RAVC',
             ravc_t=0.8,
             ravc_r=0.6,
             ravc_misalign=None,
             ngrid=1024,
             npupil=285,
             file_ravc_amp='',
             file_ravc_phase='',
             margin=50,
             get_amp=False,
             get_phase=False,
             verbose=False,
             **conf):
    ''' Create a wavefront object at the entrance pupil plane. 
    The pupil is either loaded from a fits file, or created using 
    pupil parameters.
    Can also select only one petal and mask the others.

    wf: WaveFront
        PROPER wavefront object
    mode: str
        HCI mode
    ravc_t: float
        RA transmittance
    ravc_r: float
        RA radius
    ravc_misalign: list of float
        RA misalignment
    ngrid: int
        number of pixels of the wavefront array
    npupil: int
        number of pixels of the pupil
    file_ravc_amp: str
    file_ravc_phase: str 
        ring apodizer files (optional)
    
    '''

    if mode in ['RAVC']:

        # load apodizer from files if provided
        if os.path.isfile(file_ravc_amp) and os.path.isfile(file_ravc_phase):
            if verbose is True:
                print('Load ring apodizer from files\n')
            # get amplitude and phase data
            RAVC_amp = fits.getdata(file_ravc_amp)
            RAVC_phase = fits.getdata(file_ravc_phase)
            # resize to npupil
            RAVC_amp = impro.resize_img(RAVC_amp, npupil)
            RAVC_phase = impro.resize_img(RAVC_phase, npupil)
            # pad with zeros to match PROPER gridsize
            RAVC_amp = impro.pad_img(RAVC_amp, ngrid)
            RAVC_phase = impro.pad_img(RAVC_phase, ngrid)
            # build complex apodizer
            apo = RAVC_amp * np.exp(1j * RAVC_phase)

        # or else, define the apodizer as a ring (with % misalignments)
        else:
            # RAVC misalignments
            ravc_misalign = [
                0, 0, 0, 0, 0, 0
            ] if ravc_misalign is None else list(ravc_misalign)
            dx_amp, dy_amp, dz_amp = ravc_misalign[0:3]
            dx_phase, dy_phase, dz_phase = ravc_misalign[3:6]
            # create apodizer
            apo = circular_apodization(wf, ravc_r, 1., ravc_t, xc=dx_amp, \
                yc=dy_amp, NORM=True)
            apo = proper.prop_shift_center(apo)
            if verbose is True:
                print('Create ring apodizer')
                print('   ravc_t=%3.4f, ravc_r=%3.4f'\
                    %(ravc_t, ravc_r))
                print('   ravc_misalign=%s' % ravc_misalign)
                print('')

        # multiply the loaded apodizer
        proper.prop_multiply(wf, apo)

        # get the apodizer amplitude and phase for output
        apo_amp = impro.crop_img(proper.prop_get_amplitude(wf), npupil,\
                margin) if get_amp is True else None
        apo_phase = impro.crop_img(proper.prop_get_phase(wf), npupil,\
                margin) if get_phase is True else None

        return wf, apo_amp, apo_phase

    else:  # no ring apodizer
        return wf, None, None
Esempio n. 19
0
def simple_telescope(wavelength, gridsize):

    # Define entrance aperture diameter and other quantities
    d_objective = 5.0  # objective diameter in meters
    fl_objective = 20.0 * d_objective  # objective focal length in meters
    fl_eyepiece = 0.021  # eyepiece focal length
    fl_eye = 0.022  # human eye focal length
    beam_ratio = 0.3  # initial beam width/grid width

    # Define the wavefront
    wfo = proper.prop_begin(d_objective, wavelength, gridsize, beam_ratio)

    # print d_objective, wavelength, gridsize, beam_ratio
    # Define a circular aperture
    proper.prop_circular_aperture(wfo, d_objective / 2)

    # proper.prop_propagate(wfo, fl_objective)
    # proper.prop_propagate(wfo, fl_objective)
    # proper.prop_propagate(wfo, fl_objective)

    # plt.imshow(proper.prop_get_amplitude(wfo))
    # plt.show()

    # Define entrance
    proper.prop_define_entrance(wfo)
    # plt.imshow(proper.prop_get_amplitude(wfo))
    # plt.show()

    # proper.prop_propagate(wfo, fl_objective+fl_eyepiece, "eyepiece")
    # # plt.imshow(proper.prop_get_amplitude(wfo))
    # # plt.show()
    #
    # proper.prop_propagate(wfo, fl_objective+fl_eyepiece, "eyepiece")
    # plt.imshow(proper.prop_get_amplitude(wfo))
    # plt.show()
    #
    # proper.prop_propagate(wfo, fl_objective+fl_eyepiece, "eyepiece")
    # plt.imshow(proper.prop_get_amplitude(wfo))
    # plt.show()

    # Define a lens
    proper.prop_lens(wfo, fl_objective, "objective")
    # plt.imshow(proper.prop_get_amplitude(wfo))
    # plt.show()
    # Propagate the wavefront
    proper.prop_propagate(wfo, fl_objective + fl_eyepiece, "eyepiece")
    # plt.imshow(proper.prop_get_amplitude(wfo))
    # plt.show()
    # Define another lens
    proper.prop_lens(wfo, fl_eyepiece, "eyepiece")
    # plt.imshow(proper.prop_get_amplitude(wfo))
    # plt.show()
    exit_pupil_distance = fl_eyepiece / (1 - fl_eyepiece /
                                         (fl_objective + fl_eyepiece))
    proper.prop_propagate(wfo, exit_pupil_distance, "exit pupil at eye lens")
    # quicklook_wf(wfo)
    # plt.imshow(proper.prop_get_amplitude(wfo))
    # plt.show()
    proper.prop_lens(wfo, fl_eye, "eye")
    proper.prop_propagate(wfo, fl_eye, "retina")
    # plt.imshow(proper.prop_get_amplitude(wfo))
    # plt.show()
    quicklook_wf(wfo)
    phase_map = proper.prop_get_phase(wfo)
    amp_map = proper.prop_get_amplitude(wfo)
    # quicklook_im(phase_map)

    amp_map[80:100, 80:100] = 0
    quicklook_im(amp_map, logAmp=True)

    import numpy as np
    wfo.wfarr = proper.prop_shift_center(amp_map * np.cos(phase_map) +
                                         1j * amp_map * np.sin(phase_map))
    # quicklook_wf(wf_array[iw,0])
    proper.prop_propagate(wfo, fl_eye, "retina")
    proper.prop_lens(wfo, fl_eye, "eye")
    quicklook_wf(wfo)

    # End
    (wfo, sampling) = proper.prop_end(wfo)

    return (wfo, sampling)
Esempio n. 20
0
def lyotmask_init(lyotmask_calib='',
                  dir_temp='',
                  clc_diam=80,
                  pscale=5.47,
                  magnification=100,
                  ngrid=1024,
                  beam_ratio=0.26,
                  verbose=False,
                  **conf):
    '''
    
    Creates/writes classical lyot masks, or loads them if files 
    already exist.
    The following parameters will be added to conf: 
        lyotmask_calib, lyotmask
    
    Returns: conf (updated and sorted)

    '''

    # update conf with local variables (remove unnecessary)
    conf.update(locals())
    [conf.pop(key) for key in ['conf', 'verbose'] if key in conf]

    # check if lyot mask already loaded for this calib
    calib = 'lyotmask_%s_%s_%3.4f' % (clc_diam, ngrid, beam_ratio)
    if lyotmask_calib == calib:
        return conf

    else:
        # check for existing file
        filename = os.path.join(dir_temp, '%s.fits' % calib)
        if os.path.isfile(filename):
            if verbose is True:
                print('   loading lyot mask')
            lyotmask = fits.getdata(os.path.join(dir_temp, filename))

        # create file
        else:
            if verbose is True:
                print("   writing lyot mask")
            # calculate the size (in pixels) of the lyot mask,
            # rounded up to an odd value
            nmask = int(np.ceil(clc_diam / pscale))
            nmask += 1 - nmask % 2
            # create a magnified lyot mask
            rmask = clc_diam / pscale / nmask
            r, t = polar_coord(nmask * magnification)
            lyotmask = np.zeros(np.shape(r))
            lyotmask[r > rmask] = 1
            # resize and pad with ones to amtch ngrid
            lyotmask = pad_img(resize_img(lyotmask, nmask), ngrid, 1)
            # save as fits file
            fits.writeto(os.path.join(dir_temp, filename),
                         np.float32(lyotmask),
                         overwrite=True)

        # shift the lyotmask amplitude
        lyotmask = proper.prop_shift_center(lyotmask)
        # add lyotmask amplitude at the end of conf
        conf = {k: v for k, v in sorted(conf.items())}
        conf.update(lyotmask_calib=calib, lyotmask=lyotmask)

        if verbose is True:
            print('   clc_diam=%s, ngrid=%s, beam_ratio=%3.4f'%\
                (clc_diam, ngrid, beam_ratio))

        return conf
Esempio n. 21
0
def dummy_telescope(lmda, grid_size, kwargs):
    """
    propagates instantaneous complex E-field thru Subaru from the DM through SCExAO

    uses PyPROPER3 to generate the complex E-field at the pupil plane, then propagates it through SCExAO 50x50 DM,
        then coronagraph, to the focal plane
    :returns spectral cube at instantaneous time in the focal_plane()
    """
    # print("Propagating Broadband Wavefront Through Subaru")

    # Initialize the Wavefront in Proper
    wfo = proper.prop_begin(entrance_d, lmda, grid_size, beam_ratio)

    # Defines aperture (baffle-before primary)
    proper.prop_circular_aperture(wfo, entrance_d / 2)
    proper.prop_define_entrance(wfo)  # normalizes abs intensity

    # SCExAO Reimaging 1
    proper.prop_lens(wfo, fl_SxOAPG)
    proper.prop_propagate(wfo, fl_SxOAPG * 2)  # move to second pupil

    ########################################
    # Import/Apply Actual DM Map
    # #######################################
    plot_flag = False
    if kwargs['verbose'] and kwargs['ix'] == 0:
        plot_flag = True
    dm_map = kwargs['map']
    # flat = proper.prop_zernikes(wfo, [2, 3], np.array([5, 1]))  # zernike[2,3] = x,y tilt
    # adding a tilt for shits and giggles
    # proper.prop_propagate(wfo, fl_SxOAPG)  # from tweeter-DM to OAP2
    errormap(wfo,
             dm_map,
             SAMPLING=dm_pitch,
             MIRROR_SURFACE=True,
             MICRONS=True,
             BR=beam_ratio,
             PLOT=plot_flag)  # WAVEFRONT=True
    # errormap(wfo, dm_map, SAMPLING=dm_pitch, AMPLITUDE=True, BR=beam_ratio, PLOT=plot_flag)  # WAVEFRONT=True
    # proper.prop_circular_aperture(wfo, entrance_d/2)

    if kwargs['verbose'] and kwargs['ix'] == 0:
        fig, subplot = plt.subplots(nrows=1, ncols=2, figsize=(12, 5))
        ax1, ax2 = subplot.flatten()
        fig.suptitle('SCExAO Model WFO after errormap',
                     fontweight='bold',
                     fontsize=14)
        # ax.imshow(dm_map, interpolation='none')
        ax1.imshow(np.abs(proper.prop_shift_center(wfo.wfarr))**2,
                   interpolation='none')
        ax1.set_title('Amplitude')
        ax2.imshow(np.angle(proper.prop_shift_center(wfo.wfarr)),
                   interpolation='none',
                   vmin=-2 * np.pi,
                   vmax=2 * np.pi)  # , cmap='hsv'
        ax2.set_title('Phase')
    # ------------------------------------------------
    # proper.prop_propagate(wfo, fl_SxOAPG)  # from tweeter-DM to OAP2

    # SCExAO Reimaging 2
    proper.prop_lens(wfo, fl_SxOAPG)
    proper.prop_propagate(wfo,
                          fl_SxOAPG)  # focus at exit of DM telescope system
    proper.prop_lens(wfo, fl_SxOAPG)
    proper.prop_propagate(wfo,
                          fl_SxOAPG)  # focus at exit of DM telescope system
    # ########################################
    # # Focal Plane
    # # #######################################
    # Check Sampling in focal plane
    # shifts wfo from Fourier Space (origin==lower left corner) to object space (origin==center)
    # proper.prop_shift_center(wfo.wfarr)
    # wf, samp = proper.prop_end(wfo, NoAbs=True)
    wf = proper.prop_shift_center(wfo.wfarr)
    samp = proper.prop_get_sampling(wfo)
    # smp_asec = proper.prop_get_sampling_arcsec(wfo)

    if kwargs['verbose'] and kwargs['ix'] == 0:
        fig, ax = plt.subplots(nrows=1, ncols=1)
        fig.suptitle('SCExAO Model Focal Plane',
                     fontweight='bold',
                     fontsize=14)
        ax.imshow(
            np.abs(wf)**2,
            interpolation='none',
            norm=LogNorm(
                vmin=1e-7,
                vmax=1e-2))  # np.abs(proper.prop_shift_center(wfo.wfarr))**2
    #
    # if kwargs['verbose'] and kwargs['ix']==0:
    #     print(f"\n\tFocal Plane\n"
    #           f"sampling at focal plane is {smp_asec * 1e3:.4f} mas\n"
    #           f"\tfull FOV is {smp_asec * grid_size * 1e3:.2f} mas")
    #     s_rad = proper.prop_get_sampling_radians(wfo)
    #     print(f"sampling at focal plane is {s_rad * 1e6:.6f} urad")

    # print(f"Finished simulation")

    return wf, samp
Esempio n. 22
0
def vortex(wfo, CAL, charge, f_lens, path, Debug_print):

    n = int(proper.prop_get_gridsize(wfo))
    ofst = 0  # no offset
    ramp_sign = 1  #sign of charge is positive
    #sampling = n
    ramp_oversamp = 11.  # vortex is oversampled for a better discretization

    if charge != 0:
        if CAL == 1:  # create the vortex for a perfectly circular pupil
            if (Debug_print == True):
                print("CAL:1, charge ", charge)
            writefield(path, 'zz_psf', wfo.wfarr)  # write the pre-vortex field
            nramp = int(n * ramp_oversamp)  #oversamp
            # create the vortex by creating a matrix (theta) representing the ramp (created by atan 2 gradually varying matrix, x and y)
            y1 = np.ones((nramp, ), dtype=np.int)
            y2 = np.arange(0, nramp, 1.) - (nramp / 2) - int(ramp_oversamp) / 2
            y = np.outer(y2, y1)
            x = np.transpose(y)
            theta = np.arctan2(y, x)
            x = 0
            y = 0
            #vvc_tmp_complex = np.array(np.zeros((nramp,nramp)), dtype=complex)
            #vvc_tmp_complex.imag = ofst + ramp_sign*charge*theta
            #vvc_tmp = np.exp(vvc_tmp_complex)
            vvc_tmp = np.exp(1j * (ofst + ramp_sign * charge * theta))
            theta = 0
            vvc_real_resampled = cv2.resize(
                vvc_tmp.real, (0, 0),
                fx=1 / ramp_oversamp,
                fy=1 / ramp_oversamp,
                interpolation=cv2.INTER_LINEAR
            )  # scale the pupil to the pupil size of the simualtions
            vvc_imag_resampled = cv2.resize(
                vvc_tmp.imag, (0, 0),
                fx=1 / ramp_oversamp,
                fy=1 / ramp_oversamp,
                interpolation=cv2.INTER_LINEAR
            )  # scale the pupil to the pupil size of the simualtions
            vvc = np.array(vvc_real_resampled, dtype=complex)
            vvc.imag = vvc_imag_resampled
            vvcphase = np.arctan2(vvc.imag,
                                  vvc.real)  # create the vortex phase
            vvc_complex = np.array(np.zeros((n, n)), dtype=complex)
            vvc_complex.imag = vvcphase
            vvc = np.exp(vvc_complex)
            vvc_tmp = 0.
            writefield(path, 'zz_vvc',
                       vvc)  # write the theoretical vortex field
            wfo0 = wfo
            proper.prop_multiply(wfo, vvc)
            proper.prop_propagate(wfo, f_lens, 'OAP2')
            proper.prop_lens(wfo, f_lens)
            proper.prop_propagate(wfo, f_lens, 'forward to Lyot Stop')
            proper.prop_circular_obscuration(
                wfo, 1., NORM=True)  # null the amplitude iside the Lyot Stop
            proper.prop_propagate(wfo, -f_lens)  # back-propagation
            proper.prop_lens(wfo, -f_lens)
            proper.prop_propagate(wfo, -f_lens)
            writefield(path, 'zz_perf',
                       wfo.wfarr)  # write the perfect-result vortex field
            wfo = wfo0
        else:
            if (Debug_print == True):
                print("CAL:0, charge ", charge)
            vvc = readfield(path,
                            'zz_vvc')  # read the theoretical vortex field
            vvc = proper.prop_shift_center(vvc)
            scale_psf = wfo._wfarr[0, 0]
            psf_num = readfield(path, 'zz_psf')  # read the pre-vortex field
            psf0 = psf_num[0, 0]
            psf_num = psf_num / psf0 * scale_psf
            perf_num = readfield(
                path, 'zz_perf')  # read the perfect-result vortex field
            perf_num = perf_num / psf0 * scale_psf
            wfo._wfarr = (
                wfo._wfarr - psf_num
            ) * vvc + perf_num  # the wavefront takes into account the real pupil with the perfect-result vortex field

    return
Esempio n. 23
0
def vortex_init(vortex_calib='',
                dir_temp='',
                diam_ext=37,
                lam=3.8,
                ngrid=1024,
                beam_ratio=0.26,
                focal=660,
                vc_charge=2,
                verbose=False,
                **conf):
    '''
    
    Creates/writes vortex back-propagation fitsfiles, or loads them if files 
    already exist.
    The following parameters will be added to conf: 
        vortex_calib, psf_num, perf_num, vvc
    
    Returns: conf (updated and sorted)

    '''

    # update conf with local variables (remove unnecessary)
    conf.update(locals())
    [conf.pop(key) for key in ['conf', 'verbose'] if key in conf]

    # check if back-propagation params already loaded for this calib
    calib = 'vortex_%s_%s_%3.4f' % (vc_charge, ngrid, beam_ratio)
    if vortex_calib == calib:
        return conf

    else:
        # check for existing file
        filename = os.path.join(dir_temp, '%s.fits' % calib)
        if os.path.isfile(filename):
            if verbose is True:
                print('   loading vortex back-propagation params')
            data = fits.getdata(os.path.join(dir_temp, filename))
            # read the pre-vortex field
            psf_num = data[0] + 1j * data[1]
            # read the theoretical vortex field
            vvc = data[2] + 1j * data[3]
            # read the perfect-result vortex field
            perf_num = data[4] + 1j * data[5]

        # create files
        else:
            if verbose is True:
                print("   writing vortex back-propagation params")
            # create circular pupil
            wf_tmp = proper.prop_begin(diam_ext, lam, ngrid, beam_ratio)
            proper.prop_circular_aperture(wf_tmp, 1, NORM=True)
            # propagate to vortex
            lens(wf_tmp, focal)
            # pre-vortex field
            psf_num = deepcopy(wf_tmp.wfarr)
            # vortex phase ramp is oversampled for a better discretization
            ramp_oversamp = 11.
            nramp = int(ngrid * ramp_oversamp)
            start = -nramp / 2 - int(ramp_oversamp) / 2 + 0.5
            end = nramp / 2 - int(ramp_oversamp) / 2 + 0.5
            Vp = np.arange(start, end, 1.)
            # Pancharatnam Phase = arg<Vref,Vp> (horizontal input polarization)
            Vref = np.ones(Vp.shape)
            prod = np.outer(Vref, Vp)
            phiPan = np.angle(prod + 1j * prod.T)
            # vortex phase ramp exp(ilphi)
            ofst = 0
            ramp_sign = 1
            vvc_tmp = np.exp(1j * (ramp_sign * vc_charge * phiPan + ofst))
            vvc = np.array(impro.resize_img(vvc_tmp.real, ngrid),
                           dtype=complex)
            vvc.imag = impro.resize_img(vvc_tmp.imag, ngrid)
            phase_ramp = np.angle(vvc)
            # theoretical vortex field
            vvc_complex = np.array(np.zeros((ngrid, ngrid)), dtype=complex)
            vvc_complex.imag = phase_ramp
            vvc = np.exp(vvc_complex)
            # apply vortex
            proper.prop_multiply(wf_tmp, vvc)
            # null the amplitude inside the Lyot Stop, and back propagate
            lens(wf_tmp, focal)
            proper.prop_circular_obscuration(wf_tmp, 1., NORM=True)
            lens(wf_tmp, -focal)
            # perfect-result vortex field
            perf_num = deepcopy(wf_tmp.wfarr)
            # write all fields
            data = np.dstack((psf_num.real.T, psf_num.imag.T, vvc.real.T, vvc.imag.T,\
                perf_num.real.T, perf_num.imag.T)).T
            fits.writeto(os.path.join(dir_temp, filename),
                         np.float32(data),
                         overwrite=True)

        # shift the phase ramp
        vvc = proper.prop_shift_center(vvc)
        # add vortex back-propagation parameters at the end of conf
        conf = {k: v for k, v in sorted(conf.items())}
        conf.update(vortex_calib=calib,
                    psf_num=psf_num,
                    vvc=vvc,
                    perf_num=perf_num)

        if verbose is True:
            print('   vc_charge=%s, ngrid=%s, beam_ratio=%3.4f'%\
                (vc_charge, ngrid, beam_ratio))

        return conf
Esempio n. 24
0
def rotate_atmos(wf, it):
    time = it * ap.sample_time
    rotate_angle = tp.rot_rate * time
    wf.wfarr = proper.prop_shift_center(wf.wfarr)
    wf.wfarr = proper.prop_rotate(wf.wfarr, rotate_angle)
    wf.wfarr = proper.prop_shift_center(wf.wfarr)
Esempio n. 25
0
def scexao_model(lmda, grid_size, kwargs):
    """
    propagates instantaneous complex E-field thru Subaru from the DM through SCExAO

    uses PyPROPER3 to generate the complex E-field at the pupil plane, then propagates it through SCExAO 50x50 DM,
        then coronagraph, to the focal plane
    :returns spectral cube at instantaneous time in the focal_plane()
    """
    # print("Propagating Broadband Wavefront Through Subaru")

    # Initialize the Wavefront in Proper
    wfo = proper.prop_begin(entrance_d, lmda, grid_size, beam_ratio)

    # Defines aperture (baffle-before primary)
    proper.prop_circular_aperture(wfo, entrance_d / 2)
    proper.prop_define_entrance(wfo)  # normalizes abs intensity

    # Test Sampling
    if kwargs['verbose'] and kwargs['ix'] == 0:
        check1 = proper.prop_get_sampling(wfo)
        print(
            f"\n\tDM Pupil Plane\n"
            f"sampling at aperture is {check1 * 1e3:.4f} mm\n"
            f"Total Sim is {check1 * 1e3 * grid_size:.2f}x{check1 * 1e3 * grid_size:.2f} mm\n"
            f"Diameter of beam is {check1 * 1e3 * grid_size * beam_ratio:.4f} mm over {grid_size * beam_ratio} pix"
        )

    # SCExAO Reimaging 1
    proper.prop_lens(
        wfo, fl_SxOAPG)  # produces f#14 beam (approx exit beam of AO188)
    proper.prop_propagate(wfo, fl_SxOAPG * 2)  # move to second pupil
    if kwargs['verbose'] and kwargs['ix'] == 0:
        print(f"initial f# is {proper.prop_get_fratio(wfo):.2f}\n")

    ########################################
    # Import/Apply Actual DM Map
    # #######################################
    plot_flag = False
    if kwargs['verbose'] and kwargs['ix'] == 0:
        plot_flag = True

    dm_map = kwargs['map']
    errormap(wfo,
             dm_map,
             SAMPLING=dm_pitch,
             MIRROR_SURFACE=True,
             MASKING=True,
             BR=beam_ratio,
             PLOT=plot_flag)  # MICRONS=True

    if kwargs['verbose'] and kwargs['ix'] == 0:
        fig, subplot = plt.subplots(nrows=1, ncols=2, figsize=(12, 5))
        ax1, ax2 = subplot.flatten()
        fig.suptitle('SCExAO Model WFO after errormap',
                     fontweight='bold',
                     fontsize=14)
        ax1.imshow(proper.prop_get_amplitude(wfo), interpolation='none'
                   )  # np.abs(proper.prop_shift_center(wfo.wfarr))**2
        ax1.set_title('Amplitude')
        ax2.imshow(
            proper.prop_get_phase(wfo),
            interpolation=
            'none',  # np.angle(proper.prop_shift_center(wfo.wfarr))
            vmin=-1 * np.pi,
            vmax=1 * np.pi,
            cmap='hsv')  # , cmap='hsv'
        ax2.set_title('Phase')

    # ------------------------------------------------
    # SCExAO Reimaging 2
    proper.prop_lens(wfo, fl_SxOAPG)
    proper.prop_propagate(wfo,
                          fl_SxOAPG)  # focus at exit of DM telescope system
    proper.prop_lens(wfo, fl_SxOAPG)
    proper.prop_propagate(wfo,
                          fl_SxOAPG)  # focus at exit of DM telescope system

    # # Coronagraph
    SubaruPupil(wfo)  # focal plane mask
    # if kwargs['verbose'] and kwargs['ix']==0:
    #     fig, subplot = plt.subplots(nrows=1, ncols=2, figsize=(12, 5))
    #     ax1, ax2 = subplot.flatten()
    #     fig.suptitle('SCExAO Model WFO after FPM', fontweight='bold', fontsize=14)
    #     # ax.imshow(dm_map, interpolation='none')
    #     ax1.imshow(np.abs(proper.prop_shift_center(wfo.wfarr))**2, interpolation='none', norm=LogNorm(vmin=1e-7,vmax=1e-2))
    #     ax1.set_title('Amplitude')
    #     ax2.imshow(np.angle(proper.prop_shift_center(wfo.wfarr)), interpolation='none',
    #                vmin=-2*np.pi, vmax=2*np.pi, cmap='hsv')  # , cmap='hsv'
    #     ax2.set_title('Phase')
    proper.prop_propagate(wfo, fl_SxOAPG)
    proper.prop_lens(wfo, fl_SxOAPG)
    proper.prop_propagate(wfo, fl_SxOAPG)  # middle of 2f system
    proper.prop_circular_aperture(wfo, lyot_size, NORM=True)  # lyot stop
    proper.prop_propagate(wfo, fl_SxOAPG)  #
    proper.prop_lens(wfo, fl_SxOAPG)  # exit lens of gaussian telescope
    proper.prop_propagate(wfo, fl_SxOAPG)  # to focus

    # MEC Pickoff reimager.
    proper.prop_propagate(wfo, mec_parax_fl)  # to another pupil
    proper.prop_lens(
        wfo, mec_parax_fl)  # collimating lens, pupil size should be 8 mm
    proper.prop_propagate(wfo, mec1_fl +
                          .0142557)  # mec1_fl  .054  mec1_fl+.0101057
    # if kwargs['verbose'] and kwargs['ix']==0:
    #     current = proper.prop_get_beamradius(wfo)
    #     print(f'Beam Radius after SCExAO exit (at MEC foreoptics entrance) is {current*1e3:.3f} mm\n'
    #           f'current f# is {proper.prop_get_fratio(wfo):.2f}\n')

    # ##################################
    # MEC Optics Box
    # ###################################
    proper.prop_circular_aperture(wfo,
                                  0.00866)  # reading off the zemax diameter
    proper.prop_lens(wfo, mec1_fl)  # MEC lens 1
    proper.prop_propagate(wfo,
                          mec_l1_l2)  # there is a image plane at z=mec1_fl
    proper.prop_lens(wfo, mec2_fl)  # MEC lens 2 (tiny lens)
    proper.prop_propagate(wfo, mec_l2_l3)
    proper.prop_lens(wfo, mec3_fl)  # MEC lens 3
    proper.prop_propagate(wfo, mec3_fl,
                          TO_PLANE=False)  # , TO_PLANE=True mec_l3_focus

    # #######################################
    # Focal Plane
    # #######################################
    # Check Sampling in focal plane
    # shifts wfo from Fourier Space (origin==lower left corner) to object space (origin==center)
    # wf, samp = proper.prop_end(wfo, NoAbs=True)
    wf = proper.prop_shift_center(wfo.wfarr)
    wf = extract_center(wf, new_size=np.array(kwargs['psf_size']))
    samp = proper.prop_get_sampling(wfo)
    smp_asec = proper.prop_get_sampling_arcsec(wfo)

    if kwargs['verbose'] and kwargs['ix'] == 0:
        fig, subplot = plt.subplots(nrows=1, ncols=2, figsize=(12, 5))
        fig.subplots_adjust(left=0.08, hspace=.4, wspace=0.2)

        ax1, ax2 = subplot.flatten()
        fig.suptitle('SCExAO Model Focal Plane',
                     fontweight='bold',
                     fontsize=14)
        tic_spacing, tic_labels, axlabel = scale_lD(
            wfo, newsize=kwargs['psf_size'][0])
        tic_spacing[0] = tic_spacing[0] + 1  # hack for edge effects
        tic_spacing[-1] = tic_spacing[-1] - 1  # hack for edge effects

        im = ax1.imshow(
            np.abs(wf)**2,
            interpolation='none',
            norm=LogNorm(
                vmin=1e-7,
                vmax=1e-2))  # np.abs(proper.prop_shift_center(wfo.wfarr))**2
        ax1.set_xticks(tic_spacing)
        ax1.set_xticklabels(tic_labels)
        ax1.set_yticks(tic_spacing)
        ax1.set_yticklabels(tic_labels)
        ax1.set_ylabel(axlabel, fontsize=8)
        add_colorbar(im)

        im = ax2.imshow(np.angle(wf),
                        interpolation='none',
                        vmin=-np.pi,
                        vmax=np.pi,
                        cmap='hsv')
        ax2.set_xticks(tic_spacing)
        ax2.set_xticklabels(tic_labels)
        ax2.set_yticks(tic_spacing)
        ax2.set_yticklabels(tic_labels)
        ax2.set_ylabel(axlabel, fontsize=8)
        add_colorbar(im)

    if kwargs['verbose'] and kwargs['ix'] == 0:
        print(
            f"\nFocal Plane\n"
            f"sampling at focal plane is {samp*1e6:.1f} um ~= {smp_asec * 1e3:.4f} mas\n"
            f"\tfull FOV is {samp * kwargs['psf_size'][0] * 1e3:.2f} x {samp * kwargs['psf_size'][1] * 1e3:.2f} mm "
        )
        # s_rad = proper.prop_get_sampling_radians(wfo)
        # print(f"sampling at focal plane is {s_rad * 1e6:.6f} urad")
        print(f'final focal ratio is {proper.prop_get_fratio(wfo)}')

        print(f"Finished simulation")

    return wf, samp
Esempio n. 26
0
def apodization(wf, r_obstr, npupil, RAVC, phase_apodizer_file,
                amplitude_apodizer_file, apodizer_misalignment, Debug_print,
                Debug):

    n = int(proper.prop_get_gridsize(wf))

    if (RAVC == True):
        t1_opt = 1. - 1. / 4 * (
            r_obstr**2 + r_obstr * (math.sqrt(r_obstr**2 + 8.))
        )  # define the apodizer transmission [Mawet2013]
        R1_opt = (r_obstr / math.sqrt(1. - t1_opt)
                  )  # define the apodizer radius [Mawet2013]
        if (Debug_print == True):
            print("r1_opt: ", R1_opt)
            print("t1_opt: ", t1_opt)
        apodizer = circular_apodization(wf,
                                        R1_opt,
                                        1.,
                                        t1_opt,
                                        xc=apodizer_misalignment[0],
                                        yc=apodizer_misalignment[1],
                                        NORM=True)  # define the apodizer
        apodizer = proper.prop_shift_center(apodizer)

    if (isinstance(phase_apodizer_file, (list, tuple, np.ndarray)) == True):
        xc_pixels = int(apodizer_misalignment[3] * npupil)
        yc_pixels = int(apodizer_misalignment[4] * npupil)
        apodizer_pixels = (phase_apodizer_file.shape)[0]  ## fits file size
        scaling_factor = float(npupil) / float(
            apodizer_pixels
        )  ## scaling factor between the fits file size and the pupil size of the simulation
        if (Debug_print == True):
            print("scaling_factor: ", scaling_factor)
        apodizer_scale = cv2.resize(
            phase_apodizer_file.astype(np.float32), (0, 0),
            fx=scaling_factor,
            fy=scaling_factor,
            interpolation=cv2.INTER_LINEAR
        )  # scale the pupil to the pupil size of the simualtions
        if (Debug_print == True):
            print("apodizer_resample", apodizer_scale.shape)
        apodizer_large = np.zeros(
            (n, n))  # define an array of n-0s, where to insert the pupuil
        if (Debug_print == True):
            print("n: ", n)
            print("npupil: ", npupil)
        apodizer_large[
            int(n / 2) + 1 - int(npupil / 2) - 1 + xc_pixels:int(n / 2) + 1 +
            int(npupil / 2) + xc_pixels,
            int(n / 2) + 1 - int(npupil / 2) - 1 + yc_pixels:int(n / 2) + 1 +
            int(npupil / 2) +
            yc_pixels] = apodizer_scale  # insert the scaled pupil into the 0s grid
        apodizer = np.exp(1j * apodizer_large)

    if (isinstance(amplitude_apodizer_file,
                   (list, tuple, np.ndarray)) == True):
        xc_pixels = int(apodizer_misalignment[0] * npupil)
        yc_pixels = int(apodizer_misalignment[1] * npupil)
        apodizer_pixels = (amplitude_apodizer_file.shape)[0]  ## fits file size
        scaling_factor = float(npupil) / float(
            apodizer_pixels
        )  ## scaling factor between the fits file size and the pupil size of the simulation
        if (Debug_print == True):
            print("scaling_factor: ", scaling_factor)
        apodizer_scale = cv2.resize(
            amplitude_apodizer_file.astype(np.float32), (0, 0),
            fx=scaling_factor,
            fy=scaling_factor,
            interpolation=cv2.INTER_LINEAR
        )  # scale the pupil to the pupil size of the simualtions
        if (Debug_print == True):
            print("apodizer_resample", apodizer_scale.shape)
        apodizer_large = np.zeros(
            (n, n))  # define an array of n-0s, where to insert the pupuil
        if (Debug_print == True):
            print("n: ", n)
            print("npupil: ", npupil)
        apodizer_large[
            int(n / 2) + 1 - int(npupil / 2) - 1 + xc_pixels:int(n / 2) + 1 +
            int(npupil / 2) + xc_pixels,
            int(n / 2) + 1 - int(npupil / 2) - 1 + yc_pixels:int(n / 2) + 1 +
            int(npupil / 2) +
            yc_pixels] = apodizer_scale  # insert the scaled pupil into the 0s grid
        apodizer = apodizer_large

    proper.prop_multiply(wf, apodizer)

    return
Esempio n. 27
0
def vortex(wfo, charge, f_lens, diam, pixelsize, Debug_print=False):

    n = int(proper.prop_get_gridsize(wfo))
    ofst = 0  # no offset
    ramp_sign = 1  #sign of charge is positive
    ramp_oversamp = 11.  # vortex is oversampled for a better discretization

    if charge != 0:
        wavelength = proper.prop_get_wavelength(wfo)
        gridsize = proper.prop_get_gridsize(wfo)
        beam_ratio = pixelsize * 4.85e-9 / (wavelength / diam)
        calib = str(charge) + str('_') + str(int(
            beam_ratio * 100)) + str('_') + str(gridsize)
        my_file = str(tmp_dir + 'zz_perf_' + calib + '_r.fits')

        proper.prop_propagate(wfo, f_lens, 'inizio')  # propagate wavefront
        proper.prop_lens(wfo, f_lens,
                         'focusing lens vortex')  # propagate through a lens
        proper.prop_propagate(wfo, f_lens, 'VC')  # propagate wavefront

        if (os.path.isfile(my_file) == True):
            if (Debug_print == True):
                print("Charge ", charge)
            vvc = readfield(tmp_dir, 'zz_vvc_' +
                            calib)  # read the theoretical vortex field
            vvc = proper.prop_shift_center(vvc)
            scale_psf = wfo._wfarr[0, 0]
            psf_num = readfield(tmp_dir,
                                'zz_psf_' + calib)  # read the pre-vortex field
            psf0 = psf_num[0, 0]
            psf_num = psf_num / psf0 * scale_psf
            perf_num = readfield(tmp_dir, 'zz_perf_' +
                                 calib)  # read the perfect-result vortex field
            perf_num = perf_num / psf0 * scale_psf
            wfo._wfarr = (
                wfo._wfarr - psf_num
            ) * vvc + perf_num  # the wavefront takes into account the real pupil with the perfect-result vortex field

        else:  # CAL==1: # create the vortex for a perfectly circular pupil
            if (Debug_print == True):
                print("Charge ", charge)

            wfo1 = proper.prop_begin(diam, wavelength, gridsize, beam_ratio)
            proper.prop_circular_aperture(wfo1, diam / 2)
            proper.prop_define_entrance(wfo1)
            proper.prop_propagate(wfo1, f_lens,
                                  'inizio')  # propagate wavefront
            proper.prop_lens(
                wfo1, f_lens,
                'focusing lens vortex')  # propagate through a lens
            proper.prop_propagate(wfo1, f_lens, 'VC')  # propagate wavefront

            writefield(tmp_dir, 'zz_psf_' + calib,
                       wfo1.wfarr)  # write the pre-vortex field
            nramp = int(n * ramp_oversamp)  #oversamp
            # create the vortex by creating a matrix (theta) representing the ramp (created by atan 2 gradually varying matrix, x and y)
            y1 = np.ones((nramp, ), dtype=np.int)
            y2 = np.arange(0, nramp, 1.) - (nramp / 2) - int(ramp_oversamp) / 2
            y = np.outer(y2, y1)
            x = np.transpose(y)
            theta = np.arctan2(y, x)
            x = 0
            y = 0
            vvc_tmp = np.exp(1j * (ofst + ramp_sign * charge * theta))
            theta = 0
            vvc_real_resampled = cv2.resize(
                vvc_tmp.real, (0, 0),
                fx=1 / ramp_oversamp,
                fy=1 / ramp_oversamp,
                interpolation=cv2.INTER_LINEAR
            )  # scale the pupil to the pupil size of the simualtions
            vvc_imag_resampled = cv2.resize(
                vvc_tmp.imag, (0, 0),
                fx=1 / ramp_oversamp,
                fy=1 / ramp_oversamp,
                interpolation=cv2.INTER_LINEAR
            )  # scale the pupil to the pupil size of the simualtions
            vvc = np.array(vvc_real_resampled, dtype=complex)
            vvc.imag = vvc_imag_resampled
            vvcphase = np.arctan2(vvc.imag,
                                  vvc.real)  # create the vortex phase
            vvc_complex = np.array(np.zeros((n, n)), dtype=complex)
            vvc_complex.imag = vvcphase
            vvc = np.exp(vvc_complex)
            vvc_tmp = 0.
            writefield(tmp_dir, 'zz_vvc_' + calib,
                       vvc)  # write the theoretical vortex field

            proper.prop_multiply(wfo1, vvc)
            proper.prop_propagate(wfo1, f_lens, 'OAP2')
            proper.prop_lens(wfo1, f_lens)
            proper.prop_propagate(wfo1, f_lens, 'forward to Lyot Stop')
            proper.prop_circular_obscuration(
                wfo1, 1., NORM=True)  # null the amplitude iside the Lyot Stop
            proper.prop_propagate(wfo1, -f_lens)  # back-propagation
            proper.prop_lens(wfo1, -f_lens)
            proper.prop_propagate(wfo1, -f_lens)
            writefield(tmp_dir, 'zz_perf_' + calib,
                       wfo1.wfarr)  # write the perfect-result vortex field

            vvc = readfield(tmp_dir, 'zz_vvc_' + calib)
            vvc = proper.prop_shift_center(vvc)
            scale_psf = wfo._wfarr[0, 0]
            psf_num = readfield(tmp_dir,
                                'zz_psf_' + calib)  # read the pre-vortex field
            psf0 = psf_num[0, 0]
            psf_num = psf_num / psf0 * scale_psf
            perf_num = readfield(tmp_dir, 'zz_perf_' +
                                 calib)  # read the perfect-result vortex field
            perf_num = perf_num / psf0 * scale_psf
            wfo._wfarr = (
                wfo._wfarr - psf_num
            ) * vvc + perf_num  # the wavefront takes into account the real pupil with the perfect-result vortex field

        proper.prop_propagate(wfo, f_lens, "propagate to pupil reimaging lens")
        proper.prop_lens(wfo, f_lens, "apply pupil reimaging lens")
        proper.prop_propagate(wfo, f_lens, "lyot stop")

    return wfo
Esempio n. 28
0
def get_intensity(wf_array,
                  sp,
                  logAmp=True,
                  show=False,
                  save=True,
                  phase=False):

    if show == True:
        wfo = wf_array[0, 0]
        after_dm = proper.prop_get_amplitude(wfo)
        phase_afterdm = proper.prop_get_phase(wfo)

        fig = plt.figure(figsize=(14, 10))
        ax1 = plt.subplot2grid((3, 2), (0, 0), rowspan=2)
        ax2 = plt.subplot2grid((3, 2), (0, 1), rowspan=2)
        ax3 = plt.subplot2grid((3, 2), (2, 0))
        ax4 = plt.subplot2grid((3, 2), (2, 1))
        if logAmp:
            ax1.imshow(after_dm,
                       origin='lower',
                       cmap="YlGnBu_r",
                       norm=LogNorm())
        else:
            ax1.imshow(after_dm, origin='lower', cmap="YlGnBu_r")
        ax2.imshow(phase_afterdm, origin='lower',
                   cmap="YlGnBu_r")  #, vmin=-0.5, vmax=0.5)

        ax3.plot(after_dm[int(tp.grid_size / 2)])
        ax3.plot(np.sum(np.eye(tp.grid_size) * after_dm, axis=1))

        # plt.plot(np.sum(after_dm,axis=1)/after_dm[128,128])

        ax4.plot(phase_afterdm[int(tp.grid_size / 2)])
        # ax4.plot(np.sum(np.eye(tp.grid_size)*phase_afterdm,axis=1))
        plt.xlim([0, proper.prop_get_gridsize(wfo)])
        fig.set_tight_layout(True)

        plt.show()

    if save:
        shape = wf_array.shape
        ws = sp.get_ints['w']
        cs = sp.get_ints['c']

        int_maps = np.empty((0, tp.grid_size, tp.grid_size))
        for iw in ws:
            for iwf in cs:
                # int_maps.append(proper.prop_shift_center(np.abs(wf_array[iw, iwf].wfarr) ** 2))
                if phase:
                    int_map = proper.prop_get_phase(wf_array[iw, iwf])

                # int_map = proper.prop_shift_center(np.abs(wf_array[iw, iwf].wfarr) ** 2)
                else:
                    int_map = proper.prop_shift_center(
                        np.abs(wf_array[iw, iwf].wfarr)**2)
                int_maps = np.vstack((int_maps, [int_map]))
                # quicklook_im(int_map)#, logAmp=True)

        # int_maps = np.array(int_maps)
        # view_datacube(int_maps)
        import pickle, os
        if os.path.exists(iop.int_maps):
            # "with" statements are very handy for opening files.
            with open(iop.int_maps, 'rb') as rfp:
                # dprint(np.array(pickle.load(rfp)).shape)
                int_maps = np.vstack((int_maps, pickle.load(rfp)))

        with open(iop.int_maps, 'wb') as wfp:
            pickle.dump(int_maps, wfp, protocol=pickle.HIGHEST_PROTOCOL)
Esempio n. 29
0
def errormap(wf, dm_map, xshift=0., yshift=0., **kwargs):
    """Read in a surface, wavefront, or amplitude error map from a FITS file. 
    
    Map is assumed to be in meters of surface error. One (and only one) of the 
    MIRROR_SURFACE, WAVEFRONT, or AMPLITUDE switches must be specified in order 
    to properly apply the map to the wavefront.  For surface or wavefront error 
    maps, the map values are assumed to be in meters, unless the NM or MICRONS 
    switches are used to specify the units. The amplitude map must range 
    from 0 to 1.  The map will be interpolated to match the current wavefront 
    sampling if necessary.
    
    Parameters
    ----------
    wf : obj
        WaveFront class object
        
    dm_map : 2D numpy array
        the DM map in units of surface deformation
        
    xshify, yshift : float
        Amount to shift map (meters) in X,Y directions
    
    Returns
    -------
    DMAP : numpy ndarray
        Returns map (after any necessary resampling) if set.
    
    
    Other Parameters
    ----------------
    XC_MAP, YC_MAP : float
        Pixel coordinates of map center (Assumed n/2,n/2)
        
    SAMPLING : float
        Sampling of map in meters
        
    ROTATEMAP : float
        Degrees counter-clockwise to rotate map, after any resampling and 
        shifting
        
    MULTIPLY : float
        Multiplies the map by the specified factor
        
    MAGNIFY : float
        Spatially magnify the map by a factor of "constant" from its default
        size; do not use if SAMPLING is specified
        
    MIRROR_SURFACE : bool
        Indicates file contains a mirror surface height error map; It assumes a 
        positive value indicates a surface point higher than the mean surface.  
        The map will be multiplied by -2 to convert it to a wavefront map to 
        account for reflection and wavefront delay (a low region on the surface 
        causes a positive increase in the phase relative to the mean)
        
    WAVEFRONT : bool
        Indicates file contains a wavefront error map
        
    AMPLITUDE : bool
        Indicates file contains an amplitude error map
        
    NM or MICRONS : bool
        Indicates map values are in nanometers or microns. For surface or 
        wavefront maps only
        
    Raises
    ------
    SystemExit:
        If AMPLITUDE and (NM or MICRONS) parameters are input.
        
    SystemExit:
        If NM and MICRONS parameteres are input together. 
        
    ValueError:
        If map type is MIRROR_SURFACE, WAVEFRONT, or AMPLITUDE.
    """
    if ("AMPLITUDE" in kwargs and kwargs["AMPLITUDE"]) \
       and (("NM" in kwargs and kwargs["NM"]) \
       or ("MICRONS" in kwargs and kwargs["MICRONS"])):
        raise SystemExit(
            "ERRORMAP: Cannot specify NM or MICRON for an amplitude map")

    if ("NM" in kwargs and kwargs["NM"]) and \
       ("MICRONS" in kwargs and kwargs["MICRONS"]):
        raise SystemExit("ERRORMAP: Cannot specify both NM and MICRONS")

    if not "XC_MAP" in kwargs:
        s = dm_map.shape
        xc = s[
            0] // 2  # center of map read-in (xc should be 25 for SCExAO DM maps)
        yc = s[1] // 2
    else:
        xc = kwargs["XC_MAP"]
        yc = kwargs["YC_MAP"]

    # KD edit: try to get the dm map to apply only in regions of the beam
    n = proper.prop_get_gridsize(wf)  #
    new_sampling = proper.prop_get_sampling(
        wf)  #kwargs["SAMPLING"]  #*dm_map.shape[0]/npix_across_beam
    if new_sampling > (kwargs["SAMPLING"] + kwargs["SAMPLING"]*.1) or \
        new_sampling < (kwargs["SAMPLING"] - kwargs["SAMPLING"]*.1):
        dm_map = proper.prop_resamplemap(wf, dm_map, kwargs["SAMPLING"], 0, 0)
        dm_map = dm_map[n // 2:n // 2 + xc * 4, n // 2:n // 2 + xc * 4]
        # print(f'User-defined samping is {kwargs["SAMPLING"]:.6f} but proper wavefront has sampling of '
        #       f'{new_sampling:.6f}')
        warnings.warn(
            f'User-defined beam ratio does not produce aperture sampling consistent with SCExAO actuator '
            f'spacing. Resampling Map')

    # resample dm_map to size of beam in the simulation
    # grid = proper.prop_resamplemap(wf, dm_map, new_sampling, xc, yc, xshift, yshift)
    dmap = np.zeros((wf.wfarr.shape[0], wf.wfarr.shape[1]))
    r = dmap.shape
    xrc = r[0] // 2
    yrc = r[1] // 2
    dmap[xrc - xc * 2:xrc + xc * 2, yrc - yc * 2:yrc + yc * 2] = dm_map

    # Create mask to eliminate resampling artifacts outside of beam
    if ("MASKING" in kwargs and kwargs["MASKING"]):
        h, w = wf.wfarr.shape[:2]
        center = (int(w / 2), int(h / 2))
        radius = np.ceil(h * kwargs['BR'] / 2)  #
        # Making the Circular Boolean Mask
        Y, X = np.mgrid[:h, :w]
        dist_from_center = np.sqrt((X - center[0])**2 + (Y - center[1])**2)
        inds = dist_from_center <= radius
        # Applying the Mask to the dm_map
        mask = np.zeros_like(dmap)
        mask[inds] = 1
        dmap *= mask

    # Shift the center of dmap to 0,0
    dmap = proper.prop_shift_center(dmap)

    if kwargs['PLOT']:
        import matplotlib.pyplot as plt
        from matplotlib.colors import LogNorm, SymLogNorm

        fig, subplot = plt.subplots(nrows=1, ncols=2, figsize=(12, 5))
        ax1, ax2 = subplot.flatten()
        fig.suptitle(f'RTC DM Voltage Maps')

        ax1.imshow(
            dm_map, norm=SymLogNorm(1e-2)
        )  # LogNorm(vmin=np.min(dm_map),vmax=np.max(dm_map))  SymLogNorm(1e-2)
        ax1.set_title('DM Map Read In')
        ax2.imshow(proper.prop_shift_center(
            dmap))  #  , cmap='hsv' must shift the center because
        # proper assumes dmap center is 0,0, so we need to shift it back to plot properly
        ax2.set_title('DM Map in Center of Proper simulated beam')

    if "ROTATEMAP" in kwargs or "MAGNIFY" in kwargs:
        # readmap stores map with center at (0,0), so shift
        # before and after rotation
        dmap = proper.prop_shift_center(dmap)
        if "ROTATEMAP" in kwargs:
            dmap = proper.prop_rotate(dmap,
                                      kwargs["ROTATEMAP"],
                                      CUBIC=-0.5,
                                      MISSING=0.0)
        if "MAGNIFY" in kwargs:
            dmap = proper.prop_magnify(dmap, kwargs["MAGNIFY"], dmap.shape[0])
            dmap = proper.prop_shift_center(dmap)

    if ("MICRONS" in kwargs and kwargs["MICRONS"]):
        dmap *= 1.e-6

    if ("NM" in kwargs and kwargs["NM"]):
        dmap *= 1.e-9

    if "MULTIPLY" in kwargs:
        dmap *= kwargs["MULTIPLY"]

    i = complex(0., 1.)

    if ("MIRROR_SURFACE" in kwargs and kwargs["MIRROR_SURFACE"]):
        wf.wfarr *= np.exp(-4 * np.pi * i / wf.lamda * dmap)  # Krist version
    elif "WAVEFRONT" in kwargs:
        wf.wfarr *= np.exp(2 * np.pi * i / wf.lamda * dmap)
    elif "AMPLITUDE" in kwargs:
        wf.wfarr *= dmap
    else:
        raise ValueError(
            "ERRORMAP: Unspecified map type: Use MIRROR_SURFACE, WAVEFRONT, or AMPLITUDE"
        )

    # check1 = proper.prop_get_sampling(wf)
    # print(f"\n\tErrormap Sampling\n"
    #       f"sampling in errormap.py is {check1 * 1e3:.4f} mm\n")

    return dmap
Esempio n. 30
0
def propcustom_zernikes(a, zernike_num, zernike_val, eps=0., **kwargs):
    """
    Add Zernike-polynomial wavefront errors to current wavefront.
    
    Noll ordering is used and a circular system is assumed. An arbitrary number
    of Zernikes normalized for an unobscured circular region can be computed,
    but only the first 22 Zernikes can be computed normalized for a
    centrally-obscured region.
    
    
    Parameters
    ----------
    a : object
        WaveFront class object
        
    zernike_num : numpy ndarray
        Scalar or 1D array specifying which Zernike polynomials to include
        
    zernike_val : numpy ndarray
        Scalar or 1D array containing Zernike coefficients (in meters of RMS
        wavefront phase error or dimensionless RMS amplitude error) for Zernike
        polynomials indexed by "zernike_num".
        
    eps : float
        Central obscuration ratio (0.0-1.0); default is 0.0
        
    
    Returns
    -------
        None
        Adds wavefront errors to current wavefront array
        
    dmap : numpy ndarray
        Aberration map
        
    
    Other Parameters
    ----------------
    AMPLITUDE : bool
        Optional keyword that specifies that the Zernike values in "zernike_val"
        represent the wavefront RMS amplitude (rather than phase) variation.
        The current wavefront will be multipled by the generated map.
        
    NAME : str
        String containing name of surface that will be printed when executed.
        
    NO_APPLY : bool
        If set, the aberration map will be generated but will not be applied to
        the wavefront. This is useful if you just want to generate a map for
        your own use and modification (e.g. to create an error map for a multi-
        segmented system, each with its own aberration map).
        
    RADIUS : float
        Optional keyword specifying the radius to which the Zernike polynomials
        are normalized. If not specified, the pilot beam radius is used.
    
    CENTERING : str
        String containing the centering of the array. "interpixel" centers the
        array between pixels. Any other value gives pixel centering.
    
    
    Raises
    ------
    ValueError:
        Maximum index for an obscured Zernike polynomial is 22
    
    
    Notes
    -----
    The user specifies 1D arrays containing the Zernike polynomial coefficient
    indicies, the respective coefficients, and if an obstructed circular aperture
    the central obscuration ratio. A wavefront error map will be generated and
    added to the current wavefront.
    
    Zernike index and corresponding aberration for 1st 22 zernikes
    1 : Piston
    2 : X tilt
    3 : Y tilt
    4 : Focus
    5 : 45 degree astigmatism
    6 : 0 degree astigmatism
    7 : Y coma
    8 : X coma
    9 : Y clover (trefoil)
    10 : X clover (trefoil)
    11 : 3rd order spherical
    12 : 5th order 0 degree astig
    13 : 5th order 45 degree astig
    14 : X quadrafoil
    15 : Y quadrafoil
    16 : 5th order X coma
    17 : 5th order Y coma
    18 : 5th order X clover
    19 : 5th order Y clover
    20 : X pentafoil
    21 : Y pentafoil
    22 : 5th order spherical
    
    Update - JEK - Fixed use of ** instead of ** in obscured Zernikes
    Update - AR - Added CENTERING as keyword
    """
    zernike_num = np.asarray(zernike_num)
    zernike_val = np.asarray(zernike_val)
    n = proper.n
    
    if proper.print_it and not ("NO_APPLY" in kwargs and kwargs["NO_APPLY"]):
        if "NAME" in kwargs:
            print("Applying aberrations at %s" % kwargs["NAME"])
        else:
            print("Applying aberrations")
            
    max_z = zernike_num.max()
    
    if eps != 0. and max_z > 22:
        raise ValueError("PROP_ZERNIKES: Maximum index for an obscured Zernike polynomial is 22.")
        
    dmap = np.zeros([n, n], dtype=np.float64)
    
    if "RADIUS" not in kwargs:
        beam_radius = proper.prop_get_beamradius(a)
    else:
        beam_radius = kwargs["RADIUS"]
        
    dx = proper.prop_get_sampling(a) / beam_radius
    x_offset = 0.
    y_offset = 0.
    if("CENTERING" in kwargs):
        if kwargs["CENTERING"] not in _VALID_CENTERING:
            raise ValueError(_CENTERING_ERR)
    
        # Shift by half pixel
        if('interpixel' in kwargs["CENTERING"]):
            x_offset = dx/2.
            y_offset = dx/2.

    x = (np.arange(n, dtype=np.float64) - n//2) * dx + x_offset
    # x_pow_2 = x**2
    
    if (eps == 0.):
        # get list of executable equations defining Zernike polynomials
        zlist, maxrp, maxtc = proper.prop_noll_zernikes(max_z, COMPACT=True,
                                                        EXTRA_VALUES=True)
        
        for j in range(n):
            ab = np.zeros(n, dtype=np.float64)
            y = (j - n//2) * dx + y_offset
            r = np.sqrt(x**2 + y**2)
            t = np.arctan2(y, x)
        
            # predefine r**power, cos(const*theta), sin(const*theta) vectors
            for i in range(2, maxrp+1):
                rps = str(i).strip()
                cmd = "r_pow_" + rps + " = r**i"
                exec(cmd)
            
            for i in range(1, maxtc+1):
                tcs = str(i).strip()
                cmd = "cos" + tcs + "t = np.cos(i*t)"
                exec(cmd)
                cmd = "sin" + tcs + "t = np.sin(i*t)"
                exec(cmd)
           
            # assemble aberrations
            for iz in range(zernike_num.size):
                tmp = eval(zlist[zernike_num[iz]])
                ab += zernike_val[iz] * tmp
            
            dmap[j, :] += ab
            
    else:
        
        for j in range(n):
            y = (j-n//2) * dx + y_offset
            r = np.sqrt(x**2 + y**2)
            r2 = r**2
            r3 = r**3
            r4 = r**4
            r5 = r**5
            t = np.arctan2(y, x)
            
            for iz in range(len(zernike_num)):
                if zernike_num[iz] == 1:
                    ab = 1.
                elif zernike_num[iz] == 2:
                    ab = (2*r*np.cos(t))/np.sqrt(1 + eps**2)
                elif zernike_num[iz] == 3:
                    ab = (2*r*np.sin(t))/np.sqrt(1 + eps**2)
                elif zernike_num[iz] == 4:
                    ab = (np.sqrt(3)*(1 + eps**2 - 2*r2))/(-1 + eps**2)
                elif zernike_num[iz] == 5:
                    ab = (np.sqrt(6)*r2*np.sin(2*t))/np.sqrt(1 + eps**2 + eps**4)
                elif zernike_num[iz] == 6:
                    ab = (np.sqrt(6)*r2*np.cos(2*t))/np.sqrt(1 + eps**2 + eps**4)
                elif zernike_num[iz] == 7:
                    ab = (2*np.sqrt(2)*r*(2 + 2*eps**4 - 3*r2 + eps**2*(2 - 3*r2))*np.sin(t))/((-1 + eps**2)*np.sqrt(1 + 5*eps**2 + 5*eps**4 + eps**6))
                elif zernike_num[iz] == 8:
                    ab = (2*np.sqrt(2)*r*(2 + 2*eps**4 - 3*r2 + eps**2*(2 - 3*r2))*np.cos(t))/((-1 + eps**2)*np.sqrt(1 + 5*eps**2 + 5*eps**4 + eps**6))
                elif zernike_num[iz] == 9:
                    ab = (2*np.sqrt(2)*r3*np.sin(3*t))/np.sqrt(1 + eps**2 + eps**4 + eps**6)
                elif zernike_num[iz] == 10:
                    ab = (2*np.sqrt(2)*r3*np.cos(3*t))/np.sqrt(1 + eps**2 + eps**4 + eps**6)
                elif zernike_num[iz] == 11:
                    ab = (np.sqrt(5)*(1 + eps**4 - 6*r2 + 6*r4 + eps**2*(4 - 6*r2)))/ (-1 + eps**2)**2
                elif zernike_num[iz] == 12:
                    ab = (np.sqrt(10)*r2*(3 + 3*eps**6 - 4*r2 + eps**2*(3 - 4*r2) + eps**4*(3 - 4*r2))*np.cos(2*t))/ ((-1 + eps**2)*np.sqrt((1 + eps**2 + eps**4)*(1 + 4*eps**2 + 10*eps**4 + 4*eps**6 + eps**8)))
                elif zernike_num[iz] == 13:
                    ab = (np.sqrt(10)*r2*(3 + 3*eps**6 - 4*r2 + eps**2*(3 - 4*r2) + eps**4*(3 - 4*r2))*np.sin(2*t))/ ((-1 + eps**2)*np.sqrt((1 + eps**2 + eps**4)*(1 + 4*eps**2 + 10*eps**4 + 4*eps**6 + eps**8)))
                elif zernike_num[iz] == 14:
                    ab = (np.sqrt(10)*r4*np.cos(4*t))/np.sqrt(1 + eps**2 + eps**4 + eps**6 + eps**8)
                elif zernike_num[iz] == 15:
                    ab = (np.sqrt(10)*r4*np.sin(4*t))/np.sqrt(1 + eps**2 + eps**4 + eps**6 + eps**8)
                elif zernike_num[iz] == 16:
                    ab = (2*np.sqrt(3)*r*(3 + 3*eps**8 - 12*r2 + 10*r4 - 12*eps**6*(-1 + r2) + 2*eps**4*(15 - 24*r2 + 5*r4) + 4*eps**2*(3 - 12*r2 + 10*r4))*np.cos(t))/((-1 + eps**2)**2*np.sqrt((1 + 4*eps**2 + eps**4)* (1 + 9*eps**2 + 9*eps**4 + eps**6)))
                elif zernike_num[iz] == 17:
                    ab = (2*np.sqrt(3)*r*(3 + 3*eps**8 - 12*r2 + 10*r4 - 12*eps**6*(-1 + r2) + 2*eps**4*(15 - 24*r2 + 5*r4) + 4*eps**2*(3 - 12*r2 + 10*r4))*np.sin(t))/((-1 + eps**2)**2*np.sqrt((1 + 4*eps**2 + eps**4)* (1 + 9*eps**2 + 9*eps**4 + eps**6)))
                elif zernike_num[iz] == 18:
                    ab = (2*np.sqrt(3)*r3*(4 + 4*eps**8 - 5*r2 + eps**2*(4 - 5*r2) + eps**4*(4 - 5*r2) + eps**6*(4 - 5*r2))*np.cos(3*t))/ ((-1 + eps**2)*np.sqrt((1 + eps**2 + eps**4 + eps**6)*(1 + 4*eps**2 + 10*eps**4 + 20*eps**6 + 10*eps**8 + 4*eps**10 + eps**12)))
                elif zernike_num[iz] == 19:
                    ab = (2*np.sqrt(3)*r3*(4 + 4*eps**8 - 5*r2 + eps**2*(4 - 5*r2) + eps**4*(4 - 5*r2) + eps**6*(4 - 5*r2))*np.sin(3*t))/ ((-1 + eps**2)*np.sqrt((1 + eps**2 + eps**4 + eps**6)*(1 + 4*eps**2 + 10*eps**4 + 20*eps**6 + 10*eps**8 + 4*eps**10 + eps**12)))
                elif zernike_num[iz] == 20:
                    ab = (2*np.sqrt(3)*r5*np.cos(5*t))/ np.sqrt(1 + eps**2 + eps**4 + eps**6 + eps**8 + eps**10)
                elif zernike_num[iz] == 21:
                    ab = (2*np.sqrt(3)*r5*np.sin(5*t))/ np.sqrt(1 + eps**2 + eps**4 + eps**6 + eps**8 + eps**10)
                elif zernike_num[iz] == 22:
                    ab = (np.sqrt(7)*(1 + eps**6 - 12*r2 + 30*r4 - 20*r**6 + eps**4*(9 - 12*r2) + eps**2*(9 - 36*r2 + 30*r4)))/ (-1 + eps**2)**3
                    
            dmap[j, :] += zernike_val[iz] * ab
            
    if not ("NO_APPLY" in kwargs and kwargs["NO_APPLY"]):
        if ("AMPLITUDE" in kwargs and kwargs["AMPLITUDE"]):
            a.wfarr *= proper.prop_shift_center(dmap)
        else:
            i = complex(0, 1)
            a.wfarr *= np.exp(i*2*np.pi/a.lamda*proper.prop_shift_center(dmap))
            
    return dmap