def test_single_seg_psf(segmentid=1): """Test calculation of a single segment PSF, including options to remove piston/tip/tilt as used by MIRAGE """ nrc = webbpsf.NIRCam() nrc.filter = 'F212N' nrc, ote = webbpsf.enable_adjustable_ote(nrc) ote.zero(zero_original=True) segname = webbpsf.constants.SEGNAMES_WSS_ORDER[segmentid-1][0:2] ote.move_seg_local(segname, xtilt=1, piston=-1) pupil = webbpsf.webbpsf_core.one_segment_pupil(segmentid) ote.amplitude = pupil[0].data psf = nrc.calc_psf(nlambda=1) ote.remove_piston = True ote.update_opd() psf_rm_piston = nrc.calc_psf(nlambda=1) assert np.allclose(psf[0].data, psf_rm_piston[0].data), "Piston removal should not affect the overall PSF" assert np.allclose( webbpsf.measure_centroid(psf), webbpsf.measure_centroid(psf_rm_piston)), "centroid should not shift" ote.remove_piston_tip_tilt = True ote.update_opd() psf_rm_ptt = nrc.calc_psf(nlambda=1) assert not np.allclose(psf[0].data, psf_rm_ptt[0].data), "Piston/Tip/Tip removal should shift the overall PSF" assert np.abs(webbpsf.measure_centroid(psf)[0] - webbpsf.measure_centroid(psf_rm_ptt)[0]) > 40, "centroid should shift susbtantially with/without tip/tilt removal"
def nircam_nocoro(filter, Aber_WSS): """ Create PSF :param filter: :param Aber_WSS: :return: """ # Create NIRCam object nc = webbpsf.NIRCam() # Set filter nc.filter = filter # Adjust OTE with aberrations nc, ote = webbpsf.enable_adjustable_ote(nc) nc.include_si_wfe = False # set SI internal WFE to zero ote.reset() ote.zero() for i in range(nb_seg): seg = wss_segs[i].split('-')[0] ote._apply_hexikes_to_seg(seg, Aber_WSS[i, :]) # Calculate PSF psf_nc = nc.calc_psf(oversample=1, fov_pixels=int(im_size_e2e), nlambda=1) psf_webbpsf = psf_nc[1].data return psf_webbpsf
def test_move_sur(plot=False): """ Test we can move mirrors using Segment Update Requests """ import webbpsf import os import glob surdir = os.path.join(webbpsf.__path__[0], 'tests', 'surs') surs = glob.glob(surdir+'/*sur.xml') nrc = webbpsf.NIRCam() nrc.filter='F212N' nrc, ote = webbpsf.enable_adjustable_ote(nrc) ote.zero(zero_original=True) for s in surs: print("Testing "+s) ote.reset() ote.move_sur(s) # the coarse phasing SUR is a no-op after 3 groups; all others have some effect if 'coarse_phasing' not in s: assert not np.allclose(ote.segment_state, 0), "Expected some segments to be moved" ote.move_sur(s, reverse=True) assert np.allclose(ote.segment_state, 0), "Reversing moves didn't bring us back to zero" # Test every DOF on A1-1 and SM and check the OTE state updated accordingly s = glob.glob(surdir+'/example_alldof_A1-SM_sur.xml')[0] print("Testing "+s) ote.reset() ote.move_sur(s) assert np.allclose(ote.segment_state[0], [1, 2, 3, 4, 5, 6]) assert np.allclose(ote.segment_state[-1], [1, 2, 3, 4, 5, 0]) # Test moving one at a time. This test relies on specifics of what's in the image stacking SUR. s = glob.glob(surdir+'/example_image_stacking*sur.xml')[0] print("Testing moving one group at a time with "+s) ote.reset() sur = webbpsf.surs.SUR(s) ngroups = len(sur.groups) oldstate = ote.segment_state.copy() for igrp in range(1, ngroups+1): print("Group {} should move segment {}".format(igrp, 2*igrp+6)) ote.move_sur(s, group=igrp) movedsegs = np.abs((ote.segment_state - oldstate).sum(axis=1)) assert (movedsegs!=0).sum()==1, "Only expected one segment to move" whichmoved = np.argmax(movedsegs)+1 print ("Moved segment", whichmoved) assert whichmoved == 2*igrp+6, "An unexpected segment moved" oldstate = ote.segment_state.copy() if plot: psf = nrc.calc_psf(fov_pixels=256, add_distortion=False) plt.figure() ote.display_opd(title="After Group {}".format(igrp)) plt.figure() webbpsf.display_psf(psf, ext=1, title="After Group {}".format(igrp))
def nircam_coro(filter, fpm, ppm, Aber_WSS): """ -- Deprecated function still used in analytical PASTIS and some notebooks. -- Create NIRCam image with specified filter and coronagraph, and aberration input. :param filter: str, filter name :param fpm: focal plane mask :param ppm: pupil plane mask - Lyot stop :param Aber_WSS: list or array holding Zernike coefficients ordered in WSS convention and in METERS :return: """ # Set up NIRCam and coronagraph nc = webbpsf.NIRCam() nc.filter = filter nc.image_mask = fpm nc.pupil_mask = ppm # Adjust OTE with aberrations nc, ote = webbpsf.enable_adjustable_ote(nc) nc.include_si_wfe = False # set SI internal WFE to zero ote.reset() ote.zero() for i in range(NB_SEG): seg = WSS_SEGS[i].split('-')[0] ote._apply_hexikes_to_seg(seg, Aber_WSS[i,:]) # Calculate PSF psf_nc = nc.calc_psf(oversample=1, fov_pixels=int(IM_SIZE_E2E), nlambda=1) psf_webbpsf = psf_nc[1].data return psf_webbpsf
def nircam_nocoro(filter, Aber_WSS): """ -- Deprecated function still used in analytical PASTIS and some notebooks. -- :param filter: :param Aber_WSS: :return: """ # Create NIRCam object nc = webbpsf.NIRCam() # Set filter nc.filter = filter # Adjust OTE with aberrations nc, ote = webbpsf.enable_adjustable_ote(nc) nc.include_si_wfe = False # set SI internal WFE to zero ote.reset() ote.zero() for i in range(NB_SEG): seg = WSS_SEGS[i].split('-')[0] ote._apply_hexikes_to_seg(seg, Aber_WSS[i,:]) # Calculate PSF psf_nc = nc.calc_psf(oversample=1, fov_pixels=int(IM_SIZE_E2E), nlambda=1) psf_webbpsf = psf_nc[1].data return psf_webbpsf
def test_apply_field_dependence_model(): ''' Test to make sure the field dependence model is giving sensible output Checks cases comparing master chief ray, center of NIRCam, and center of NIRISS. ''' rms = lambda array, mask: np.sqrt((array[mask]**2).mean()) # Get the OPD without any sort of field dependence ote = webbpsf.opds.OTE_Linear_Model_WSS(v2v3=None) opd_no_field_model = ote.opd.copy() mask = ote.get_transmission(0) != 0 # Get the OPD at the zero field point of v2 = 0, v3 = -468 arcsec # Center of NIRCAM fields, but not physically on a detector. ote.v2v3 = (0, -468) * u.arcsec ote._apply_field_dependence_model(assume_si_focus=False) # Do this test directly on the OTE OPD, without any implicit focus adjustments opd_zero_field = ote.opd.copy() * ote.get_transmission(0) rms1 = rms(opd_no_field_model - opd_zero_field, mask) assert(rms1 < 7e-9), "OPDs expected to match didn't, at center field (zero field dependence)" # Get the OPD at some arbitrary nonzero field point (Center of NRC A) ote.v2v3 = (1.4, -8.2) * u.arcmin ote._apply_field_dependence_model(assume_si_focus=False) opd_arb_field = ote.opd.copy() * ote.get_transmission(0) rms2 = rms(opd_no_field_model - opd_arb_field, mask) assert(rms2 > 7e-9), "OPDs expected to differ didn't" assert np.isclose(rms2, 26.1e-9, atol=1e-9), "field dep OPD at center of NIRCam A was not the expected value" # Now we invoke this via an SI class, to show that works too: # Get the OPD at the center of NIRISS nis = webbpsf.NIRISS() nis.pupilopd = None # disable any global WFE, so we just look at the field dependent part nis.detector_position = (1024, 1024) nis, ote_nis = webbpsf.enable_adjustable_ote(nis) # Test if we directly invoke the OTE model, in this case also disabling SI focus implicit optimization ote_nis._apply_field_dependence_model(assume_si_focus=False) opd_nis_cen = ote_nis.opd.copy() rms3 = rms(opd_nis_cen, mask) # The value used in the following test is derived from this model itself, so it's a bit circular; # but at least this test should suffice to detect any unintended significant change in the # outputs of this model assert np.isclose(rms3, 36.0e-9, atol=1e-9), "Field-dependent OTE WFE at selected field point (NIRISS center) didn't match expected value (test case: explicit call, assume_si_focus=False)" # Now test as usd in a webbpsf calculation, implicitly, and with the defocus backout ON # The WFE here is slightly less, due to the focus optimization nis = webbpsf.NIRISS() nis.pupilopd = None # disable any global WFE, so we just look at the field dependent part nis.detector_position = (1024, 1024) osys = nis.get_optical_system() opd_nis_cen_v2 = osys.planes[0].opd.copy() rms4 = rms(opd_nis_cen_v2, mask) assert np.isclose(rms4, 28.0e-9, atol=1e-9), "Field-dependent OTE WFE at selected field point (NIRISS center) didn't match expected value(test case: implicit call, assume_si_focus=True."
def generate_random_ote_deployment(out_dir, reduction_factor=0.2, save=True): """Create a WebbPSF adjustable OTE object representing a perturbed OTE mirror state by randomly generating mirror deployment errors. Parameters ---------- out_dir : str Path to directory in which to save OPD FITS files and the deployment error dictionary reduction_factor : optional Factor by which to reduce the input deployment errors. Default is 0.2. save : bool, optional Denotes whether to save out the OPD (with and without tip/tilt) as FITs files and the deployment error dictionary as a yaml Returns ------- ote : webbpsf.opds.OTE_Linear_Model_WSS object WebbPSF OTE object describing perturbed OTE state with tip and tilt removed segment_tilts : numpy.ndarray List of X and Y tilts for each mirror segment, in microradians ote_opd_with_tilts : numpy.ndarray Array representing perturbed OTE OPD pupil before tip/tilt is removed """ # Make an adjustable OTE object with WebbPSF nc = webbpsf.NIRCam() nc, ote = webbpsf.enable_adjustable_ote(nc) # Generate OPD and vector list with reduced deployment errors deployment_errors = generate_deployment_errors(out_dir=out_dir, save=save) deployment_errors = reduce_deployment_errors( deployment_errors, reduction_factor=reduction_factor, out_dir=out_dir, save=save) # Create an OTE object with those deployment errors ote, segment_tilts = apply_deployment_errors(ote, deployment_errors, out_dir=out_dir, save=save) ote_opd_with_tilts = ote.opd # Save array to plot below # Remove tip and tilt ote = remove_piston_tip_tilt(ote, out_dir=out_dir, save=save) return ote, segment_tilts, ote_opd_with_tilts
def set_up_nircam(): """ Return a configured instance of the NIRCam simulator on JWST. Sets up the Lyot stop and filter from the configfile, turns of science instrument (SI) internal WFE and zeros the OTE. :return: Tuple of NIRCam instance, and its OTE """ nircam = webbpsf.NIRCam() nircam.include_si_wfe = False nircam.filter = CONFIG_PASTIS.get('JWST', 'filter_name') nircam.pupil_mask = CONFIG_PASTIS.get('JWST', 'pupil_plane_stop') nircam, ote = webbpsf.enable_adjustable_ote(nircam) ote.zero(zero_original=True) # https://github.com/spacetelescope/webbpsf/blob/96537c459996f682ac6e9af808809ca13fb85e87/webbpsf/opds.py#L1125 return nircam, ote
def load_ote_from_deployment_yaml(deployments_file, out_dir, save=True): """Create a WebbPSF adjustable OTE object representing a perturbed OTE mirror state using mirror deployments defined in a YAML file. Parameters ---------- deployments_file : str Path to YAML file containing deployments data. Expects format as produced by ``mirage.psf.deployments.generate_deployment_errors`` out_dir : str Path to directory in which to save OPD FITS files save : bool, optional Denotes whether to save out the OPD (with and without tip/tilt) as FITs files Returns ------- ote : webbpsf.opds.OTE_Linear_Model_WSS object WebbPSF OTE object describing perturbed OTE state with tip and tilt removed segment_tilts : numpy.ndarray List of X and Y tilts for each mirror segment, in microradians ote_opd_with_tilts : numpy.ndarray Array representing perturbed OTE OPD pupil before tip/tilt is removed """ # Make an adjustable OTE object with WebbPSF nc = webbpsf.NIRCam() nc, ote = webbpsf.enable_adjustable_ote(nc) # Open existing file with previous deployments with open(deployments_file) as f: deployment_errors = yaml.unsafe_load(f) # Create OTE object and list of segment tilts ote, segment_tilts = apply_deployment_errors(ote, deployment_errors, out_dir=out_dir, save=save) ote_opd_with_tilts = ote.opd # Save array to plot below # Remove tip and tilt ote = remove_piston_tip_tilt(ote, out_dir=out_dir, save=save) return ote, segment_tilts, ote_opd_with_tilts
def test_enable_adjustable_ote(): """ Some basic tests of the OTE LOM""" nc = webbpsf.NIRCam() nc, ote = webbpsf.enable_adjustable_ote(nc) # did this produce an OTE object? assert isinstance(ote, webbpsf.opds.OTE_Linear_Model_WSS), "Didn't get an OTE object back" # can we compute the rms? rms = ote.rms() # and can we move a mirror? ote.move_seg_local('B1', piston=10, clocking=200) assert ote.segment_state[6, 2] == 10, "Couldn't piston" assert ote.segment_state[6, 3] == 200, "Couldn't clock" # did that misalignment make things much worse? assert ote.rms() > rms*10, "Huge piston offset didn't make the WFE much worse"
def test_enable_adjustable_ote(): """ Some basic tests of the OTE LOM""" nc = webbpsf.NIRCam() nc, ote = webbpsf.enable_adjustable_ote(nc) # did this produce an OTE object? assert isinstance(ote, webbpsf.opds.OTE_Linear_Model_WSS), "Didn't get an OTE object back" # can we compute the rms? rms = ote.rms() # and can we move a mirror? ote.move_seg_local('B1', piston=10, clocking=200) assert ote.segment_state[6, 2] == 10, "Couldn't piston" assert ote.segment_state[6, 3] == 200, "Couldn't clock" # did that misalignment make things much worse? assert ote.rms() > rms*10, "Huge piston offset didn't make the WFE much worse"
def generate_wavefront_errors(nb_of_maps, errors, nb_zernikes, path): import poppy, webbpsf import random import matplotlib.pyplot as plt # intial wavefront map nc = webbpsf.NIRCam() nc, ote = webbpsf.enable_adjustable_ote(nc) osys = nc._get_aberrations() # perturbed wavefront map nc_perturb = webbpsf.NIRCam() nc_perturb, ote_perturb = webbpsf.enable_adjustable_ote(nc_perturb) osys_perturb = nc_perturb._get_aberrations() # final wavefront map nc_final = webbpsf.NIRCam() nc_final, ote_final = webbpsf.enable_adjustable_ote(nc_final) osys_final = nc_final._get_aberrations() tab_opd_final = [] for n, error in zip(range(nb_of_maps), errors): print(n, error) # change aberrations in wavefront map: example with random zernikes # this map will be our perturbation map and we will add it to the initial map with a certain weight # creating the perturbation map #weight = 0.2 weight = error / 100 for i in range(nb_zernikes): #tmp = random.randint(-10,10) tmp = random.randint(-1, 1) osys_perturb.zernike_coeffs[ i] = weight * tmp * osys.zernike_coeffs[i] osys_final.zernike_coeffs[i] = osys.zernike_coeffs[ i] + weight * tmp * osys.zernike_coeffs[i] # implementing and displaying the wavefront maps #display_ote_and_psf(nc, ote, title="Initial OPD and PSF") ote_perturb.reset() ote_perturb.move_global_zernikes(osys_perturb.zernike_coeffs[0:10]) #display_ote_and_psf(nc_perturb, ote_perturb, title="Perturbed OPD and PSF") ote_final.reset() ote_final.move_global_zernikes(osys_final.zernike_coeffs[0:10]) #display_ote_and_psf(nc_final, ote_final, title="Final OPD and PSF") rms = ote.rms() rms_perturb = ote_perturb.rms() rms_final = ote_final.rms() print(rms, rms_perturb, rms_final) print('') #print(osys.zernike_coeffs) #print('') #print(osys_perturb.zernike_coeffs) #print('') #print(osys_final.zernike_coeffs) #print('') opd = poppy.zernike.opd_from_zernikes( osys.zernike_coeffs[0:10], npix=1024, basis=poppy.zernike.zernike_basis_faster) opd_perturb = poppy.zernike.opd_from_zernikes( osys_perturb.zernike_coeffs[0:10], npix=1024, basis=poppy.zernike.zernike_basis_faster) opd_final = poppy.zernike.opd_from_zernikes( osys_final.zernike_coeffs[0:10], npix=1024, basis=poppy.zernike.zernike_basis_faster) #tab_opd_final.append(opd_final) write_fits(path + '_opd' + str(n) + '.fits', opd) write_fits(path + '_opd_perturb' + str(n) + '.fits', opd_perturb) write_fits(path + '_opd_final' + str(n) + '.fits', opd_final) #plt.figure(figsize=(12,4)) #ax1 = plt.subplot(131) #ax1.imshow(opd) #ax1.set_title('initial wavefront map') #ax2 = plt.subplot(132) #ax2.imshow(opd_perturb) #ax2.set_title('perturbed wavefront map') #ax3 = plt.subplot(133) #ax3.imshow(opd_final) #ax3.set_title('sum of maps') #plt.show() wavefront_error = mse(np.nan_to_num(opd), np.nan_to_num(opd_final)) print('mse', error, wavefront_error) print("MSE: %.2f" % (wavefront_error * 100)) return tab_opd_final
def do_test_nircam_blc(clobber=False, kind='circular', angle=0, save=False, display=False, outputdir=None): """ Test NIRCam BLC coronagraphs Compute BLC PSFs on axis and offset and check the values against the expectation. Note that the 'correct' values are just prior calculations with WebbPSF; the purpose of this routine is just to check for basic functionaltiy of the code and consistency with prior results. See the validate_* tests instead for validation against independent models of JWST coronagraphy performance - that is NOT what we're trying to do here. """ nc = webbpsf_core.NIRCam() nc.pupilopd = None nc, ote = webbpsf.enable_adjustable_ote(nc) ote._include_nominal_field_dep = False # disable OTE field dependence model for this test # for consistency with expected values prepared before that model existed nc.filter = 'F210M' offsets = [0, 0.25, 0.50] if kind == 'circular': nc.image_mask = 'MASK210R' nc.pupil_mask = 'CIRCLYOT' fn = 'm210r' expected_total_fluxes = [1.84e-5, 0.0240, 0.1376 ] # Based on a prior calculation with WebbPSF # values updated slightly for Rev W aperture results # Updated 2019-05-02 for coron WFE - changes from [1.35e-5, 0.0240, 0.1376] to [1.84e-5, 0.0240, 0.1376] else: nc.image_mask = 'MASKSWB' nc.pupil_mask = 'WEDGELYOT' nc.options[ 'bar_offset'] = 0 # For consistency with how this test was developed # FIXME update the expected fluxes for the offset positions # which are now the default. fn = 'mswb' if angle == 0: expected_total_fluxes = [ 3.71e-6, .0628, 0.1449 ] # Based on a prior calculation with WebbPSF # Updated 2019-05-02 for coron WFE - changes from [2.09e-6, .0415, 0.1442] to [3.71e-6, .0628, 0.1449] elif angle == 45 or angle == -45: expected_total_fluxes = [3.71e-6, 0.0221, 0.1192] # Based on a prior calculation # Updated 2016-09-29 for Rev W results - slight change from 0.1171 to 0.1176 # Updated 2018-02-20 for recoded MASKSWB - changes from 0.0219 to 0.0220; 0.1176 to 0.1192 ?? # Updated 2019-05-02 for coron WFE - changes from [2.09e-6, 0.0220, 0.1192] to [3.71e-6, 0.0221, 0.1192] else: raise ValueError( "Don't know how to check fluxes for angle={0}".format(angle)) # If you change either of the following, the expected flux values will need to be updated: nlam = 1 oversample = 4 if outputdir is None: import tempfile outputdir = tempfile.gettempdir() if display: nc.display() plt.figure() #for offset in [0]: for offset, exp_flux in zip( offsets, expected_total_fluxes): #np.linspace(0.0, 0.5, nsteps): nc.options['source_offset_theta'] = angle nc.options['source_offset_r'] = offset fnout = os.path.join( outputdir, 'test_nircam_%s_t%d_r%.2f.fits' % (fn, angle, offset)) # We can save the outputs; this is not recommended or useful for general testing but is # helpful when/if debugging this test routine itself. if not os.path.exists(fnout) or clobber: psf = nc.calc_psf(oversample=oversample, nlambda=nlam, save_intermediates=False, display=display) #, monochromatic=10.65e-6) if save: plt.savefig(fnout + ".pdf") psf.writeto(fnout, clobber=clobber) else: psf = fits.open(fnout) totflux = psf[0].data.sum() #print("Offset: {} Expected Flux: {} Calc Flux: {}".format(offset,exp_flux,totflux)) # FIXME tolerance temporarily increased to 1% in final flux, to allow for using # regular propagation rather than semi-analytic. See poppy issue #169 assert abs( totflux - exp_flux ) < 1e-4, f"Total flux {totflux} is out of tolerance relative to expectations {exp_flux}, for offset={offset}, angle={angle}" #assert( abs(totflux - exp_flux) < 1e-2 ) _log.info( "File {0} has the expected total flux based on prior reference calculation: {1}" .format(fnout, totflux))
# Create NIRCam objects, one for perfect PSF and one with coronagraph print('Setting up the E2E simulation.') nc = webbpsf.NIRCam() # Set filter nc.filter = filter # Same for coronagraphic case nc_coro = webbpsf.NIRCam() nc_coro.filter = filter # Add coronagraphic elements to nc_coro nc_coro.image_mask = fpm nc_coro.pupil_mask = lyot_stop # Null the OTE OPDs for the PSFs, maybe we will add internal WFE later. nc, ote = webbpsf.enable_adjustable_ote(nc) # create OTE for default PSF nc_coro, ote_coro = webbpsf.enable_adjustable_ote( nc_coro) # create OTE for coronagraph ote.zero() # set OTE for default PSF to zero ote_coro.zero() # set OTE for coronagraph to zero nc.include_si_wfe = False # set SI internal WFE to zero nc_coro.include_si_wfe = False # set SI internal WFE to zero # Generate the E2E PSFs with and without coronagraph print('Calculating perfect PSF without coronograph...') psf_start_time = time.time() psf_default_hdu = nc.calc_psf(fov_pixels=int(im_size_e2e), oversample=1, nlambda=1) psf_end_time = time.time() print('Calculating this PSF with WebbPSF took',
def num_matrix_jwst(): """ Generate a numerical PASTIS matrix for a JWST coronagraph. All inputs are read from the (local) configfile and saved to the specified output directory. """ import webbpsf from e2e_simulators import webbpsf_imaging as webbim # Keep track of time start_time = time.time() # runtime is currently around 21 minutes print('Building numerical matrix for JWST\n') # Parameters resDir = os.path.join(CONFIG_INI.get('local', 'local_data_path'), 'active', 'matrix_numerical') which_tel = CONFIG_INI.get('telescope', 'name') nb_seg = CONFIG_INI.getint(which_tel, 'nb_subapertures') im_size_e2e = CONFIG_INI.getint('numerical', 'im_size_px_webbpsf') inner_wa = CONFIG_INI.getint(which_tel, 'IWA') outer_wa = CONFIG_INI.getint(which_tel, 'OWA') sampling = CONFIG_INI.getfloat('numerical', 'sampling') fpm = CONFIG_INI.get(which_tel, 'focal_plane_mask') # focal plane mask lyot_stop = CONFIG_INI.get(which_tel, 'pupil_plane_stop') # Lyot stop filter = CONFIG_INI.get(which_tel, 'filter_name') nm_aber = CONFIG_INI.getfloat('calibration', 'single_aberration') * u.nm wss_segs = webbpsf.constants.SEGNAMES_WSS_ORDER zern_max = CONFIG_INI.getint('zernikes', 'max_zern') zern_number = CONFIG_INI.getint('calibration', 'zernike') zern_mode = util.ZernikeMode( zern_number) # Create Zernike mode object for easier handling wss_zern_nb = util.noll_to_wss( zern_number) # Convert from Noll to WSS framework # If subfolder "matrix_numerical" doesn't exist yet, create it. if not os.path.isdir(resDir): os.mkdir(resDir) # If subfolder "OTE_images" doesn't exist yet, create it. if not os.path.isdir(os.path.join(resDir, 'OTE_images')): os.mkdir(os.path.join(resDir, 'OTE_images')) # If subfolder "psfs" doesn't exist yet, create it. if not os.path.isdir(os.path.join(resDir, 'psfs')): os.mkdir(os.path.join(resDir, 'psfs')) # If subfolder "darkholes" doesn't exist yet, create it. if not os.path.isdir(os.path.join(resDir, 'darkholes')): os.mkdir(os.path.join(resDir, 'darkholes')) # Create the dark hole mask. pup_im = np.zeros([im_size_e2e, im_size_e2e ]) # this is just used for DH mask generation dh_area = util.create_dark_hole(pup_im, inner_wa, outer_wa, sampling) # Create a direct WebbPSF image for normalization factor fake_aber = np.zeros([nb_seg, zern_max]) psf_perfect = webbim.nircam_nocoro(filter, fake_aber) normp = np.max(psf_perfect) psf_perfect = psf_perfect / normp # Set up NIRCam coro object from WebbPSF nc_coro = webbpsf.NIRCam() nc_coro.filter = filter nc_coro.image_mask = fpm nc_coro.pupil_mask = lyot_stop # Null the OTE OPDs for the PSFs, maybe we will add internal WFE later. nc_coro, ote_coro = webbpsf.enable_adjustable_ote( nc_coro) # create OTE for coronagraph nc_coro.include_si_wfe = False # set SI internal WFE to zero #-# Generating the PASTIS matrix and a list for all contrasts matrix_direct = np.zeros([nb_seg, nb_seg]) # Generate empty matrix all_psfs = [] all_dhs = [] all_contrasts = [] print('nm_aber: {}'.format(nm_aber)) for i in range(nb_seg): for j in range(nb_seg): print('\nSTEP: {}-{} / {}-{}'.format(i + 1, j + 1, nb_seg, nb_seg)) # Get names of segments, they're being addressed by their names in the ote functions. seg_i = wss_segs[i].split('-')[0] seg_j = wss_segs[j].split('-')[0] # Put the aberration on the correct segments Aber_WSS = np.zeros([ nb_seg, zern_max ]) # The Zernikes here will be filled in the WSS order!!! # Because it goes into _apply_hexikes_to_seg(). Aber_WSS[i, wss_zern_nb - 1] = nm_aber.to( u.m ).value # Aberration on the segment we're currently working on; # convert to meters; -1 on the Zernike because Python starts # numbering at 0. Aber_WSS[j, wss_zern_nb - 1] = nm_aber.to( u.m).value # same for other segment # Putting aberrations on segments i and j ote_coro.reset( ) # Making sure there are no previous movements on the segments. ote_coro.zero() # set OTE for coronagraph to zero # Apply both aberrations to OTE. If i=j, apply only once! ote_coro._apply_hexikes_to_seg(seg_i, Aber_WSS[ i, :]) # set segment i (segment numbering starts at 1) if i != j: ote_coro._apply_hexikes_to_seg(seg_j, Aber_WSS[j, :]) # set segment j # If you want to display it: # ote_coro.display_opd() # plt.show() # Save OPD images for testing opd_name = 'opd_' + zern_mode.name + '_' + zern_mode.convention + str( zern_mode.index) + '_segs_' + str(i + 1) + '-' + str(j + 1) plt.clf() ote_coro.display_opd() plt.savefig(os.path.join(resDir, 'OTE_images', opd_name + '.pdf')) print('Calculating WebbPSF image') image = nc_coro.calc_psf(fov_pixels=int(im_size_e2e), oversample=1, nlambda=1) psf = image[0].data / normp # Save WebbPSF image to disk filename_psf = 'psf_' + zern_mode.name + '_' + zern_mode.convention + str( zern_mode.index) + '_segs_' + str(i + 1) + '-' + str(j + 1) util.write_fits(psf, os.path.join(resDir, 'psfs', filename_psf + '.fits'), header=None, metadata=None) all_psfs.append(psf) print('Calculating mean contrast in dark hole') dh_intensity = psf * dh_area contrast = np.mean(dh_intensity[np.where(dh_intensity != 0)]) print('contrast:', contrast) # Save DH image to disk and put current contrast in list filename_dh = 'dh_' + zern_mode.name + '_' + zern_mode.convention + str( zern_mode.index) + '_segs_' + str(i + 1) + '-' + str(j + 1) util.write_fits(dh_intensity, os.path.join(resDir, 'darkholes', filename_dh + '.fits'), header=None, metadata=None) all_dhs.append(dh_intensity) all_contrasts.append(contrast) # Fill according entry in the matrix matrix_direct[i, j] = contrast # Transform saved lists to arrays all_psfs = np.array(all_psfs) all_dhs = np.array(all_dhs) all_contrasts = np.array(all_contrasts) # Filling the off-axis elements matrix_two_N = np.copy( matrix_direct ) # This is just an intermediary copy so that I don't mix things up. matrix_pastis = np.copy( matrix_direct) # This will be the final PASTIS matrix. for i in range(nb_seg): for j in range(nb_seg): if i != j: matrix_off_val = (matrix_two_N[i, j] - matrix_two_N[i, i] - matrix_two_N[j, j]) / 2. matrix_pastis[i, j] = matrix_off_val print('Off-axis for i{}-j{}: {}'.format( i + 1, j + 1, matrix_off_val)) # Normalize matrix for the input aberration matrix_pastis /= np.square(nm_aber.value) # Save matrix to file filename_matrix = 'PASTISmatrix_num_' + zern_mode.name + '_' + zern_mode.convention + str( zern_mode.index) util.write_fits(matrix_pastis, os.path.join(resDir, filename_matrix + '.fits'), header=None, metadata=None) print('Matrix saved to:', os.path.join(resDir, filename_matrix + '.fits')) # Save the PSF and DH image *cubes* as well (as opposed to each one individually) util.write_fits(all_psfs, os.path.join(resDir, 'psfs', 'psf_cube' + '.fits'), header=None, metadata=None) util.write_fits(all_dhs, os.path.join(resDir, 'darkholes', 'dh_cube' + '.fits'), header=None, metadata=None) np.savetxt(os.path.join(resDir, 'contrasts.txt'), all_contrasts, fmt='%e') # Tell us how long it took to finish. end_time = time.time() print('Runtime for matrix_building.py:', end_time - start_time, 'sec =', (end_time - start_time) / 60, 'min') print('Data saved to {}'.format(resDir))
:param filter: str, filter name :param fpm: focal plane mask :param ppm: pupil plane mask - Lyot stop :return: """ nc = webbpsf.NIRCam() nc.filter = filter nc.image_mask = fpm nc.pupil_mask = ppm return nc if __name__ == '__main__': nc_coro = setup_coro('F335M', 'MASK335R', 'CIRCLYOT') nc_coro, ote_coro = webbpsf.enable_adjustable_ote(nc_coro) ote_coro.zero() #ote_coro._apply_hexikes_to_seg('A1', [1e-6]) #ote_coro._apply_hexikes_to_seg('A3', [1e-6]) #ote_coro.move_seg_local('A6', xtilt=0.5) psf = nc_coro.calc_psf(oversample=1) psf = psf[1].data plt.subplot(1, 2, 1) ote_coro.display_opd() plt.subplot(1, 2, 2) plt.imshow(psf, norm=LogNorm()) plt.show()
def generate_wavefront_errors_correction(nb_of_maps, errors, nb_zernikes): import poppy, webbpsf import matplotlib.pyplot as plt # intial wavefront map nc = webbpsf.NIRCam() nc, ote = webbpsf.enable_adjustable_ote(nc) osys = nc._get_aberrations() # final wavefront map nc_final = webbpsf.NIRCam() nc_final, ote_final = webbpsf.enable_adjustable_ote(nc_final) osys_final = nc_final._get_aberrations() tab_wavefront_error = np.zeros(nb_of_maps) tab_error = np.zeros(nb_of_maps) for n, error in zip(range(nb_of_maps), errors): print(n, error) #print(zip(range(nb_of_maps))) #print(errors) # change aberrations in wavefront map: example with random zernikes # this map will be our perturbation map and we will add it to the initial map with a certain weight # creating the perturbation map #weight = 0.2 #weight = error/100 osys_corrected = osys.zernike_coeffs.copy() for i in range(nb_zernikes): if i < error + 1: osys_corrected[i] = 0 print(osys.zernike_coeffs) print(osys_corrected) opd = poppy.zernike.opd_from_zernikes( osys.zernike_coeffs, npix=1024, basis=poppy.zernike.zernike_basis_faster) opd_corrected = poppy.zernike.opd_from_zernikes( osys_corrected, npix=1024, basis=poppy.zernike.zernike_basis_faster) wavefront_error = mse(np.nan_to_num(opd), np.nan_to_num(opd_corrected)) print('mse', error, wavefront_error) #tab_opd_final.append(opd_final) #write_fits('_opd'+str(n)+'.fits',opd) #write_fits('_opd_corrected'+str(n)+'.fits',opd_corrected) plt.figure(figsize=(12, 4)) ax1 = plt.subplot(131) #ax1.imshow(opd,vmin=np.min(opd),vmax=np.max(opd)) ax1.imshow(opd) ax1.set_title('initial wavefront map') ax2 = plt.subplot(132) #ax2.imshow(opd_corrected,vmin=np.min(opd),vmax=np.max(opd)) ax2.imshow(opd_corrected) ax2.set_title('corrected wavefront map') ax3 = plt.subplot(133) ax3.imshow(opd - opd_corrected) ax3.set_title('sum of maps') plt.show() tab_wavefront_error[n] = wavefront_error tab_error[n] = error return tab_wavefront_error, tab_error