def add_errors(wf, onaxis=True, phase_screen=None, amp_screen=None, tiptilt=None, misalign=[0, 0, 0, 0, 0, 0], ngrid=1024, verbose=False, **conf): # apply phase screen (scao residuals, ncpa, petal piston) if phase_screen is not None: assert phase_screen.ndim == 2, "phase_screen dim must be 2." proper.prop_add_phase(wf, pad_img(phase_screen, ngrid)) # apply amplitude screen (Talbot effect) if amp_screen is not None: assert amp_screen.ndim == 2, "amp_screen dim must be 2." proper.prop_multiply(wf, pad_img(amp_screen, ngrid)) # apply tip-tilt (Zernike 2,3) if tiptilt is not None: proper.prop_zernikes(wf, [2, 3], np.array(tiptilt, ndmin=1)) # pupil-plane apodization (already preloaded if no RA misalign) if onaxis == False or ('RAVC' in conf['mode'] and misalign is not None): conf.update(ravc_misalign=misalign) wf = apodizer(wf, onaxis=onaxis, verbose=verbose, **conf) return wf
def add_atmos(wf, it): """ creates a phase offset matrix for each wavelength at each time step, sampled from the atmosphere generated by hcipy HCIpy generates an atmosphere with given parameters. The returned field is in units of phase delay for each wavelength. prop_add_phase wants the phase delay to be in units of meters for each wavelength, so we convert to meters via the wavelength/np.pi :param wf: a single (2D) wfo.wf_collection[iw,ib] at one wavelength and object :param it: timestep# in obs_sequence. Comes from medis_main.gen_timeseries() :return: nothing returned, wfo is modified with proper.prop_add_phase """ if tp.use_atmos is False: pass # don't do anything. Putting this type of check here allows universal toggling on/off rather than # commenting/uncommenting in the proper perscription else: wavelength = wf.lamda # the .lamda comes from proper, not from Wavefronts class # Check for Existing File atmos_map = get_filename(it, wavelength) # dprint(f"atmos map applied is {atmos_map}") if not os.path.exists(atmos_map): gen_atmos(plot=True) atm_map = fits.open(atmos_map)[1].data atm_map = unwrap_phase(atm_map) atm_map *= wavelength / ( 2 * np.pi ) # converts atmosphere in units of phase delay (rad) into distance (m) proper.prop_add_phase(wf, atm_map)
def static_ncpa(wf, npupil, screen): n = int(proper.prop_get_gridsize(wf)) screen_pixels = (screen.shape)[0] # size of the phase screen sf = float(npupil) / float( screen_pixels ) # scaling factor the phase screen to the simulation pupil size screen_scale = cv2.resize( screen.astype(np.float32), (0, 0), fx=sf, fy=sf, interpolation=cv2.INTER_LINEAR ) # scale the the phase screen to the simulation pupil size screen_large = np.zeros( (n, n)) # define an array of n-0s, where to insert the screen screen_large[ int(n / 2) + 1 - int(npupil / 2) - 1:int(n / 2) + 1 + int(npupil / 2), int(n / 2) + 1 - int(npupil / 2) - 1:int(n / 2) + 1 + int(npupil / 2)] = screen_scale # insert the scaled screen into the 0s grid proper.prop_add_phase(wf, screen_large) return wf
def add_aber(wf, d_lens, aber_params, aber_vals, step=0, lens_name=None): """ loads a phase error map and adds aberrations using proper.prop_add_phase if no aberration file exists, creates one for specific lens using generate_maps :param wf: a single (2D) wfo.wf_collection[iw,io] at one wavelength and object :param d_lens: diameter (in m) of lens (only used when generating new aberrations maps) :param aber_params: parameters specified by tp.aber_params :param step: is the step number for quasistatic aberrations :param lens_name: name of the lens, used to save/read in FITS file of aberration map :return returns nothing but will act upon a given wavefront and apply new or loaded-in aberration map """ # TODO this does not currently loop over time, so it is not using quasi-static abberations. if tp.use_aber is False: pass # don't do anything. Putting this type of check here allows universal toggling on/off rather than # commenting/uncommenting in the proper perscription else: # dprint("Adding Abberations") # Load in or Generate Aberration Map iop.aberdata = f"gridsz{sp.grid_size}_bmratio{sp.beam_ratio}_tsteps{sp.numframes}" iop.aberdir = os.path.join(iop.testdir, iop.aberroot, iop.aberdata) filename = f"{iop.aberdir}/t{step}_{lens_name}.fits" if not os.path.isfile(filename): generate_maps(aber_vals, d_lens, lens_name) if aber_params['Phase']: phase_map = readFITS(filename) # Add Phase Map proper.prop_add_phase(wf, phase_map) if aber_params['Amp']: dprint("Outdated code-please update") raise NotImplementedError
def remove_zernike(wf, pup, allSF, zpols): proper.prop_add_phase(wf, allSF) LSF = proper.prop_zernikes(wf, np.arange(len(zpols)) + 1, zpols).astype('float32') LSF[pup == 0] = 0 HSF = allSF.astype('float32') - LSF return LSF, HSF
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
def propagate_one(wf, phase_screen=None, tiptilt=None, misalign=None, ngrid=1024, npupil=285, tag=None, onaxis=True, savefits=False, verbose=False, **conf): """ Propagate one single wavefront. An off-axis PSF can be obtained by switching onaxis to False, thereby decentering the focal plane mask (if any). """ # update conf conf.update(ngrid=ngrid, npupil=npupil) # keep a copy of the input wavefront wf1 = deepcopy(wf) # apply phase screen (scao residuals, ncpa, petal piston) if phase_screen is not None: phase_screen = np.nan_to_num(phase_screen) phase_screen = pad_img(resize_img(phase_screen, npupil), ngrid) proper.prop_add_phase(wf1, phase_screen) # apply tip-tilt (Zernike 2,3) if tiptilt is not None: # translate the tip/tilt from lambda/D into RMS phase errors # RMS = x/2 = ((lam/D)*(D/2))/2 = lam/4 tiptilt = np.array(tiptilt, ndmin=1)*conf['lam']/4 proper.prop_zernikes(wf1, [2,3], tiptilt) # update RAVC misalignment conf.update(ravc_misalign=misalign) # pupil-plane apodization wf1, apo_amp, apo_phase = apodizer(wf1, get_amp=True, verbose=verbose, **conf) # focal-plane mask, only in 'on-axis' configuration if onaxis == True: wf1 = fp_mask(wf1, verbose=verbose, **conf) # Lyot-stop or APP wf1, ls_amp, ls_phase = lyot_stop(wf1, get_amp=True, verbose=verbose, **conf) # detector psf = detector(wf1, verbose=verbose, **conf) # save psf as fits file if savefits == True: tag = '' if tag is None else '%s_'%tag name = '%s%s_PSF'%(tag, {True: 'onaxis', False: 'offaxis'}[onaxis]) save2fits(psf, name, **conf) return psf
def add_atmos(wf, it, param_tup=None, spatial_zoom=False): """ creates a phase offset matrix for each wavelength at each time step, sampled from the atmosphere generated by hcipy HCIpy generates an atmosphere with given parameters. The returned field is in units of phase delay for each wavelength. prop_add_phase wants the phase delay to be in units of meters for each wavelength, so we convert to meters via the wavelength/np.pi :param wf: a single (2D) wfo.wf_collection[iw,ib] at one wavelength and object :param it: timestep# in obs_sequence. Comes from medis_main.gen_timeseries() :return: nothing returned, wfo is modified with proper.prop_add_phase """ if tp.use_atmos is False: pass # don't do anything. Putting this type of check here allows universal toggling on/off rather than # commenting/uncommenting in the proper perscription else: wavelength = wf.lamda # the .lamda comes from proper, not from Wavefronts class # Check for Existing File) atmos_map = get_filename(it, wavelength, param_tup) if not os.path.exists(atmos_map): # todo remove when all test scripts use the new format # TODO check for new file name convention in addition to directory name print( 'atmospheres should be created at the beginng, not on the fly') raise NotImplementedError atm_map = fits.open(atmos_map)[1].data atm_map = unwrap_phase(atm_map) atm_map *= wavelength / ( 2 * np.pi ) # converts atmosphere in units of phase delay (rad) into distance (m) if spatial_zoom: scale = ap.wvl_range[0] / wavelength atm_map = clipped_zoom(atm_map, scale) h, w = atm_map.shape[:2] mask = circular_mask(h, w, radius=scale * h * sp.beam_ratio / 2) atm_map[~mask] = 0 atm_map[mask] -= np.mean( atm_map[mask]) # remove bias from spatial stretching proper.prop_add_phase(wf, atm_map)
def prop_tilt(wf, tilt_x, tilt_y): """Tilt a wavefront in X and Y. based on tilt(self, Xangle, Yangle) from Poppy (poppy_core.py) From that function's docs: The sign convention is chosen such that positive Yangle tilts move the star upwards in the array at the focal plane. (This is sort of an inverse of what physically happens in the propagation to or through focus, but we're ignoring that here and trying to just work in sky coords) Parameters ---------- wf : obj WaveFront class object tilt_x : float Tilt angle along x in arc seconds tilt_y : float Tilt angle along y in arc seconds Returns ------- None Modifies wavefront array in wf object. """ if np.abs(tilt_x) > 0 or np.abs(tilt_y) > 0: sampling = proper.prop_get_sampling(wf) # m/pixel xangle_rad = tilt_x * np.pi / 648000. # rad. yangle_rad = tilt_y * np.pi / 648000. # rad. ngrid = proper.prop_get_gridsize(wf) # pixels U, V = np.indices(wf.wfarr.shape, dtype=float) U -= (ngrid - 1) / 2.0 # pixels X U *= sampling # m V -= (ngrid - 1) / 2.0 # pixels Y V *= sampling # m # Not totally comfortable that these are combined linearly # but go with Poppy for now phase = V * xangle_rad + U * yangle_rad # rad. m proper.prop_add_phase(wf, phase)
def polmap( wavefront, polfile, pupil_diam_pix, condition, MUF=1.0 ): n = proper.prop_get_gridsize( wavefront ) lambda_m = proper.prop_get_wavelength(wavefront) if condition <= 2: (amp, pha) = polab( polfile, lambda_m, pupil_diam_pix, condition ) elif condition == 5: (amp_m45_x, pha_m45_x) = polab( polfile, lambda_m, pupil_diam_pix, -1 ) (amp_p45_x, pha_p45_x) = polab( polfile, lambda_m, pupil_diam_pix, +1 ) amp = (amp_m45_x + amp_p45_x) / 2 pha = (pha_m45_x + pha_p45_x) / 2 elif condition == 6: (amp_m45_y, pha_m45_y) = polab( polfile, lambda_m, pupil_diam_pix, -2 ) (amp_p45_y, pha_p45_y) = polab( polfile, lambda_m, pupil_diam_pix, +2 ) amp = (amp_m45_y + amp_p45_y) / 2 pha = (pha_m45_y + pha_p45_y) / 2 elif condition == 10: (amp_m45_x, pha_m45_x) = polab( polfile, lambda_m, pupil_diam_pix, -1 ) (amp_p45_x, pha_p45_x) = polab( polfile, lambda_m, pupil_diam_pix, +1 ) (amp_m45_y, pha_m45_y) = polab( polfile, lambda_m, pupil_diam_pix, -2 ) (amp_p45_y, pha_p45_y) = polab( polfile, lambda_m, pupil_diam_pix, +2 ) amp = (amp_m45_x + amp_p45_x + amp_m45_y + amp_p45_y) / 4 pha = (pha_m45_x + pha_p45_x + pha_m45_y + pha_p45_y) / 4 else: raise Exception( 'POLMAP: unmatched condition' ) proper.prop_multiply( wavefront, trim(amp,n) ) proper.prop_add_phase( wavefront, trim(MUF*pha,n) ) amp = 0 phase = 0 amp_p45x = 0 amp_m45x = 0 amp_p45y = 0 amp_m45y = 0 pha_p45x = 0 pha_m45x = 0 pha_p45y = 0 pha_m45y = 0 return
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
def propcustom_dm(wf, dm_z0, dm_xc, dm_yc, spacing=0., **kwargs): """ Generate a deformable mirror surface almost exactly like PROPER. Simulate a deformable mirror of specified actuator spacing, including the effects of the DM influence function. Has two more optional keywords compared to proper.prop_dm Parameters ---------- wf : obj WaveFront class object dm_z0 : str or numpy ndarray Either a 2D numpy array containing the surface piston of each DM actuator in meters or the name of a 2D FITS image file containing the above dm_xc, dm_yc : list or numpy ndarray The location of the optical axis (center of the wavefront) on the DM in actuator units (0 ro num_actuator-1). The center of the first actuator is (0.0, 0.0) spacing : float Defines the spacing in meters between actuators; must not be used when n_act_across_pupil is specified. Returns ------- dmap : numpy ndarray Returns DM surface (not wavefront) map in meters Other Parameters ---------------- FIT : bool Switch that tells routine that the values in "dm_z" are the desired surface heights rather than commanded actuator heights, and so the routine should fit this map, accounting for actuator influence functions, to determine the necessary actuator heights. An iterative error-minimizing loop is used for the fit. NO_APPLY : bool If set, the DM pattern is not added to the wavefront. Useful if the DM surface map is needed but should not be applied to the wavefront N_ACT_ACROSS_PUPIL : int Specifies the number of actuators that span the X-axis beam diameter. If it is a whole number, the left edge of the left pixel is aligned with the left edge of the beam, and the right edge of the right pixel with the right edge of the beam. This determines the spacing and size of the actuators. Should not be used when "spacing" value is specified. XTILT, YTILT, ZTILT : float Specify the rotation of the DM surface with respect to the wavefront plane in degrees about the X, Y, Z axes, respectively, with the origin at the center of the wavefront. The DM surface is interpolated and orthographically projected onto the wavefront grid. The coordinate system assumes that the wavefront and initial DM surface are in the X,Y plane with a lower left origin with Z towards the observer. The rotations are left handed. The default rotation order is X, Y, then Z unless the /ZYX switch is set. XYZ or ZYX : bool Specifies the rotation order if two or more of XTILT, YTILT, or ZTILT are specified. The default is /XYZ for X, Y, then Z rotations. inf_fn : string specify a new influence function as a FITS file with the same header keywords as PROPER's default influence function. Needs these values in info.PrimaryData.Keywords: 'P2PDX_M' % pixel width x (m) 'P2PDY_M' % pixel width y (m) 'C2CDX_M' % actuator pitch x (m) 'C2CDY_M' % actuator pitch y (m) inf_sign : {+,-} specifies the sign (+/-) of the influence function. Given as an option because the default influence function file is positive, but positive DM actuator commands make a negative deformation for Xinetics and BMC DMs. Raises ------ ValueError: User cannot specify both actuator spacing and N_ACT_ACROSS_PUPIL ValueError: User must specify either actuator spacing or N_ACT_ACROSS_PUPIL """ if "ZYX" in kwargs and "XYZ" in kwargs: raise ValueError('PROP_DM: Error: Cannot specify both XYZ and ZYX ' + 'rotation orders. Stopping') elif "ZYX" not in kwargs and 'XYZ' not in kwargs: XYZ = 1 # default is rotation around X, then Y, then Z # ZYX = 0 elif "ZYX" in kwargs: # ZYX = 1 XYZ = 0 elif "XYZ" in kwargs: XYZ = 1 # ZYX = 0 if "XTILT" in kwargs: xtilt = kwargs["XTILT"] else: xtilt = 0. if "YTILT" in kwargs: ytilt = kwargs["YTILT"] else: ytilt = 0. if "ZTILT" in kwargs: ztilt = kwargs["ZTILT"] else: ztilt = 0. if type(dm_z0) == str: dm_z = proper.prop_fits_read(dm_z0) # Read DM setting from FITS file else: dm_z = dm_z0 if "inf_fn" in kwargs: inf_fn = kwargs["inf_fn"] else: inf_fn = "influence_dm5v2.fits" if "inf_sign" in kwargs: if(kwargs["inf_sign"] == '+'): sign_factor = 1. elif(kwargs["inf_sign"] == '-'): sign_factor = -1. else: sign_factor = 1. n = proper.prop_get_gridsize(wf) dx_surf = proper.prop_get_sampling(wf) # sampling of surface in meters beamradius = proper.prop_get_beamradius(wf) # Default influence function sampling is 0.1 mm, peak at (x,y)=(45,45) # Default influence function has shape = 1x91x91. Saving it as a 2D array # before continuing with processing dir_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data") inf = proper.prop_fits_read(os.path.join(dir_path, inf_fn)) inf = sign_factor*np.squeeze(inf) s = inf.shape nx_inf = s[1] ny_inf = s[0] xc_inf = nx_inf // 2 yc_inf = ny_inf // 2 dx_inf = 0.1e-3 # influence function spacing in meters dx_dm_inf = 1.0e-3 # nominal spacing between DM actuators in meters inf_mag = 10 if spacing != 0 and "N_ACT_ACROSS_PUPIL" in kwargs: raise ValueError("PROP_DM: User cannot specify both actuator spacing" + "and N_ACT_ACROSS_PUPIL. Stopping.") if spacing == 0 and "N_ACT_ACROSS_PUPIL" not in kwargs: raise ValueError("PROP_DM: User must specify either actuator spacing" + " or N_ACT_ACROSS_PUPIL. Stopping.") if "N_ACT_ACROSS_PUPIL" in kwargs: dx_dm = 2. * beamradius / int(kwargs["N_ACT_ACROSS_PUPIL"]) else: dx_dm = spacing dx_inf = dx_inf * dx_dm / dx_dm_inf # Influence function sampling scaled # to specified DM actuator spacing if "FIT" in kwargs: x = (np.arange(5, dtype=np.float64) - 2) * dx_dm if proper.use_cubic_conv: inf_kernel = proper.prop_cubic_conv(inf.T, x/dx_inf+xc_inf, x/dx_inf+yc_inf, GRID=True) else: xygrid = np.meshgrid(x/dx_inf+xc_inf, x/dx_inf+yc_inf) inf_kernel = map_coordinates(inf.T, xygrid, order=3, mode="nearest") (dm_z_commanded, dms) = proper.prop_fit_dm(dm_z, inf_kernel) else: dm_z_commanded = dm_z s = dm_z.shape nx_dm = s[1] ny_dm = s[0] # Create subsampled DM grid margin = 9 * inf_mag nx_grid = nx_dm * inf_mag + 2 * margin ny_grid = ny_dm * inf_mag + 2 * margin xoff_grid = margin + inf_mag/2 # pixel location of 1st actuator center in subsampled grid yoff_grid = xoff_grid dm_grid = np.zeros([ny_grid, nx_grid], dtype = np.float64) x = np.arange(nx_dm, dtype=int) * int(inf_mag) + int(xoff_grid) y = np.arange(ny_dm, dtype=int) * int(inf_mag) + int(yoff_grid) dm_grid[np.tile(np.vstack(y), (nx_dm,)), np.tile(x, (ny_dm, 1))] = dm_z_commanded dm_grid = ss.fftconvolve(dm_grid, inf, mode='same') # 3D rotate DM grid and project orthogonally onto wavefront xdim = int(np.round(np.sqrt(2) * nx_grid * dx_inf / dx_surf)) # grid dimensions (pix) projected onto wavefront ydim = int(np.round(np.sqrt(2) * ny_grid * dx_inf / dx_surf)) if xdim > n: xdim = n if ydim > n: ydim = n x = np.ones((ydim, 1), dtype=int) * ((np.arange(xdim) - xdim // 2) * dx_surf) y = (np.ones((xdim, 1), dtype=int) * ((np.arange(ydim) - ydim // 2) * dx_surf)).T a = xtilt * np.pi / 180 b = ytilt * np.pi / 180 g = ztilt * np.pi /180 if XYZ: m = np.array([[cos(b)*cos(g), -cos(b)*sin(g), sin(b), 0], [cos(a)*sin(g) + sin(a)*sin(b)*cos(g), cos(a)*cos(g)-sin(a)*sin(b)*sin(g), -sin(a)*cos(b), 0], [sin(a)*sin(g)-cos(a)*sin(b)*cos(g), sin(a)*cos(g)+cos(a)*sin(b)*sin(g), cos(a)*cos(b), 0], [0, 0, 0, 1] ]) else: m = np.array([ [cos(b)*cos(g), cos(g)*sin(a)*sin(b)-cos(a)*sin(g), cos(a)*cos(g)*sin(b)+sin(a)*sin(g), 0], [cos(b)*sin(g), cos(a)*cos(g)+sin(a)*sin(b)*sin(g), -cos(g)*sin(a)+cos(a)*sin(b)*sin(g), 0], [-sin(b), cos(b)*sin(a), cos(a)*cos(b), 0], [0, 0, 0, 1] ]) # Forward project a square edge = np.array([[-1.0, -1.0, 0.0, 0.0], [1.0, -1.0, 0.0, 0.0], [1.0, 1.0, 0.0, 0.0], [-1.0, 1.0, 0.0, 0.0]]) new_xyz = np.dot(edge, m) # determine backward projection for screen-raster-to-DM-surce computation dx_dxs = (new_xyz[0, 0] - new_xyz[1, 0]) / (edge[0, 0] - edge[1, 0]) dx_dys = (new_xyz[1, 0] - new_xyz[2, 0]) / (edge[1, 1] - edge[2, 1]) dy_dxs = (new_xyz[0, 1] - new_xyz[1, 1]) / (edge[0, 0] - edge[1, 0]) dy_dys = (new_xyz[1, 1] - new_xyz[2, 1]) / (edge[1, 1] - edge[2, 1]) xs = (x/dx_dxs - y*dx_dys/(dx_dxs*dy_dys)) / \ (1 - dy_dxs*dx_dys/(dx_dxs*dy_dys)) ys = (y/dy_dys - x*dy_dxs/(dx_dxs*dy_dys)) / \ (1 - dx_dys*dy_dxs/(dx_dxs*dy_dys)) xdm = (xs + dm_xc * dx_dm) / dx_inf + xoff_grid ydm = (ys + dm_yc * dx_dm) / dx_inf + yoff_grid if proper.use_cubic_conv: grid = proper.prop_cubic_conv(dm_grid.T, xdm, ydm, GRID = False) grid = grid.reshape([xdm.shape[1], xdm.shape[0]]) else: grid = map_coordinates(dm_grid.T, [xdm, ydm], order=3, mode="nearest", prefilter = True) dmap = np.zeros([n, n], dtype=np.float64) nx_grid, ny_grid = grid.shape xmin, xmax = n // 2 - xdim // 2, n // 2 - xdim // 2 + nx_grid ymin, ymax = n // 2 - ydim // 2, n // 2 - ydim // 2 + ny_grid dmap[ymin:ymax, xmin:xmax] = grid if "NO_APPLY" not in kwargs: proper.prop_add_phase(wf, 2 * dmap) # convert surface to WFE return dmap
def wfirst_phaseb_compact(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'] cor_type = 'hlc' # coronagraph type ('hlc', 'spc', 'none') input_field_rootname = '' # rootname of files containing aberrated pupil polaxis = 0 # polarization condition (only used with input_field_rootname) source_x_offset = 0 # source offset in lambda0_m/D radians (tilt applied at primary) source_y_offset = 0 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) fpm_axis = 'p' # HLC FPM axis: '', 's', 'p' 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 'cor_type' in PASSVALUE: cor_type = PASSVALUE['cor_type'] if 'fpm_axis' in PASSVALUE: fpm_axis = PASSVALUE['fpm_axis'] is_hlc = False is_spc = 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.69pol' + fpm_axis + '_' 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_small = 1024 # gridsize in non-critical areas n_big = 2048 # gridsize to/from FPM 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' ] fpm_axis = 's' lam_occs = [ prefix + 'occ_lam' + s + 'theta6.69pol' + fpm_axis + '_' 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_small = 1024 # gridsize in non-critical areas n_big = 2048 # gridsize to/from FPM 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_rotated.fits' fpm_file = file_dir + 'fpm_0.05lamdivD.fits' fpm_sampling = 0.05 # sampling in lambda0/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 + 'lyotstop_0.5mag.fits' n_small = 2048 # gridsize in non-critical areas n_big = 1400 # gridsize to FPM (propagation to/from FPM handled by MFT) 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_rotated.fits' fpm_file = file_dir + 'fpm_0.05lamdivD.fits' fpm_sampling = 0.05 # sampling in lambda0/D of FPM mask lyot_stop_file = file_dir + 'LS_half_symm_CGI180718_Str3.20pct_38D91_N500_pixel.fits' fpm_sampling_lambda_m = 0.825e-6 lambda0_m = 0.825e-6 # FPM scaled for this central wavelength n_small = 2048 # gridsize in non-critical areas n_big = 1400 # gridsize to FPM (propagation to/from FPM handled by MFT) 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' use_fpm = 0 use_lyot_stop = 0 n_small = 1024 n_big = 1024 else: raise Exception('wfirst_phaseb_compact: Unsuported 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'] if 'input_field_rootname' in PASSVALUE: input_field_rootname = PASSVALUE['input_field_rootname'] if 'polaxis' in PASSVALUE: polaxis = PASSVALUE['polaxis'] 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 '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 'final_sampling_lam0' in PASSVALUE: final_sampling_lam0 = PASSVALUE['final_sampling_lam0'] if 'output_dim' in PASSVALUE: output_dim = PASSVALUE['output_dim'] if polaxis != 0 and input_field_rootname == '': raise Exception( 'wfirst_phaseb_compact: polaxis can only be defined when input_field_rootname is given' ) diam_at_dm1 = 0.0463 d_dm1_dm2 = 1.0 n = n_small # start off with less padding wavefront = proper.prop_begin(diam_at_dm1, lambda_m, n, float(pupil_diam_pix) / n) if input_field_rootname == '': pupil = proper.prop_fits_read(pupil_file) proper.prop_multiply(wavefront, trim(pupil, n)) pupil = 0 else: lams = format(lambda_m * 1e6, "6.4f") pols = format(int(round(polaxis))) rval = proper.prop_fits_read(input_field_rootname + '_' + lams + 'um_' + pols + '_real.fits') ival = proper.prop_fits_read(input_field_rootname + '_' + lams + 'um_' + pols + '_imag.fits') proper.prop_multiply(wavefront, trim(rval + 1j * ival, n)) rval = 0 ival = 0 proper.prop_define_entrance(wavefront) 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 use_dm1 != 0: 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 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: 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 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_dm1_dm2, 'back to DM1') (wavefront, sampling_m) = proper.prop_end(wavefront, NOABS=True) # apply shape pupil mask if is_spc == True: pupil_mask = proper.prop_fits_read(pupil_mask_file) wavefront *= trim(pupil_mask, n) pupil_mask = 0 # propagate to FPM and apply FPM if is_hlc == True: n = n_big wavefront = trim(wavefront, n) wavefront = ffts(wavefront, -1) # to focus 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) wavefront *= trim(occ, n) occ_r = 0 occ_i = 0 occ = 0 wavefront = ffts(wavefront, +1) # to lyot stop elif is_spc == True: n = n_big wavefront = trim(wavefront, n) fpm = proper.prop_fits_read(fpm_file) nfpm = fpm.shape[1] fpm_sampling_lam = fpm_sampling * fpm_sampling_lambda_m / lambda_m wavefront = mft2(wavefront, fpm_sampling_lam, pupil_diam_pix, nfpm, -1) # MFT to highly-sampled focal plane wavefront *= fpm fpm = 0 pupil_diam_pix = pupil_diam_pix / 2.0 # Shrink pupil by 1/2 wavefront = mft2(wavefront, fpm_sampling_lam, pupil_diam_pix, int(pupil_diam_pix), +1) # MFT to Lyot stop with 1/2 magnification n = n_small wavefront = trim(wavefront, n) lyot = proper.prop_fits_read(lyot_stop_file) wavefront *= trim(lyot, n) lyot = 0 wavefront *= n wavefront = ffts(wavefront, -1) # to focus # rotate to convention used by full prescription wavefront[:, :] = np.rot90(wavefront, 2) wavefront[:, :] = np.roll(wavefront, 1, axis=0) wavefront[:, :] = np.roll(wavefront, 1, axis=1) if final_sampling_lam0 != 0: mag = (float(pupil_diam_pix) / n) / final_sampling_lam0 * (lambda_m / lambda0_m) wavefront = proper.prop_magnify(wavefront, mag, output_dim, AMP_CONSERVE=True) else: wavefront = trim(wavefront, output_dim) sampling_m = 0.0 return wavefront, sampling_m
def prop_conic(wf, lens_fl, conic, surface_name=""): """Alter the current wavefront as a perfect conic lens would. based on prop_len Parameters ---------- wf : obj WaveFront class object lens_fl : float Focal length of lens in meters conic : float Conic constant, <-1 for hyperbolas, -1 for parabolas, between -1 and 0 for ellipses, 0 for spheres, and greater than 0 for oblate ellipsoids surface_name : str String containing name of surface; used when printing out that a lens is being applied Returns ------- None Modifies wavefront array in wf object. """ rayleigh_factor = proper.rayleigh_factor if proper.print_it: if surface_name == "": print("Applying lens") else: print("Applying lens at %s" % (surface_name)) # calculate waist radius at current surface wf.z_Rayleigh = np.pi * wf.w0**2 / wf.lamda w_at_surface = wf.w0 * np.sqrt(1. + ((wf.z - wf.z_w0) / wf.z_Rayleigh)**2) if (wf.z - wf.z_w0) != 0.0: # lens is not at focus or entrance pupil gR_beam_old = (wf.z - wf.z_w0) + wf.z_Rayleigh**2 / (wf.z - wf.z_w0) gR_beam_old_inf = 0. if gR_beam_old != lens_fl: gR_beam = 1. / (1. / gR_beam_old - 1. / lens_fl) gR_beam_inf = 0 if proper.verbose: print(" LENS: Gaussian R_beam_old = ", gR_beam_old, " R_beam = ", gR_beam) else: gR_beam_inf = 1 if proper.verbose: print(" LENS: Gaussian R_beam_old = ", gR_beam_old, " R_bream = Infinite") else: gR_beam_old_inf = 1 # at focus or entrance pupil, input beam is planar gR_beam = -lens_fl gR_beam_inf = 0 # output beam is spherical if proper.verbose: print(" LENS: Gaussian R_beam_old = Infinite R_beam = ", gR_beam) if wf.beam_type_old == "INSIDE_" or wf.reference_surface == "PLANAR": R_beam_old = 0.0 else: R_beam_old = wf.z - wf.z_w0 if not gR_beam_inf: wf.z_w0 = -gR_beam / (1. + (wf.lamda * gR_beam / (np.pi * w_at_surface**2))**2) + wf.z wf.w0 = w_at_surface / np.sqrt(1. + (np.pi * w_at_surface**2 / (wf.lamda * gR_beam))**2) else: wf.z_w0 = wf.z wf.w0 = w_at_surface # output beam is planar # determine new Rayleigh distance from focus; if currently inside this, # then output beam is planar wf.z_Rayleigh = np.pi * wf.w0**2 / wf.lamda if np.abs(wf.z_w0 - wf.z) < rayleigh_factor * wf.z_Rayleigh: beam_type_new = "INSIDE_" R_beam = 0. else: beam_type_new = "OUTSIDE" R_beam = wf.z - wf.z_w0 wf.propagator_type = wf.beam_type_old + "_to_" + beam_type_new # Apply phase changes as needed, but don't apply if the phase is going to be # similarly altered during propagation if proper.verbose: print(" LENS: propagator_type = ", wf.propagator_type) if wf.beam_type_old == "INSIDE_": sR_beam_old = "Infinite" else: sR_beam_old = str(R_beam_old).strip() if beam_type_new == "INSIDE_": sR_beam = "Infinite" else: sR_beam = str(R_beam).strip() print(" LENS: R beam old = %s R_beam = %s lens_fl = %6.3f" % (sR_beam_old, sR_beam, lens_fl)) print(" LENS: Beam diameter at lens = %4.3f" % (w_at_surface * 2)) # (BJ) Note this operation could be cached in WaveFront, and in any case this op # wastefully calculates sqrt for each point, but each one gets squared prior to # passing to prop_add_phase below. rho = proper.prop_radius(wf) # For different propagator types if wf.propagator_type == "INSIDE__to_INSIDE_": lens_phase = 1. / lens_fl elif wf.propagator_type == "INSIDE__to_OUTSIDE": lens_phase = 1. / lens_fl + 1. / R_beam elif wf.propagator_type == "OUTSIDE_to_INSIDE_": lens_phase = 1. / lens_fl - 1. / R_beam_old elif wf.propagator_type == "OUTSIDE_to_OUTSIDE": if R_beam_old == 0.0: lens_phase = 1. / lens_fl + 1. / R_beam elif R_beam == 0.0: lens_phase = 1. / lens_fl - 1. / R_beam_old else: lens_phase = 1. / lens_fl - 1. / R_beam_old + 1. / R_beam if proper.verbose: print(" LENS: 1/lens_fl = ", 1. / lens_fl) print(" LENS: 1/R_beam_old = ", 1. / R_beam_old) print(" LENS: 1/R_beam = ", 1. / R_beam) print(" LENS: lens_phase = ", lens_phase) # (BJ) Conic phase calculation # replaces the line: # proper.prop_add_phase(wf, -rho**2 * (lens_phase/2.)) ### BEGIN ### def conic_phase(r, k, phi): rsq = r**2 return -rsq * phi / (1. + math.sqrt(1. - (1. + k) * rsq * (phi**2))) conic_phase = np.vectorize(conic_phase) calc_phase = conic_phase(rho, conic, 1. / lens_fl) # np.save("conic_phase_{}_{}.dat".format(lens_fl, conic), calc_phase) quad_phase_corr = -rho**2 * ((lens_phase - 1. / lens_fl) / 2.) # np.save("quad_phase_{}.dat".format(lens_fl), quad_phase) proper.prop_add_phase(wf, calc_phase + quad_phase_corr) #### END #### if beam_type_new == "INSIDE_": wf.reference_surface = "PLANAR" else: wf.reference_surface = "SPHERI" wf.beam_type_old = beam_type_new wf.current_fratio = np.abs(wf.z_w0 - wf.z) / (2. * w_at_surface) # save stuff for layout plots if proper.do_table: proper.lens_fl_list[proper.action_num] = lens_fl proper.lens_eff_fratio_list[proper.action_num] = wf.current_fratio proper.beam_diam_list[proper.action_num] = 2 * w_at_surface proper.sampling_list[proper.action_num] = wf.dx if surface_name != "": proper.surface_name_list[proper.action_num] = surface_name else: proper.surface_name_list[proper.action_num] = "(LENS)" proper.action_num += 1 if proper.verbose: print(" LENS: Rayleigh distance = %4.12f" % (wf.z_Rayleigh)) return